@mark.parametrize()
;@mark.parametrize()
.Встроенный декоратор @pytest.mark.parametrize()
позволяет передавать значения аргументам для тестовой функции.
Пример тестовой функции, которая проверяет передаваемые выражения на ожидаемые результаты:
# 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
Чтобы функция test_eval()
запускалась три раза, декоратор @pytest.mark.parametrize()
определяет три разных кортежа (test_input, expected)
, используя их по очереди.
Примечание. Значения параметров передаются в тесты как есть (без копирования). Например, если в качестве значения аргумента передается список или словарь, а код тестового примера изменяет его, то изменения будут отражены в последующих вызовах тестового примера.
Примечание. Фреймворк
pytest
по умолчанию для параметризации экранирует любые символы, отличные от ascii, используемые в строках Unicode, потому что у него есть несколько недостатков. Если в параметрах нужно использовать строки Unicode и видеть их в терминале как есть (без экранирования), то используйте эту опцию в файлеpytest.ini
:[pytest] disable_test_id_escaping_and_forfeit_all_rights_to_community_support = TrueИмейте в виду, такое поведение используется на свой страх и риск, так как это может вызвать нежелательные побочные эффекты и даже ошибки в зависимости от используемой ОС и установленных в настоящее время плагинов.
Как и было задумано в примере выше, только одна пара входных/выходных значений не проходит тестирование. В итогом отчете трассировки pytest
, наряду с аргументами тестовой функции, также можно увидеть входные и выходные значения.
Обратите внимание, что декоратор @pytest.mark.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` 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
Также, при передаче параметров тесту, можно отметить отдельные тестовые экземпляры, например, с помощью функций @pytest.mark.xfail()
или @pytest.mark.skip()
или pytest.mark.skipif()
:
# test_expectation.py import pytest @pytest.mark.parametrize( "test_input,expected", [("3+5", 8), ("2+4", 6), # параметр `("6*9", 42)` отмечается как дающий сбой pytest.param("6*9", 42, marks=pytest.mark.xfail)], ) def test_eval(test_input, expected): assert eval(test_input) == expected
Один набор параметров, который ранее вызывал сбой, теперь отображается как тест "xfailed" (ожидается сбой).
В случае, если значения, предоставленные для параметризации, приводят к пустому списку, например, если они динамически генерируются какой-либо функцией, то поведение pytest
определяется параметром empty_parameter_set_mark
.
Параметр empty_parameter_set_mark
определяется в файле pytest.ini
и позволяет выбрать действие для пустых наборов параметров.
skip
: пропускает тесты с пустым набором параметров (по умолчанию);xfail
: помечает тесты с пустым набором параметров как xfail(run=False)
;fail_at_collect
: с пустым набором параметров вызывает исключение.Пример установки empty_parameter_set_mark
:
# pytest.ini [pytest] empty_parameter_set_mark = xfail
Чтобы получить все комбинации для нескольких значений аргументов тестовой функции, можно сложить декораторы @pytest.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.mark.parametrize
аргумента indirect
со значением True
позволяет передать параметры сначала вызываемой фикстуре, прежде чем они попадут в тест.
Обратите внимание, что название фикстуры, аргумент теста и передаваемый декоратором параметр имеют одинаковые имена.
import pytest @pytest.fixture def fixt(request): return request.param * 3 @pytest.mark.parametrize("fixt", ["a", "b"], indirect=True) def test_indirect(fixt): assert len(fixt) == 3
Здесь параметр fixt
сначала передается в фикстуру fixt()
. В фикстуре значение fixt
извлекается при помощи объекта request.param
и умножается на 3 (сначала 'a'*3
, потом 'b'*3
). Далее, по очереди, возвращаемое значение попадает в тестовую функцию test_indirect()
.
Такое поведение можно использовать, например, для выполнения более дорогостоящей настройки во время выполнения теста в фикстуре, а не выполнять эти шаги настройки во время сбора данных.
Также аргумент indirect
декоратора @pytest.mark.parametrize
дает возможность применить косвенную передачу к определенным аргументам (выборочно). Это можно сделать, если передать аргументу indirect
список или кортеж имен параметров.
В приведенном ниже примере есть тестовая функция test_indirect()
, которая принимает два аргумента: x
и y
. Также в коде определены две фикстуры со схожими именами. Аргументу indirect
декоратора @pytest.mark.parametrize
передается список, содержащий имя фикстуры x
. В следствии чего косвенная передача будет применена только к параметру x
, и значение a
будет передано соответствующей функции фикстуре:
# test_indirect_list.py import pytest @pytest.fixture(scope="function") def x(request): return request.param * 3 @pytest.fixture(scope="function") def y(request): return request.param * 2 @pytest.mark.parametrize("x, y", [("a", "b")], indirect=["x"]) def test_indirect(x, y): assert x == "aaa" assert y == "b"
Чтобы применить метки или установить идентификаторы id
для отдельного набора параметров теста, используем функцию pytest.param()
.
Например:
# test_param_example.py import pytest @pytest.mark.parametrize( "test_input,expected", [ ("3+5", 8), pytest.param("1+7", 8, marks=pytest.mark.basic), pytest.param("2+4", 6, marks=pytest.mark.basic, id="basic_2+4"), pytest.param( "6*9", 42, marks=[pytest.mark.basic, pytest.mark.xfail], id="basic_6*9" ), ], ) def test_eval(test_input, expected): assert eval(test_input) == expected
В этом примере есть 4 параметризованных теста. За исключением первого теста, остальные отмечаются пользовательским маркером basic
, к тому-же в последнем тесте используется встроенная метка xfail
(как ожидаемо провальный). Для наглядности, для некоторых тестов, устанавливаем идентификаторы id
.
Запускаем pytest
в подробном режиме и только с маркером basic
командой: $ pytest -v -m basic
.
В результате:
basic
.basic
.test_eval[1+7-8]
пройден, но имя сбивает с толку.test_eval[basic_2+4]
пройден.test_eval[basic_6*9]
был помечен как xfail
и ожидаемо завершится ошибкой.