これは?
単体テストやりたいよね〜 pytestの書き方メモっとこうか
pytestの準備
インストール
pip install pytest
toyコード作成
ディレクトリ構成
pytest_work/ data/ out.csv src/ work.py tests/ test_sample.py
work.py
# -*- coding: utf-8 -*- """ DFを二つ作り、結合してその件数をチェックする処理をpytestで実施してみる。 """ import pandas as pd from pathlib import Path def main(): a = pd.DataFrame( { 'a': [1, 2, 3], 'b': [12, 13, 14] } ) b = pd.DataFrame( { 'a': [3, 4, 5], 'c': ['a', 'b', 'c'] } ) c = pd.merge(a, b, on='a') out_file = Path.cwd().parent / "data" / "out.csv" c.to_csv(out_file, index=False) if __name__ == '__main__': main()
work.pyを実行すると、DFのaとbの'a'というカラムの重複分がDFとして作成されて、data/out.csvとして出力される。
これに対し、tests/test_sample.pyでpytestを実施するイメージでやってみる。
テスト
test_sample.py
# -*- coding: utf-8 -*- import pytest import pandas as pd from pathlib import Path class TestSample: def test_return(self): x = 'test' assert x.find('st') != -1 def test_uniq(self): target_file = Path.cwd().parent / "data" / "out.csv" x = pd.read_csv(target_file) assert len(x) != 0 if __name__ == '__main__': a = TestSample() a.test_uniq()
classにTestから始まる名称を作成し、その中の関数にtest_で始まるように定義していく。
test_return関数は、固定の変数に対するテストでtest_uniqがout.csvが0件でなければエラーとなるように記載している。
この状態で、tests/に移動して以下を実行するとテストができる。
(pytest-work-P7cVJe5b-py3.9) bash-3.2$ pytest test_sample.py ========================================================== test session starts ========================================================== platform darwin -- Python 3.9.5, pytest-7.2.1, pluggy-1.0.0 rootdir: /Users/shirai_y/work/tmp/pytest_work/tests collected 2 items test_sample.py .. [100%] =========================================================== 2 passed in 0.57s ===========================================================
これは、2つのテストをpassしたよってこと。
特定のテストだけ走らせたい場合は以下のようにすると良い。
(pytest-work-P7cVJe5b-py3.9) bash-3.2$ pytest test_sample.py::TestSample::test_uniq ========================================================== test session starts ========================================================== platform darwin -- Python 3.9.5, pytest-7.2.1, pluggy-1.0.0 rootdir: /Users/shirai_y/work/tmp/pytest_work/tests collected 1 item test_sample.py . [100%] =========================================================== 1 passed in 0.54s ===========================================================
テストファイル名の後に「::」でクラス名やその後に「::」で関数名を記載すると、指定したテストだけが実施できる。
便利な機能
assertの第二引数にメッセージを書くと、エラーの内容がテスト時に出力される。
test_sample.py
# -*- coding: utf-8 -*- import pytest import pandas as pd from pathlib import Path class TestSample: def test_return(self): x = 'test' assert x.find('st') != -1 def test_uniq(self): target_file = Path.cwd().parent / "data" / "out.csv" x = pd.read_csv(target_file) assert len(x) != 0, "重複しているレコードが無いよ!" # こんな感じでエラーの内容を記載。 if __name__ == '__main__': a = TestSample() a.test_uniq()
pytest実行。
(pytest-work-P7cVJe5b-py3.9) bash-3.2$ pytest test_sample.py ========================================================== test session starts ========================================================== platform darwin -- Python 3.9.5, pytest-7.2.1, pluggy-1.0.0 rootdir: /Users/shirai_y/work/tmp/pytest_work/tests collected 2 items test_sample.py .F [100%] =============================================================== FAILURES ================================================================ _________________________________________________________ TestSample.test_uniq __________________________________________________________ self = <test_sample.TestSample object at 0x11c90f7f0> def test_uniq(self): target_file = Path.cwd().parent / "data" / "out.csv" x = pd.read_csv(target_file) > assert len(x) != 0, "重複しているレコードが無いよ!" E AssertionError: 重複しているレコードが無いよ! E assert 0 != 0 E + where 0 = len(Empty DataFrame\nColumns: [a, b, c]\nIndex: []) test_sample.py:15: AssertionError ======================================================== short test summary info ======================================================== FAILED test_sample.py::TestSample::test_uniq - AssertionError: 重複しているレコードが無いよ! ====================================================== 1 failed, 1 passed in 0.42s ======================================================
エラーが出たときに何のエラーなのかがわかりやすくて良き。
fixture
conftest.pyにテストで共通で使うような関数を記述しておくと、これをpytestの実体コード上で扱うことができる。
fixture.py
# -*- coding: utf-8 -*- import pytest import pandas as pd from pathlib import Path @pytest.fixture(scope="class") def df_load_output(): return pd.read_csv(Path.cwd().parent / "data" / "out.csv")
test_sample.py
# -*- coding: utf-8 -*- import pytest class TestSample: def test_return(self): x = 'test' assert x.find('st') != -1 def test_uniq(self, df_load_output): # 引数にtextureで指定した関数名を追加して、 x = df_load_output # その名称で使うとfixtureの処理が実行できる。 assert len(x) != 0, "重複しているレコードが無いよ!" if __name__ == '__main__': a = TestSample() a.test_uniq()
重複チェック以外にも同じファイルに対する処理をする場合に、毎回load処理を記述しなくても良いのが良いね。
あとは、DBコネクションとか共通系の処理はfixtureに書いてあげると良いみたい。