• 一般社団法人Pythonエンジニア育成推進協会 顧問理事 寺田学氏

    著者:寺田学
    一般社団法人Pythonエンジニア育成推進協会 顧問理事

一般社団法人Pythonエンジニア育成推進協会(以下、当協会)の顧問理事の寺田学です。私は試験の問題策定とコミュニティ連携を行う立場です。 プログラミングの経験を積むには「まずは作って動かす」ということが大事になります。 実際に自分で作ったプログラムをいざ動かしてみると、思った通りに動かないということはままあると思います。 そして、動かなかったときにその原因を探り、ひとつひとつ修正していくことになりますが、初心者にとってそれが難しいというケースはよくあります。 そこで今回はデバックとテストをテーマにお話ししてみたいと思います。

プロの現場はテストコードで製品の品質を担保する

たとえばプロの世界できちんとした製品を作る場合には、まずはテストコードを作り、テストが通るように実装していく「テストファースト」という考え方があります。 各機能を作っている時点ではどこが動かないのかはわかりませんし、特定のパターンで動かなくなるといったケースももちろん出てきます。 そのため、まずはユニットテスト(単体テスト)で、ひとつひとつの機能がきちんと動くかを確かめていくという作業が行われます。もちろんテストコードが適切に書かれている必要がありますが、テストがきちんと通れば自信をもって機能として成り立っていると言うことができるようになります。 そしてその後、ファンクショナルテスト(機能テスト)を経て、最終的にはエンドツーエンドテストという、作った機能をすべて結合して行う結合試験を行うことで、システム全体をトータルして見たときに問題なく動くか、ユーザー視点で見たときにも想定した動きになっているかといったことを確かめる作業が入ります。 ちなみにテストコードの作成にはpytestというライブラリを使って、ファンクショナルテスト、ユニットテストといったコードを書くことができます。

初心者におすすめのデバック方法

とはいえ、プログラミングを始めたばかりの人やデータ分析の世界にいる人たちにとって、テストを適切に定義して最初からきちんと書くことは難しく、おそらくほとんどの人は何かしらのスクリプトや関数を書いて、動かしながら動作を確認していると思います。 最初のうちはそれでいいと思いますが、この手法をとる場合には、どうバグやエラーをなくしていくかを考える必要があります。 そこでよく利用されるデバック方法として、printデバッグがあります。

printデバック

print文を利用したデバックです。 print文をプログラムの途中に仕掛け、出力されたものを見ることで、本来入るべき値が入っているかを確認します。 たとえばif文で条件に合致したときに入れるといった処理の時、if文の間にprint文をいれることで適切な処理が順番にされているかを確認したり、ある条件を設定したif文に対して間違った判定がされ、別のところに処理がいってしまっていることを確認したりといったことができます。 特にif~elif~elseの場合、どの処理に入っているかはデータ構造などから想像するしかありませんが、print文を仕掛けることで適切に動いているかを確認できるようになります。 これはfor文の繰り返し回数の確認にも使用できる手法です。 こうすると、どこで処理が間違った方向に行ってしまったか、適切な入力値が入っているか、入力値が適切かを見ることができます。 返ってきた値が想定と違うという時には、まずはprint文を使って適切なものが順番に入っているかをマークしてみましょう。

<便利な使い方>

print関数は、オブジェクトや変数を入れたときには出力可能な文字列を必ず返してくれる決まりになっています。ただ、普通の文字列であればそのまま返されるので問題ありませんが、特殊なオブジェクトの場合、実際に入っている値がわからないということはよくあります。 慣れてくればトレースバックを適切に読めるようになりますが、うまく読み取れるようになるには時間がかかると思います。 そこで役立つ組み込み関数がtype関数とdir関数です。

①type関数

オブジェクトを一つだけ与えるとその値の型が返される関数で、オブジェクトに入っている値が文字列ならstr、数値ならint、リストならlistといったように、どのような型が入っているかを教えてくれます。 たとえば以下のように変数同士の計算を行ったときにエラーが発生したとします。 そこでトレースバックを正しく読み取れれば、数値と文字列では型が違うから足し算ができないというエラーの原因を読み解くことができますが、慣れないうちは難しいケースがあります。 そうしたときに、下図のようにtype関数を使えば、変数aと変数bに入っている値の型を明確にでき、足し算ができなかった原因を突き止めることができます。

  • type関数を使えば、変数aと変数bに入っている値の型を明確にでき、足し算ができなかった原因を突き止めることができます

②dir関数

オブジェクトが持っているメソッドや属性を表示してくれる関数です。 これは複雑なオブジェクトであるほど役立つもので、属性やメソッドがわかることでそのオブジェクトでできそうなことを読み取ることができます。 表示された属性やメソッドの項目については公式ドキュメントにも記載されていますので、検索してみてください(help関数もありますが、ドキュメントを見た方が早いと思います)。

  • dir関数は、オブジェクトが持っているメソッドや属性を表示してくれる関数です。

慣れたらloggerを使って流れを確認する

実際のプロダクトを作るときにはprint関数では形式がわかりにくいため、logger(ロガー)を設定してログファイルとして情報を書き出して確認しています。 多くのWebフレームワークではloggingモジュールですでにloggerの基本的な設定がされているため、ログの出力したいレベル(デバック、インフォ、ワーニング、エラーといったレベルを選べる)に応じてプロダクション内のコードに残し、適切なログを取得できるよう仕込んでおきます。 ログファイルは処理が進むほど追記されていく形なので、動かしていけば一連の流れで見ることができます。 ちなみに、ロガーがデバックモードに対応しているのであれば、コンソールにログが流れるようになっているものもたくさんありますので、コンソールでログの流れを確認することもできます。

トレースバックを正しく読んで、エラーの内容を把握する

Pythonで表示されるエラーメッセージ(トレースバック)については、丁寧でわかりやすいものにしようという動きが3.10以降に盛んに行われています。昔からpythonはほかの言語に比べると比較的トレースバックが丁寧で、内容を見ればある程度どこで何が起きているかがわかるようになっています。 先ほどの例で出た変数aと変数bの足し算で出たトレースバックで確認してみましょう。

  • 「変数a,bの値がint(数値)とstr(文字列)と型が異なるため足し算はできない(サポートされていない)」というTypeError

ここで出たトレースバックからは「変数a,bの値がint(数値)とstr(文字列)と型が異なるため足し算はできない(サポートされていない)」というTypeError(型エラー)が出たということが読み取れます。

この例自体はシンプルなので、聞けば納得できるものだとは思いますが、場合によっては長文のエラーが出るため、どこを見たらいいかわからないとしり込みしてしまう人も出てくると思います。 トレースバックの読み方のコツとして、大体最後の行(●●Error:~)にヒントがあるということを覚えておいてください。 そして、まずは出てきたエラーをまずはそのままGoogleで検索をしてみましょう。同じエラーや似たようなものを解説してくれているページが見つかることがあります。 とにかく最後の行を見て適切にエラーを読み解くこと、そして、エラーの種類自体はサードパーティ製のライブラリを使用している場合は別で確認する必要がありますが、Pythonのみなら大体10種類くらいを覚えておけば問題ありません。 たとえば、SyntaxError、IndentationError、ModuleNotFoundError、AttributeError、TypeError、ValueError、NameError、KeyErrorなどです。それぞれの意味についてはぜひ検索してみてくださいね。

breakpoint関数でデバッガーを起動、プログラムの流れと問題個所を把握

IDE(統合開発環境)にはブレークポイントという、どこで止めるかを設定できる機能があります。これを適切に使いこなすことができれば、該当箇所で一度処理を止めて、次の処理に行くときの流れを確認できるデバッガーを利用することができます。 print文ではどうしてもミスが分からないことはありますので、そういったときにはこのデバッガーの機能を利用します。

デバッガーの使い方は色々あり、統合開発環境固有のデバッガーを使う方法もあります。 私自身はこれを使うことはあまりなく、どちらかというとPythonの組み込み関数であるbreakpoint関数でデバッガーを使用しています。

breakpoint関数は上手く通っていないと思われる処理のところに[breakpoint()]と記述して実行すると、コンソールの方で処理の流れを把握できる状態を作り出すことができます。 これは以前であれば[import pdb; pdb.set_trace()]という形で書かれていましたが、バージョン3.7で[breakpoint]が組み込み関数として追加されたものです。確かにデバッガーの使い方は覚えなくてはなりませんが、便利に使える機能です。

Jupyter Notebookなどを使っているデータ分析者であれば、デバッガーを使うよりは、Jupyter NoteBookで処理を分割してしまった方が速いため、データ分析の世界ではそこまでデバッガーは重要視されていません。 ただ、Web系やコンソール系、自動処理系のプログラムを作っている場合にはbreakpoint関数はよく使用されていますので、その方面を目指すのであれば覚えておいた方がいいと思います。

まとめ

テストコードが書けるようになれば、いろいろなパターンの入力値、出力値のテストができるようになります。 プログラムを作るにあたっての動作確認はやはりテストコードを書くのが良く、最終的にはテストコードで担保したいところですが、そうはいっても初心者が最初からできるようなものではありません。

ロガーを使うにしても、最初は設定が難しいと思いますので、まずはprintデバッグでコンソール上から確認する方法に慣れましょう。 そしてprintデバッグやロガーなどで流れを確認できるようになり、テストコードも書けるようになったのであれば、デバッガーを使わなくても問題ないと思いますし、実際、デバッガーを使わない人はいます。

ただ、個人的にはデバッガーを積極的に使って、どのオブジェクトが、どのようになっているかという流れを確認できるようになっていた方がいいと考えています。なぜなら、デバッガーを活用できるようになれば、より効率的に自分がやりたい処理を実現する方法がわかりやすくなるからです。

どのようなやり方でデバッグをするにしても、最終的な目的としては、適切なものが来ているか、通過して処理ができているかを見ることに変わりはありません。 printデバッグ、ロガー、デバッガー、テストコードといろんな手法にチャレンジすることで、自分の作りたいものを効率的に作れるよう、学んでいっていただければと思います。

当協会の最新情報は公式サイトか、公式Facebookページでご覧いただけます。FacebookページではPythonに関連したニュースもお知らせしていますので、ぜひフォローしてみてください。また、YouTubeチャンネル「Pythonエンジニア認定試験」では、私が試験概要や学習のコツをお話ししたものや、合格した方のコメント動画を公開しています。こちらもぜひご覧ください。

[PR]提供:Pythonエンジニア育成推進協会