フィクスチャとテスト関数をパラメータ化する方法

pytest は複数のレベルでテストのパラメータ化を可能にします:

  • @pytest.mark.parametrize を使用すると、テスト関数やクラスで複数の引数セットやフィクスチャを定義できます。

  • pytest_generate_tests を使用すると、カスタムパラメータ化スキームや拡張を定義できます。

@pytest.mark.parametrize:テスト関数のパラメータ化

組み込みの pytest.mark.parametrize デコレータを使用すると、テスト関数の引数をパラメータ化できます。 以下は、特定の入力が期待される出力をもたらすことを確認するテスト関数の典型的な例です:

# content of test_expectation.py
import pytest


@pytest.mark.parametrize("test_input,expected", [("3+5", 8), ("2+4", 6), ("6*9", 42)])
def test_eval(test_input, expected):
    assert eval(test_input) == expected

ここでは、@parametrize デコレータが3つの異なる (test_input,expected) タプルを定義しており、test_eval 関数はそれらを順に使用して3回実行されます:

$ pytest
=========================== test session starts ============================
platform linux -- Python 3.x.y, pytest-8.x.y, pluggy-1.x.y
rootdir: /home/sweet/project
collected 3 items

test_expectation.py ..F                                              [100%]

================================= FAILURES =================================
____________________________ test_eval[6*9-42] _____________________________

test_input = '6*9', expected = 42

    @pytest.mark.parametrize("test_input,expected", [("3+5", 8), ("2+4", 6), ("6*9", 42)])
    def test_eval(test_input, expected):
>       assert eval(test_input) == expected
E       AssertionError: assert 54 == 42
E        +  where 54 = eval('6*9')

test_expectation.py:6: AssertionError
========================= short test summary info ==========================
FAILED test_expectation.py::test_eval[6*9-42] - AssertionError: assert 54...
======================= 1 failed, 2 passed in 0.12s ========================

注釈

パラメータ値はそのままテストに渡されます (コピーは一切行われません) 。

例えば、リストや辞書をパラメータ値として渡し、テストケースコードがそれを変更した場合、その変更は後続のテストケース呼び出しに反映されます。

注釈

pytest はデフォルトでパラメータ化に使用されるユニコード文字列の非ASCII文字をエスケープしますが、これはいくつかの欠点があります。 ただし、パラメータ化でユニコード文字列を使用し、ターミナルでそのまま (エスケープされずに) 表示したい場合は、pytest.ini にこのオプションを使用してください:

[pytest]
disable_test_id_escaping_and_forfeit_all_rights_to_community_support = True

ただし、これにより使用するOSや現在インストールされているプラグインによっては望ましくない副作用やバグが発生する可能性があるため、自己責任で使用してください。

この例で設計されているように、入力/出力値のペアのうち1つだけが単純なテスト関数に失敗します。 そして、通常のテスト関数の引数と同様に、トレースバックで inputoutput の値を確認できます。

クラスやモジュールにパラメータ化マーカーを使用することもでき (テスト関数に属性をマークする方法 を参照) 、引数セットで複数の関数を呼び出すことができます。 例えば:

import pytest


@pytest.mark.parametrize("n,expected", [(1, 2), (3, 4)])
class TestClass:
    def test_simple_case(self, n, expected):
        assert n + 1 == expected

    def test_weird_simple_case(self, n, expected):
        assert (n * 1) + 1 == expected

モジュール内のすべてのテストをパラメータ化するには、pytestmark グローバル変数に割り当てることができます:

import pytest

pytestmark = pytest.mark.parametrize("n,expected", [(1, 2), (3, 4)])


class TestClass:
    def test_simple_case(self, n, expected):
        assert n + 1 == expected

    def test_weird_simple_case(self, n, expected):
        assert (n * 1) + 1 == expected

また、パラメータ化内で個々のテストインスタンスにマークを付けることも可能です。 例えば、組み込みの mark.xfail を使用します:

# content of test_expectation.py
import pytest


@pytest.mark.parametrize(
    "test_input,expected",
    [("3+5", 8), ("2+4", 6), pytest.param("6*9", 42, marks=pytest.mark.xfail)],
)
def test_eval(test_input, expected):
    assert eval(test_input) == expected

これを実行してみましょう:

$ pytest
=========================== test session starts ============================
platform linux -- Python 3.x.y, pytest-8.x.y, pluggy-1.x.y
rootdir: /home/sweet/project
collected 3 items

test_expectation.py ..x                                              [100%]

======================= 2 passed, 1 xfailed in 0.12s =======================

以前に失敗を引き起こしたパラメータセットが、現在は「xfailed」 (失敗することが予想される) テストとして表示されます。

parametrize に提供された値が空のリストになる場合 - 例えば、何らかの関数によって動的に生成される場合 - pytest の動作は empty_parameter_set_mark オプションによって定義されます。

複数のパラメータ化された引数のすべての組み合わせを取得するには、parametrize デコレータを積み重ねることができます:

import pytest


@pytest.mark.parametrize("x", [0, 1])
@pytest.mark.parametrize("y", [2, 3])
def test_foo(x, y):
    pass

これにより、引数が x=0/y=2, x=1/y=2, x=0/y=3, x=1/y=3 に設定されたテストが、デコレータの順序でパラメータを使い果たして実行されます。

基本的な pytest_generate_tests の例

場合によっては、独自のパラメータ化スキームを実装したり、フィクスチャのパラメータやスコープを決定するための動的な要素を実装したりすることがあるかもしれません。 そのためには、テスト関数を収集する際に呼び出される pytest_generate_tests フックを使用できます。 渡された metafunc オブジェクトを通じて、要求されたテストコンテキストを検査し、最も重要なこととして、metafunc.parametrize() を呼び出してパラメータ化を引き起こすことができます。

例えば、新しい pytest コマンドラインオプションを介して設定したい文字列入力を受け取るテストを実行したいとしましょう。 まず、stringinput フィクスチャ関数引数を受け入れる簡単なテストを書いてみましょう:

# content of test_strings.py


def test_valid_string(stringinput):
    assert stringinput.isalpha()

次に、コマンドラインオプションの追加とテスト関数のパラメータ化を含む conftest.py ファイルを追加します:

# content of conftest.py


def pytest_addoption(parser):
    parser.addoption(
        "--stringinput",
        action="append",
        default=[],
        help="list of stringinputs to pass to test functions",
    )


def pytest_generate_tests(metafunc):
    if "stringinput" in metafunc.fixturenames:
        metafunc.parametrize("stringinput", metafunc.config.getoption("stringinput"))

今、2つの stringinput 値を渡すと、テストは2回実行されます:

$ pytest -q --stringinput="hello" --stringinput="world" test_strings.py
..                                                                   [100%]
2 passed in 0.12s

失敗するテストにつながる stringinput で実行してみましょう:

$ pytest -q --stringinput="!" test_strings.py
F                                                                    [100%]
================================= FAILURES =================================
___________________________ test_valid_string[!] ___________________________

stringinput = '!'

    def test_valid_string(stringinput):
>       assert stringinput.isalpha()
E       AssertionError: assert False
E        +  where False = <built-in method isalpha of str object at 0xdeadbeef0001>()
E        +    where <built-in method isalpha of str object at 0xdeadbeef0001> = '!'.isalpha

test_strings.py:4: AssertionError
========================= short test summary info ==========================
FAILED test_strings.py::test_valid_string[!] - AssertionError: assert False
1 failed in 0.12s

予想通り、テスト関数は失敗します。

stringinput を指定しない場合、metafunc.parametrize() が空のパラメータリストで呼び出されるため、スキップされます:

$ pytest -q -rs test_strings.py
s                                                                    [100%]
========================= short test summary info ==========================
SKIPPED [1] test_strings.py: got empty parameter set ['stringinput'], function test_valid_string at /home/sweet/project/test_strings.py:2
1 skipped in 0.12s

異なるパラメータセットで metafunc.parametrize を複数回呼び出す場合、それらのセット全体でパラメータ名を重複させることはできません。 そうしないとエラーが発生します。

さらに例を示します

さらに例を見たい場合は、パラメータ化の例 を参照してください。