概要
pytestによるテストの構造化
pytestを記述していると特定のメソッドだけを実行したい、あるいは特定のメソッド以外を実行したいといったシーンが出てくるかと思います。そんなときに使える便利機能がpytest.markとpytest-describeになります。pytest-describeでテストを見やすく構造化し、その中から指定のテストを実行するためにpytest.markを使用します。これによりテストの範囲や状況に応じたテスト実行が可能になります。
pytest.mark
pytest.markはpytestの機能の一部、特定のテストをマーク(ラベル付け)し、選択的に実行することができる機能です。マークをつけることで、条件に応じたテストの実行やスキップが可能です。またテスト環境、カテゴリー、スピードなどによってテストを分類できるので大規模なテストスイートの管理が容易になります。
pytest-describe&pytest-spec
pytest-describeはpytest用のプラグインで、テストの可読性を高めるためにネストされた記述的なテスト構造を提供します。describeを用いて構造的に記述することで、テストのシナリオや意図を明確にしやすくします。特にソフトウェアの動作を自然言語で定義した後、テストを記述していくBDD(振る舞い駆動開発)と相性が良いといわれています。さらにpytest-specを導入することでテスト結果を仕様書のようにわかりやすく可視化することができます。
本記事ではこの機能は扱いませんが下記の記事にて解説していますので、興味がある方はご参考ください。
pytest.markの使い方
前提
その他にpytestの実装に関する基本的なことを学びたい方は下記の記事をご参考ください
pytest.markの使い方
@pytest.mark.parametrize()
このマーカーは同じテストを異なるパラメータで繰り返し実行するためのマーカーです。このマーカーを使用すると、複数の異なる入力に対してテストを簡単に行うことができます。
3行目の”input,expected”はテスト関数に渡される2つの変数名(input と expected)です。1つ目のタプル (1, 2) は、テスト関数にinput=1とexpected=2として、2つ目のタプル (3, 4) はinput=3とexpected=4として、3つ目のタプル (5, 6) は、input=5とexpected=6として渡されます。
テスト結果の出力ですが、input = 1の場合、input + 1 が2なので、expected = 2との比較は成功します。以下同様に2つのケースが成功として出力されます。
import pytest
@pytest.mark.parametrize("input,expected", [(1, 2), (3, 4), (5, 6)])
def test_increment(input, expected):
assert input + 1 == expected
# 出力結果
test_increment[1-2] PASSED
test_increment[3-4] PASSED
test_increment[5-6] PASSED
@pytest.mark.skip()
このマーカーは指定されたテストをスキップします。reasonに文言を指定することでテスト出力時にスキップに関するメモを表示させることができます。
import pytest
@pytest.mark.skip(reason="This test is skipped")
def test_will_be_skipped():
assert 1 == 1
# 出力結果
SKIPPED [reason: This test is skipped]
@pytest.mark.skipif()
このマーカーは特定の条件が満たされた場合にテストをスキップします。@pytest.mark.skip()にスキップする条件が加わったもので、サンプルコードでは実行環境の情報を持つsys.platformがwin32の場合、テストをスキップします。
import pytest
import sys
@pytest.mark.skipif(sys.platform == "win32", reason="Skipping on Windows")
def test_skip_on_windows():
assert 1 == 1
# 出力結果
SKIPPED [reason: Skipping on Windows]
@pytest.mark.xfail()
このマーカーはテストが失敗することが予期される場合に使用します。失敗しても警告として扱われ、テスト全体の失敗にはなりません。利用シーンとしては既知のバグが存在する、未実装の機能に対してテストを実装するなど一時的にテストの失敗を成功にしておきたい場合に利用されます。
import pytest
@pytest.mark.xfail(reason="This test is expected to fail")
def test_will_fail():
assert 1 == 2
# 出力結果
XPASS
カスタムマーカー
pytestではカスタムマーカーを定義して特定のテストをグループ化したりフィルタリングしたりできます。下記の例ではslow
というカスタムマーカーを使用して時間がかかるテストをマークしています。
@pytest.mark.slow
の slow
という文字列は任意です。カスタムマーカーは任意の名前で定義でき、特定の目的に応じて自由にカスタマイズできます。例えばデータベース用テストマーカーとして@pytest.mark.database
を登録したりネットワーク用テストマーカーとして @pytest.mark.network
を登録するなど用途に合わせた名前にすることもできます。
ただしカスタムマーカーを使用する際には、pytest.ini
ファイルでそのマーカーを登録しておく必要があります。これにより、テストを実行するときに警告を避けることができます。なおpytest.ini
はプロジェクトのルートディレクトリ直下に配置します。
import pytest
@pytest.mark.slow
def test_slow_function():
import time
time.sleep(2)
assert True
# 出力結果
test_slow_function PASSED
# pytest.ini
[pytest]
markers =
slow: marks tests as slow (deselect with '-m "not slow"')
まとめ
本記事ではpytest.markついて解説しました。効率よくテストを実装するためにもpytest.markを利用して指定したテストのみを実行できるスキルは重要です。ぜひ本記事を参考に開発の現場でも積極的に使用して狙ったテストの実行ができるようにトレーニングしてみてください!