pytest 使い方まとめ
西田@大阪です
pythonのテストフレームワークであるpytestの主だった使い方をまとめてみました
今回の記事で利用したバージョンは以下です
- Python: 3.6.5
- pytest: 3.8.0
- freezegun: 0.3.10
テスト対象
ファイルはtestから始まるファイルが対象になります
1 | test_sample.py |
メソッドはtestから始まる関数が対象になります
1 2 | def test_something(): # ~~~ |
複数のテストをまとめたいときはclassを使いグルーピングしたいテストをそのクラスのメソッドとして記述します
1 2 3 4 5 | class TestGroup(object): def test_something_1(): # ~~~ def test_something_2(): # ~~~ |
assertion
assert キワードのあとに bool 値を返す式を書きます
1 2 | a = 1assert 1 == a |
例外のテスト
特定の例外を送出することを確認するのにはpytest.raisesを使います
1 2 3 | def test_exception(): with pytest.raises(ValueError) raise ValueError() |
mark
markを使ってテストにメタデータを付与することができます
テストをスキップ(ペンディング)したい場合は @pytest.mark.skip を使います
1 2 3 | @pytest.mark.skipdef test_skip(): # ~~~ |
複数のパターンのパラメーターをテストする場合には @pytest.mark.parametrizeが使えます。第1引数にテスト関数で使うための変数名をカンマ区切りで指定し、第2引数に配列でパラメーターを指定します
1 2 3 4 5 6 | @pytest.mark.parametrize("x,y,sum", [ (1, 2, 3), (2, 3, 5),])def test_add(x, y, sum): assert (x + y) == sum |
fixture
テストに必要なオブジェクトを提供するのにfixtureは使えます
@pytest.fixtureでデコレートした関数の関数名と同じ名前の引数をテスト関数に設定して使います
1 2 3 4 5 6 7 8 9 10 11 | class Client(object): def send(self, msg): # ...@pytest.fixture()def client(): return Client('connect info')def test_sample(client): client.send('message') # ... |
fixtureをつかってテストの前後に処理を挿入することもできます
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 | class Client(object): def connect(self): # ... def send(self, msg): # ... def close(self): # ...@pytest.fixture()def client(): # -- 前処理 client = Client() client.connect() # テスト実行 yield client # -- 後処理 client.close()def test_sample(client): client.send('message') # ... |
複数のテストにまとめてfixuterを設定したい場合はクラスに @pytest.mark.usefixturesを設定します
1 2 3 4 5 6 7 8 9 10 11 | @pytest.fixture()def sample_fixture(): # ...@pytest.mark.usefixtures('sample_fixture')class TestSample(object): def test_sample_1(self): # 引数に設定しなくてもsample_fixtureが実行前によばれる def test_sample_2(self): # 引数に設定しなくてもsample_fixtureが実行前によばれる |
グローバルで使える共通のfixtureを作成した場合は conftest.pyに記述します
1 2 3 4 5 6 7 8 | # ./conftest.py@pytest.fixturedef global_fixture(): # ...# ./test_sample.pydef test_sample(global_fixture): # ... |
conftest.pyは置かれたディレクトリ以下のすべてのテストで有効になります
1 2 3 4 5 6 | -- tests/ |-- conftest.py |-- test_sample_1.py |-- sub/ | |-- conftest.py | |-- test_sample_2.py |
上記の例ですと
test_sample_1.pyの中のテストはtest/conftest.pyが有効ですsub/test_sample_2.pyの中のテストはtest/conftest.pyとtest/sub/conftest.pyが有効になります
またautouseをTrueに設定することで、引数やデコレーターで指定をしなくても自動で実行されるfixtureを作ることができます
autouseがTrueのfixtureをconftest.pyに書くことによって、conftest.pyが有効な範囲のテストに前後処理を追加することできます
下記はscopeにfunctionを設定しすべてのテスト実行前に処理を追加している例です
1 2 3 4 5 6 7 8 9 10 11 | # ./conftest.py@pytest.fixture(scope="function", autouse=True)def setup(): # ...# ./test_sample.pydef test_sample_1(): # テスト実行前にsetup()が呼ばれるdef test_sample_2(): # テスト実行前にsetup()が呼ばれる |
Mock
monkeypatch fixture を使えば既存の関数をモックすることができます
※ ただし datetime.datetime.now などCで書かれている組み込み型はこの方法ではモックできません。
1 2 3 4 5 6 7 8 9 10 11 | class MockTeapotResponse: def getcode(self): return 418def test_mock(monkeypatch): def mock_urlopen(*_): return MockTeapotResponse() monkeypatch.setattr(request, 'urlopen', mock_urlopen) assert res.getcode() == 418 |
時間を操作
monkeypatch fixtureを使っても datetime.datetime.now をMockして時間を操作することはできませんが、 freezegunを使うことで時間を操作してテストを行えます
1 2 3 4 5 6 7 8 9 10 11 | from datetime import datetimefrom freezegun import freeze_timedef test_freeze_time(): now = datetime.now() with freeze_time(now) as frozen_time: assert datetime.now() == now # tickで時間を進める frozen_time.tick(timedelta(seconds=1)) assert datetime.now() == now + timedelta(seconds=1) |
まとめ
いかがでしたでしょうか?
標準のunittestよりも簡潔にかけるので、これからpythonでテストを書くときは積極的に使っていきたいと思います