はじめに
こんにちは。孔子の80代目子孫兼アプリケーションサービス部の孔です。受験戦争で有名な韓国で育った私には、やっぱり「テスト」というと恐怖の対象なので、なるべく避けていきたい道でしたが、やっぱり開発者としてこれは避けては通れない道だと思い、ちゃんとテストツールを勉強することにしました。そこでpytestを勉強し、ある程度使い方がわかってきたので、自分の勉強内容を共有するためにシリーズでブログを書きます。全くpytestを触ったことない方でも「わかりやすく」&「全体像がわかるように」を意識して書きますので
- pytestってよく聞くけどどんなもの?どうやって使うの?
- pytestを勉強したけど、全然理解ができない、わかりやすくまとめた資料とかないかな?
- Pythonでアプリケーションを作って、テストをしたいけど、どんなテストツールがあるんだろう?
と思ってる方、ぜひこのシリーズをお読みください!それでは、早速スタートです。
テストってなんで必要?
pytestの紹介をする前に、そもそもなんでソフトウェアテストが必要なのかを見てみましょう。例えば、以下のようなPythonコードがあるとします。
def add_one(n): return n + 1 add_one(3) # 4を返す
さて、add_one(3)
は4を返すとこのコードを書いた人はコメントを残しています。でも、本当にそうなんでしょうか?それはどう保証できるんでしょうか?実は保証することは簡単で、実際動かしてみればいいんです。インタープリターモードで動かしてみましょう。
>>> def add_one(n): ... return n + 1 ... >>> add_one(3) 4
想定通り、4を返してくれたので、add_one(3)
が4を返してくれるのが保証できます!やった!このように、「書いたコードが想定通りに動くのか?」を保証するために、テストは必要です。
pytest概要
上記のadd_one
関数はすぐ動作を確認できるからよかったですが、実際書くコードはより複雑ですよね。さらに、アプリケーションを動かすためのセッティングが必要だったり、特定の環境を作ったりする必要もあったりあします。そうなると、毎回インタープリターでやってみたり、実際コードを実行してみるのも困難です。その時に有用なのがテストツールになります。
Pythonで使用されるテストツールはいろいろありますが、その中でpytestが選ばれるのは便利でわかりやすいからですね。具体的には
- 使い方がわかりやすい
- エラーレポートがわかりやすい
- テストの検索が優秀
などの理由から、選ばれています。pytestのメリットは本ブログシリーズ内でも紹介していきますが、実際使っていただきながら実感いただければと思います。
pytestインストール
ここまでが簡単な紹介で、それでは実際pytestを触ってみましょう。インストール方法はpipからインストールができます。
$ pip install -U pytest $ pytest --version pytest 7.0.1 # この記事の作成時点でのバージョンとなりますので、実際に表示されるバージョンと異なる可能性があります。
pytestがインストールされたので、早速テストを作ってみましょう。先ほど書いたadd_one
関数をテストするテストを書きたいと思います。
$ mkdir pytest_directory $ cd pytest_directory $ touch test_func.py
test_func.py
ファイルに以下のようにテストコードを書いてみましょう。
# test_func.py def add_one(n): return n + 1 def test_add_one(): assert add_one(3) == 4
pytestでは、assert文(※1)を使ってテストを行います。assert文は簡単に言うと、assert以下の式が(この場合はadd_one(3) == 4
)TrueでなかったらAssertionErrorを返す文です。
コードを作成したら、以下のコマンドを実行してみましょう。
$ pytest ========================== test session starts =========================== platform linux -- Python 3.9.10, pytest-7.0.1, pluggy-1.0.0 rootdir: /home/ec2-user/pytest_directory collected 1 item test_func.py . [100%] =========================== 1 passed in 0.01s ============================
無事通ったようですね。これで、add_one
関数が想定通りに動くことがわかりました!失敗するケースも見てみましょう。以下のようにtest_func.pyコードを修正します。
# test_func.py def add_one(n): return n + 1 def test_add_one(): assert add_one(3) == 5
そしてpytestコマンドを実行すると、以下のようになります。
$ pytest ========================== test session starts =========================== platform linux -- Python 3.9.10, pytest-7.0.1, pluggy-1.0.0 rootdir: /home/ec2-user/pytest_directory collected 1 item test_func.py F [100%] ================================ FAILURES ================================ ______________________________ test_add_one ______________________________ def test_add_one(): > assert add_one(3) == 5 E assert 4 == 5 E + where 4 = add_one(3) test_func.py:5: AssertionError ======================== short test summary info ========================= FAILED test_func.py::test_add_one - assert 4 == 5 =========================== 1 failed in 0.03s ============================
どの関数が失敗していて、なぜ失敗しているのかを具体的に教えてくれてるので、なぜテストが失敗したかがよくわかります。ここまでが簡単なpytestの使い方でした。ただし、ちょっと疑問があります。pytestはテスト用のPythonファイルがどれなのか、どうやって分かったの?それに、どの関数がテスト用関数なのか(なぜtest_add_one
関数がテスト関数とわかったのか)、どうやってわかったの?ということです。その仕組みを今から見ていきましょう。
pytestがテストファイル・テスト関数を判別する方法
pytestはデフォルトでは以下の仕様になります。
test_
で始まる関数はすべてテスト対象となるTests
で終わるクラスはすべてテスト対象となるtest_
で始まるPythonファイルはすべてテスト対象となる
なので、test_func.py
ファイルにあるtest_add_one
関数はテスト対象と、pytestが判断できました。ちなみに、このルールに従っていれば、pytestを実行する配下にあるすべてのディレクトリに対して、上記のルールが適用されます。
「デフォルトでは」このような仕様になりますが、どの関数、クラス、ファイルを対象にするかは変更することができます。いろいろ方法はありますが、今回はpytest.ini
ファイルを使って設定を変更してみましょう。
$ pwd /home/ec2-user/pytest_directory $ touch pytest.ini
作成したpytest.ini
ファイルに、以下のように記載してみましょう。
[pytest] python_files = test_* python_classes = *Tests python_functions = *_test
それぞれの設定値は以下を意味しています。
- python_files:テスト対象ファイル
- python_classes:テスト対象クラス
- python_functions:テスト対象関数
それでは、再度pytestコマンドを実行してみましょう。test_add_one
という関数名は*_test
に当てはまらないので、テスト対象としてみなされず、何のテストも行われないだろうと予想できます。
$ pytest ========================== test session starts =========================== platform linux -- Python 3.9.10, pytest-7.0.1, pluggy-1.0.0 rootdir: /home/ec2-user/pytest_directory, configfile: pytest.ini collected 0 items ========================= no tests ran in 0.00s ==========================
予想通り、テスト対象がなにも見つかってないのがわかります(collected 0 items)それでは、コードを修正しましょう。test_add_one
だった関数名をadd_one_test
に変更してみます。
def add_one(n): return n + 1 def add_one_test(): assert add_one(3) == 4
そして再度pytestを実行してみます。
$ pytest ========================== test session starts =========================== platform linux -- Python 3.9.10, pytest-7.0.1, pluggy-1.0.0 rootdir: /home/ec2-user/pytest_directory, configfile: pytest.ini collected 1 item test_func.py . [100%] =========================== 1 passed in 0.01s ============================
正常に見つかりましたね!これで、pytest.ini
の設定が正しく反映されたことがわかります。補足ですが、pytest.ini
ファイルが存在するディレクトリ配下でpytestコマンドを実行してもルールが適用されます(以下のターミナルの出力の6行目に、ルートディレクトリがpytest.ini
ファイルが配置されたディレクトリになっていることがわかります)
$ pwd /home/ec2-user/pytest_directory/test $ pytest ========================== test session starts =========================== platform linux -- Python 3.9.10, pytest-7.0.1, pluggy-1.0.0 rootdir: /home/ec2-user/pytest_directory, configfile: pytest.ini collected 0 items ========================= no tests ran in 0.00s ==========================
pytest.ini
ファイルを削除したら以下のようになります(ルートディレクトリが現在のディレクトリに変わる)
$ pwd /home/ec2-user/pytest_directory $ rm pytest.ini $ ls __pycache__ test test_func.py $ cd test $ pytest ========================== test session starts =========================== platform linux -- Python 3.9.10, pytest-7.0.1, pluggy-1.0.0 rootdir: /home/ec2-user/pytest_directory/test collected 1 item test_something.py . [100%] =========================== 1 passed in 0.00s ============================
pytest.ini
ファイルで設定できるのはほかにもいろいろありますので、ご興味ありましたら公式ドキュメント(※2)もご参照ください。
最後に
今回はpytestのインストール方法、使い方、そしてテスト対象を設定する方法についてみてみました。pytestの良さを味わう一歩目を踏み出せましたね。次回はマーカーについてみていきます。それでは、また次回もよろしくお願いします!
参考資料
- pytest公式ドキュメント
- ※1 assert文
- ※2 pytest.iniで設定できる項目