カスタムマーカーの使用

テスト関数に属性をマークする方法 メカニズムを使用したいくつかの例を示します。

テスト関数にマークを付けて実行するために選択する

このようにカスタムメタデータでテスト関数に「マーク」を付けることができます:

# content of test_server.py

import pytest


@pytest.mark.webtest
def test_send_http():
    pass  # perform some webtest test for your app


@pytest.mark.device(serial="123")
def test_something_quick():
    pass


@pytest.mark.device(serial="abc")
def test_another():
    pass


class TestClass:
    def test_method(self):
        pass

次に、webtest とマークされたテストのみを実行するようにテスト実行を制限できます:

$ pytest -v -m webtest
=========================== test session starts ============================
platform linux -- Python 3.x.y, pytest-8.x.y, pluggy-1.x.y -- $PYTHON_PREFIX/bin/python
cachedir: .pytest_cache
rootdir: /home/sweet/project
collecting ... collected 4 items / 3 deselected / 1 selected

test_server.py::test_send_http PASSED                                [100%]

===================== 1 passed, 3 deselected in 0.12s ======================

または逆に、webtest 以外のすべてのテストを実行します:

$ pytest -v -m "not webtest"
=========================== test session starts ============================
platform linux -- Python 3.x.y, pytest-8.x.y, pluggy-1.x.y -- $PYTHON_PREFIX/bin/python
cachedir: .pytest_cache
rootdir: /home/sweet/project
collecting ... collected 4 items / 1 deselected / 3 selected

test_server.py::test_something_quick PASSED                          [ 33%]
test_server.py::test_another PASSED                                  [ 66%]
test_server.py::TestClass::test_method PASSED                        [100%]

===================== 3 passed, 1 deselected in 0.12s ======================

さらに、1つまたは複数のマーカーキーワード引数に一致するテストのみを実行するようにテスト実行を制限できます。 例えば、device と特定の serial="123" とマークされたテストのみを実行します:

$ pytest -v -m "device(serial='123')"
=========================== test session starts ============================
platform linux -- Python 3.x.y, pytest-8.x.y, pluggy-1.x.y -- $PYTHON_PREFIX/bin/python
cachedir: .pytest_cache
rootdir: /home/sweet/project
collecting ... collected 4 items / 3 deselected / 1 selected

test_server.py::test_something_quick PASSED                          [100%]

===================== 1 passed, 3 deselected in 0.12s ======================

注釈

マーカー式ではキーワード引数の一致のみがサポートされています。

注釈

マーカー式では、int、(エスケープされていない) strbool、および None の値のみがサポートされています。

ノードIDに基づいてテストを選択する

1つまたは複数の node IDs を位置引数として指定して、指定されたテストのみを選択できます。 これにより、モジュール、クラス、メソッド、または関数名に基づいてテストを簡単に選択できます:

$ pytest -v test_server.py::TestClass::test_method
=========================== test session starts ============================
platform linux -- Python 3.x.y, pytest-8.x.y, pluggy-1.x.y -- $PYTHON_PREFIX/bin/python
cachedir: .pytest_cache
rootdir: /home/sweet/project
collecting ... collected 1 item

test_server.py::TestClass::test_method PASSED                        [100%]

============================ 1 passed in 0.12s =============================

クラスで選択することもできます:

$ pytest -v test_server.py::TestClass
=========================== test session starts ============================
platform linux -- Python 3.x.y, pytest-8.x.y, pluggy-1.x.y -- $PYTHON_PREFIX/bin/python
cachedir: .pytest_cache
rootdir: /home/sweet/project
collecting ... collected 1 item

test_server.py::TestClass::test_method PASSED                        [100%]

============================ 1 passed in 0.12s =============================

または複数のノードを選択します:

$ pytest -v test_server.py::TestClass test_server.py::test_send_http
=========================== test session starts ============================
platform linux -- Python 3.x.y, pytest-8.x.y, pluggy-1.x.y -- $PYTHON_PREFIX/bin/python
cachedir: .pytest_cache
rootdir: /home/sweet/project
collecting ... collected 2 items

test_server.py::TestClass::test_method PASSED                        [ 50%]
test_server.py::test_send_http PASSED                                [100%]

============================ 2 passed in 0.12s =============================

注釈

ノードIDは module.py::class::method または module.py::function の形式です。 ノードIDは収集されるテストを制御するため、module.py::class はクラスのすべてのテストメソッドを選択します。 ノードはパラメータ化されたフィクスチャまたはテストの各パラメータに対しても作成されるため、パラメータ化されたテストを選択するにはパラメータ値を含める必要があります。 例えば、module.py::function[param]

失敗したテストのノードIDは、-rf オプションを使用してpytestを実行したときにテストサマリー情報に表示されます。 また、pytest --collect-only の出力からノードIDを構築することもできます。

名前に基づいてテストを選択するために -k expr を使用する

Added in version 2.0/2.3.4.

-k コマンドラインオプションを使用して、マーカーの正確な一致を提供する -m の代わりに、テスト名の部分文字列一致を実装する式を指定できます。 これにより、名前に基づいてテストを簡単に選択できます:

バージョン 5.4 で変更.

式の一致は大文字と小文字を区別しなくなりました。

$ pytest -v -k http  # running with the above defined example module
=========================== test session starts ============================
platform linux -- Python 3.x.y, pytest-8.x.y, pluggy-1.x.y -- $PYTHON_PREFIX/bin/python
cachedir: .pytest_cache
rootdir: /home/sweet/project
collecting ... collected 4 items / 3 deselected / 1 selected

test_server.py::test_send_http PASSED                                [100%]

===================== 1 passed, 3 deselected in 0.12s ======================

また、キーワードに一致するもの以外のすべてのテストを実行することもできます:

$ pytest -k "not send_http" -v
=========================== test session starts ============================
platform linux -- Python 3.x.y, pytest-8.x.y, pluggy-1.x.y -- $PYTHON_PREFIX/bin/python
cachedir: .pytest_cache
rootdir: /home/sweet/project
collecting ... collected 4 items / 1 deselected / 3 selected

test_server.py::test_something_quick PASSED                          [ 33%]
test_server.py::test_another PASSED                                  [ 66%]
test_server.py::TestClass::test_method PASSED                        [100%]

===================== 3 passed, 1 deselected in 0.12s ======================

または、"http" と "quick" テストを選択します:

$ pytest -k "http or quick" -v
=========================== test session starts ============================
platform linux -- Python 3.x.y, pytest-8.x.y, pluggy-1.x.y -- $PYTHON_PREFIX/bin/python
cachedir: .pytest_cache
rootdir: /home/sweet/project
collecting ... collected 4 items / 2 deselected / 2 selected

test_server.py::test_send_http PASSED                                [ 50%]
test_server.py::test_something_quick PASSED                          [100%]

===================== 2 passed, 2 deselected in 0.12s ======================

andornot および括弧を使用できます。

テストの名前に加えて、-k はテストの親の名前 (通常はファイルとクラスの名前) 、テスト関数に設定された属性、それに適用されたマーカーまたはその親、およびその親に明示的に追加された任意の extra keywords にも一致します。

マーカーの登録

テストスイートのマーカーを登録するのは簡単です:

# content of pytest.ini
[pytest]
markers =
    webtest: mark a test as a webtest.
    slow: mark test as slow.

上記の例に示すように、各カスタムマーカーをそれぞれの行に定義することで、複数のカスタムマーカーを登録できます。

テストスイートに存在するマーカーを尋ねることができます - リストには、定義したばかりの webtest および slow マーカーが含まれます:

$ pytest --markers
@pytest.mark.webtest: mark a test as a webtest.

@pytest.mark.slow: mark test as slow.

@pytest.mark.filterwarnings(warning): add a warning filter to the given test. see https://docs.pytest.org/en/stable/how-to/capture-warnings.html#pytest-mark-filterwarnings

@pytest.mark.skip(reason=None): skip the given test function with an optional reason. Example: skip(reason="no way of currently testing this") skips the test.

@pytest.mark.skipif(condition, ..., *, reason=...): skip the given test function if any of the conditions evaluate to True. Example: skipif(sys.platform == 'win32') skips the test if we are on the win32 platform. See https://docs.pytest.org/en/stable/reference/reference.html#pytest-mark-skipif

@pytest.mark.xfail(condition, ..., *, reason=..., run=True, raises=None, strict=xfail_strict): mark the test function as an expected failure if any of the conditions evaluate to True. Optionally specify a reason for better reporting and run=False if you don't even want to execute the test function. If only specific exception(s) are expected, you can list them in raises, and if the test fails in other ways, it will be reported as a true failure. See https://docs.pytest.org/en/stable/reference/reference.html#pytest-mark-xfail

@pytest.mark.parametrize(argnames, argvalues): call a test function multiple times passing in different arguments in turn. argvalues generally needs to be a list of values if argnames specifies only one name or a list of tuples of values if argnames specifies multiple names. Example: @parametrize('arg1', [1,2]) would lead to two calls of the decorated test function, one with arg1=1 and another with arg1=2.see https://docs.pytest.org/en/stable/how-to/parametrize.html for more info and examples.

@pytest.mark.usefixtures(fixturename1, fixturename2, ...): mark tests as needing all of the specified fixtures. see https://docs.pytest.org/en/stable/explanation/fixtures.html#usefixtures

@pytest.mark.tryfirst: mark a hook implementation function such that the plugin machinery will try to call it first/as early as possible. DEPRECATED, use @pytest.hookimpl(tryfirst=True) instead.

@pytest.mark.trylast: mark a hook implementation function such that the plugin machinery will try to call it last/as late as possible. DEPRECATED, use @pytest.hookimpl(trylast=True) instead.

プラグインからマーカーを追加して操作する方法の例については、カスタムマーカーとコマンドラインオプションでテスト実行を制御する を参照してください。

注釈

マーカーを明示的に登録することをお勧めします:

  • テストスイートにマーカーを定義する場所が1つあります

  • pytest --markers を介して既存のマーカーを尋ねると、良い出力が得られます

  • --strict-markers オプションを使用すると、関数マーカーのタイプミスがエラーとして扱われます。

クラス全体またはモジュールにマークを付ける

クラスに pytest.mark デコレータを使用して、そのすべてのテストメソッドにマーカーを適用できます:

# content of test_mark_classlevel.py
import pytest


@pytest.mark.webtest
class TestClass:
    def test_startup(self):
        pass

    def test_startup_and_more(self):
        pass

これは、デコレータを2つのテスト関数に直接適用するのと同等です。

モジュールレベルでマークを適用するには、pytestmark グローバル変数を使用します:

import pytest
pytestmark = pytest.mark.webtest

または複数のマーカー:

pytestmark = [pytest.mark.webtest, pytest.mark.slowtest]

クラスデコレータが導入される前のレガシーな理由により、テストクラスに pytestmark 属性を次のように設定することが可能です:

import pytest


class TestClass:
    pytestmark = pytest.mark.webtest

パラメータ化を使用する場合に個々のテストにマークを付ける

パラメータ化を使用する場合、マークを適用すると各個別のテストに適用されます。 ただし、個々のテストインスタンスにマーカーを適用することも可能です:

import pytest


@pytest.mark.foo
@pytest.mark.parametrize(
    ("n", "expected"), [(1, 2), pytest.param(1, 3, marks=pytest.mark.bar), (2, 3)]
)
def test_increment(n, expected):
    assert n + 1 == expected

この例では、マーク "foo" は3つのテストそれぞれに適用されますが、"bar" マークは2番目のテストにのみ適用されます。 スキップおよびxfailマークもこの方法で適用できます。 詳細は parametrize を使用した skip/xfail を参照してください。

カスタムマーカーとコマンドラインオプションでテスト実行を制御する

プラグインはカスタムマーカーを提供し、それに基づいて特定の動作を実装できます。 これは、名前付き環境を介して指定されたテストを実行するためのコマンドラインオプションとパラメータ化されたテスト関数マーカーを追加する自己完結型の例です:

# content of conftest.py

import pytest


def pytest_addoption(parser):
    parser.addoption(
        "-E",
        action="store",
        metavar="NAME",
        help="only run tests matching the environment NAME.",
    )


def pytest_configure(config):
    # register an additional marker
    config.addinivalue_line(
        "markers", "env(name): mark test to run only on named environment"
    )


def pytest_runtest_setup(item):
    envnames = [mark.args[0] for mark in item.iter_markers(name="env")]
    if envnames:
        if item.config.getoption("-E") not in envnames:
            pytest.skip(f"test requires env in {envnames!r}")

このローカルプラグインを使用したテストファイル:

# content of test_someenv.py

import pytest


@pytest.mark.env("stage1")
def test_basic_db_operation():
    pass

テストが必要とする環境とは異なる環境を指定する例:

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

test_someenv.py s                                                    [100%]

============================ 1 skipped in 0.12s ============================

そして、必要な環境を正確に指定する例:

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

test_someenv.py .                                                    [100%]

============================ 1 passed in 0.12s =============================

--markers オプションは常に利用可能なマーカーのリストを提供します:

$ pytest --markers
@pytest.mark.env(name): mark test to run only on named environment

@pytest.mark.filterwarnings(warning): add a warning filter to the given test. see https://docs.pytest.org/en/stable/how-to/capture-warnings.html#pytest-mark-filterwarnings

@pytest.mark.skip(reason=None): skip the given test function with an optional reason. Example: skip(reason="no way of currently testing this") skips the test.

@pytest.mark.skipif(condition, ..., *, reason=...): skip the given test function if any of the conditions evaluate to True. Example: skipif(sys.platform == 'win32') skips the test if we are on the win32 platform. See https://docs.pytest.org/en/stable/reference/reference.html#pytest-mark-skipif

@pytest.mark.xfail(condition, ..., *, reason=..., run=True, raises=None, strict=xfail_strict): mark the test function as an expected failure if any of the conditions evaluate to True. Optionally specify a reason for better reporting and run=False if you don't even want to execute the test function. If only specific exception(s) are expected, you can list them in raises, and if the test fails in other ways, it will be reported as a true failure. See https://docs.pytest.org/en/stable/reference/reference.html#pytest-mark-xfail

@pytest.mark.parametrize(argnames, argvalues): call a test function multiple times passing in different arguments in turn. argvalues generally needs to be a list of values if argnames specifies only one name or a list of tuples of values if argnames specifies multiple names. Example: @parametrize('arg1', [1,2]) would lead to two calls of the decorated test function, one with arg1=1 and another with arg1=2.see https://docs.pytest.org/en/stable/how-to/parametrize.html for more info and examples.

@pytest.mark.usefixtures(fixturename1, fixturename2, ...): mark tests as needing all of the specified fixtures. see https://docs.pytest.org/en/stable/explanation/fixtures.html#usefixtures

@pytest.mark.tryfirst: mark a hook implementation function such that the plugin machinery will try to call it first/as early as possible. DEPRECATED, use @pytest.hookimpl(tryfirst=True) instead.

@pytest.mark.trylast: mark a hook implementation function such that the plugin machinery will try to call it last/as late as possible. DEPRECATED, use @pytest.hookimpl(trylast=True) instead.

カスタムマーカーに呼び出し可能なものを渡す

次の例で使用される設定ファイルは以下の通りです:

# content of conftest.py
import sys


def pytest_runtest_setup(item):
    for marker in item.iter_markers(name="my_marker"):
        print(marker)
        sys.stdout.flush()

カスタムマーカーは、呼び出し可能なものとして呼び出すか、pytest.mark.MARKER_NAME.with_args を使用することによって、引数セット、すなわち args および kwargs プロパティを持つことができます。 これらの2つの方法はほとんどの場合、同じ効果を達成します。

ただし、キーワード引数なしで単一の位置引数として呼び出し可能なものがある場合、pytest.mark.MARKER_NAME(c) を使用すると、c を位置引数として渡すのではなく、カスタムマーカーで c をデコレートします (詳細は MarkDecorator を参照) 。 幸いなことに、pytest.mark.MARKER_NAME.with_args が救済します:

# content of test_custom_marker.py
import pytest


def hello_world(*args, **kwargs):
    return "Hello World"


@pytest.mark.my_marker.with_args(hello_world)
def test_with_args():
    pass

出力は次の通りです:

$ pytest -q -s
Mark(name='my_marker', args=(<function hello_world at 0xdeadbeef0001>,), kwargs={})
.
1 passed in 0.12s

カスタムマーカーが関数 hello_world で引数セットを拡張していることがわかります。 これは、舞台裏で __call__ を呼び出す呼び出し可能なものとしてカスタムマーカーを作成することと、with_args を使用することの主な違いです。

複数の場所から設定されたマーカーを読み取る

テストスイートでマーカーを多用している場合、マーカーがテスト関数に複数回適用される場合があります。 プラグインコードからそのような設定をすべて読み取ることができます。 例:

# content of test_mark_three_times.py
import pytest

pytestmark = pytest.mark.glob("module", x=1)


@pytest.mark.glob("class", x=2)
class TestClass:
    @pytest.mark.glob("function", x=3)
    def test_something(self):
        pass

ここでは、マーカー "glob" が同じテスト関数に3回適用されています。 conftestファイルから次のように読み取ることができます:

# content of conftest.py
import sys


def pytest_runtest_setup(item):
    for mark in item.iter_markers(name="glob"):
        print(f"glob args={mark.args} kwargs={mark.kwargs}")
        sys.stdout.flush()

出力をキャプチャせずにこれを実行して、何が得られるか見てみましょう:

$ pytest -q -s
glob args=('function',) kwargs={'x': 3}
glob args=('class',) kwargs={'x': 2}
glob args=('module',) kwargs={'x': 1}
.
1 passed in 0.12s

pytestでプラットフォーム固有のテストにマークを付ける

特定のプラットフォーム、すなわち pytest.mark.darwinpytest.mark.win32 などのテストをマークするテストスイートがあり、すべてのプラットフォームで実行され、特定のマーカーがないテストもあるとします。 今、特定のプラットフォームのテストのみを実行する方法が必要な場合は、次のプラグインを使用できます:

# content of conftest.py
#
import sys

import pytest

ALL = set("darwin linux win32".split())


def pytest_runtest_setup(item):
    supported_platforms = ALL.intersection(mark.name for mark in item.iter_markers())
    plat = sys.platform
    if supported_platforms and plat not in supported_platforms:
        pytest.skip(f"cannot run on platform {plat}")

次に、異なるプラットフォーム用に指定されたテストはスキップされます。 これがどのように見えるかを示すために、小さなテストファイルを作成しましょう:

# content of test_plat.py

import pytest


@pytest.mark.darwin
def test_if_apple_is_evil():
    pass


@pytest.mark.linux
def test_if_linux_works():
    pass


@pytest.mark.win32
def test_if_win32_crashes():
    pass


def test_runs_everywhere():
    pass

次に、2つのテストがスキップされ、予想通りに2つのテストが実行されるのがわかります:

$ pytest -rs # this option reports skip reasons
=========================== test session starts ============================
platform linux -- Python 3.x.y, pytest-8.x.y, pluggy-1.x.y
rootdir: /home/sweet/project
collected 4 items

test_plat.py s.s.                                                    [100%]

========================= short test summary info ==========================
SKIPPED [2] conftest.py:13: cannot run on platform linux
======================= 2 passed, 2 skipped in 0.12s =======================

次のようにマーカーコマンドラインオプションを介してプラットフォームを指定した場合に注意してください:

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

test_plat.py .                                                       [100%]

===================== 1 passed, 3 deselected in 0.12s ======================

次に、マークされていないテストは実行されません。 したがって、特定のテストに実行を制限する方法です。

テスト名に基づいてマーカーを自動的に追加する

テスト関数名が特定のタイプのテストを示すテストスイートがある場合、マーカーを自動的に定義するフックを実装して、-m オプションを使用できるようにすることができます。 このテストモジュールを見てみましょう:

# content of test_module.py


def test_interface_simple():
    assert 0


def test_interface_complex():
    assert 0


def test_event_simple():
    assert 0


def test_something_else():
    assert 0

2つのマーカーを動的に定義したいので、conftest.py プラグインでそれを行うことができます:

# content of conftest.py

import pytest


def pytest_collection_modifyitems(items):
    for item in items:
        if "interface" in item.nodeid:
            item.add_marker(pytest.mark.interface)
        elif "event" in item.nodeid:
            item.add_marker(pytest.mark.event)

今、-m オプション を使用して1セットを選択できます:

$ pytest -m interface --tb=short
=========================== test session starts ============================
platform linux -- Python 3.x.y, pytest-8.x.y, pluggy-1.x.y
rootdir: /home/sweet/project
collected 4 items / 2 deselected / 2 selected

test_module.py FF                                                    [100%]

================================= FAILURES =================================
__________________________ test_interface_simple ___________________________
test_module.py:4: in test_interface_simple
    assert 0
E   assert 0
__________________________ test_interface_complex __________________________
test_module.py:8: in test_interface_complex
    assert 0
E   assert 0
========================= short test summary info ==========================
FAILED test_module.py::test_interface_simple - assert 0
FAILED test_module.py::test_interface_complex - assert 0
===================== 2 failed, 2 deselected in 0.12s ======================

または、"event" および "interface" テストの両方を選択します:

$ pytest -m "interface or event" --tb=short
=========================== test session starts ============================
platform linux -- Python 3.x.y, pytest-8.x.y, pluggy-1.x.y
rootdir: /home/sweet/project
collected 4 items / 1 deselected / 3 selected

test_module.py FFF                                                   [100%]

================================= FAILURES =================================
__________________________ test_interface_simple ___________________________
test_module.py:4: in test_interface_simple
    assert 0
E   assert 0
__________________________ test_interface_complex __________________________
test_module.py:8: in test_interface_complex
    assert 0
E   assert 0
____________________________ test_event_simple _____________________________
test_module.py:12: in test_event_simple
    assert 0
E   assert 0
========================= short test summary info ==========================
FAILED test_module.py::test_interface_simple - assert 0
FAILED test_module.py::test_interface_complex - assert 0
FAILED test_module.py::test_event_simple - assert 0
===================== 3 failed, 1 deselected in 0.12s ======================