Сообщить об ошибке.

Способы передачи значений параметров тестам @pytest.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] пройден.
  • Tест test_eval[basic_6*9] был помечен как xfail и ожидаемо завершится ошибкой.