【pytest】独自markで共通処理を作成する

pytestでユニットテストを書く際に、独自markについて調べたのでメモ

やりたかったこと

各テスト関数の実行前にパラメータ付きの初期化処理を実行したい。
他の共通処理はfixtureを使って、実装していたが今回はパラメータ付きであったためfixtureは利用できなかった。

標準のmarkであるparametrizeは、引数を指定できるので独自markを作れば実現できると判断し調査した。

@pytest.mark.usefixtures('my_fixture') # fixtureだと引数を指定できない
def test_target_1():
    sample.target()

@pytest.mark.parametrize('param1, param2', [ # parameterizeは引数を指定してる
    (1, 2),
    (2, 3),
])
def test_target_2(my_fixture, param1, param2):
    sample.target()

使い方

テスト関数にmarkを付与する

対象のテスト関数に独自markの使用を記述する(今回は「my_init」が独自mark)

@pytest.mark.my_init(param1="テスト", param2=2) # 初期化で使用する引数を設定
def test_target_3():
    sample.target()

markに対する処理の実装

pytestのドキュメントに記載があるやり方は、hooksを利用してテスト実行時にmarkが付与されていれば独自処理を実行する方法。
Working with custom markers

conftest.py

def pytest_runtest_setup(item):
    for marker in item.iter_markers(name="my_init"):
        # 独自markがついているとき初期化処理を呼び出す
        # marker.args, marker.kwargsで引数を参照可能
        _my_init(**marker.kwargs)

def _my_init(param1: str = None, param2: int = None):
    # テスト初期化処理などを記述する
    pass

ただ、hooksの関数内ではfixtureを参照できなかった。

def pytest_runtest_setup(
        my_fixture, # fixtureの参照はできない
        item
): 
    for marker in item.iter_markers(name="my_init"):
        _my_init(**marker.kwargs)

今回は初期化処理内でfixtureを利用したかったため、公式の方法だと不足。
調べているとfixtureからでもmarkが付与されているか取得できるらしいので、そっちの方法を採用。

conftest.py

@pytest.fixture(
    scope="function",  # 関数単位の実行
    autouse=True   # 自動でfixtureを実行する
)
def my_init_fixture(request):
    for marker in request.node.own_markers:
        if marker.name=="my_init":
            # fixtureを参照する
            my_fixture = request.getfixturevalue("my_fixture")

            # 独自markのとき
            _my_init(my_fixture, **marker.kwargs)

def _my_init(my_fixture, param1: str = None, param2: int = None):
    # テスト初期化処理などを記述する
    pass

独自markの登録

このままだと、テスト実行時に未確認のmarkであると警告が出てしまう。

PytestUnknownMarkWarning: Unknown pytest.mark.my_init - is this a typo?  You can register custom marks to avoid this warning - for details, see https://docs.pytest.org/en/stable/mark.html
    @pytest.mark.my_init()

設定ファイルかhooksで、markを登録する。

設定ファイルで登録する場合

pytest.ini

[pytest]
markers =
my_init(param1=None, param2=None): markの説明を書く
hooksで登録する場合

conftest.py

def pytest_configure(config):
    config.addinivalue_line(
        "markers", "my_init(param1=None, param2=None): markの説明を書く"
    )