Spec-Zone.ru › pytest

Как параметризовать фикстуры и тестовые функции

pytest позволяет параметризовать тесты на нескольких уровнях:

  • pytest.fixture() позволяет параметризовать функции-фикстуры.
  • @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 задаёт три разных кортежа (test_input,expected), поэтому функция test_eval выполнится три раза, каждый раз используя следующий из них:

$ pytest
=========================== test session starts ============================
platform linux -- Python 3.x.y, pytest-9.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-символы в строках Unicode при параметризации, поскольку это имеет ряд недостатков. Если вы всё же хотите использовать при параметризации строки Unicode и видеть их в терминале без экранирования, задайте эту опцию в файле конфигурации:

toml

[pytest]
disable_test_id_escaping_and_forfeit_all_rights_to_community_support = true

ini

[pytest]
disable_test_id_escaping_and_forfeit_all_rights_to_community_support = true

Однако имейте в виду, что это может привести к нежелательным побочным эффектам и даже ошибкам в зависимости от используемой ОС и установленных плагинов, поэтому используйте эту настройку на свой страх и риск.

Как и задумано в этом примере, простой тест завершится ошибкой только для одной пары входных и выходных значений. Как и обычно для аргументов тестовой функции, в трассировке можно увидеть значения input и output.

Обратите внимание: маркер parametrize можно также использовать для класса или модуля (см. раздел Как помечать тестовые функции атрибутами), чтобы вызывать несколько функций с наборами аргументов, например:

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-9.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"))

Примечание

Хук pytest_generate_tests можно реализовать непосредственно в тестовом модуле или внутри тестового класса; в отличие от других хуков, pytest обнаружит его и там. Остальные хуки должны находиться в conftest.py или плагине. См. раздел Написание функций-хуков.

Если теперь передать два значения stringinput, тест запустится дважды:

$ 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 for (stringinput)
1 skipped in 0.12s

Обратите внимание: при многократном вызове metafunc.parametrize с разными наборами параметров имена параметров во всех этих наборах не должны повторяться, иначе будет вызвана ошибка.

Другие примеры

Дополнительные примеры см. в разделе другие примеры параметризации.

© 2015–2026 Holger Krekel and pytest-dev team
Licensed under the MIT License.
https://docs.pytest.org/en/stable/how-to/parametrize.html

Spec-Zone.ru

Настройки Оффлайн Что нового Помощь О нас
Spec-Zone .ru
спецификации, руководства, описания, API