Параметризация Fixtures в Python
Настройка фикстур с аргументами является распространенным вариантом использования. Однако, это мало документировано и редко сообщается, что это возможно.
Рассмотрим два способа параметризации фикстур в Pytest (фабрики фикстур и неявная параметризация) и закончу обсуждением того, когда какой из них использовать.
Резюме Fixture
Fixtures — это вспомогательные функции для сокращения повторяющихся блоков кода. Яркими примерами являются группа модульных тестов, где мы выполняем более или менее сложную инициализацию в каждом тесте, например, создаем класс.
Для этой статьи рекомендуется понимание модульного тестирования в целом и pytest
в частности: один из возможных вариантов чтения — это мой предыдущий пост, в котором также представлены фикстуры и параметризация, на которых мы основываемся здесь.
class MySummationClass:
def sum(self, x, y):
return x + y
def test_sum():
my_summation_class = MySummationClass()
assert my_summation_class.sum(2, 3) == 5
def test_sum_neg():
my_summation_class = MySummationClass()
assert my_summation_class.sum(-2, -3) == -5
Вместо того, чтобы помещать этот общий код в каждый модульный тест, мы можем инкапсулировать его в приспособлении и включать в каждый тест:
@pytest.fixture
def my_summation_class():
return MySummationClass()
def test_sum(my_summation_class):
my_summation_class = MySummationClass()
assert my_summation_class.sum(2, 3) == 5
def test_sum_neg(my_summation_class):
my_summation_class = MySummationClass()
assert my_summation_class.sum(-2, -3) == -5
Параметризация Fixture
Давайте посмотрим на следующий мотивирующий пример:
import math
from dataclasses import dataclass
from typing import Callable
import pytest
@dataclass
class Circle:
radius: float
def get_area(self) -> float:
return 2 * math.pi * self.radius
@pytest.fixture
def new_circle() -> Circle:
return Circle(5)
def test_get_area_v1(new_circle: Circle) -> None:
assert new_circle.get_area() == pytest.approx(31.4159265359)
Мы определяем класс данных, определяющий circle
: он описывается через radius
и предлагает функцию для вычисления площади круга (circle’s area).
Затем мы определяем фикстуру, создавая и возвращая новый экземпляр Circle
, и, в конце концов, тестируем функцию get_area
, используя эту фикстуру для предоставления образца объекта.
Что теперь, если бы мы захотели параметризовать эту fixture, т.е. иметь какой-то способ определить fixture, которая принимает radius
в качестве аргумента и возвращает соответствующую circle
? Это было бы удобно и легко выполнимо. Поэтому мы подробно опишем это здесь.
Методы:
Fixture Factory
Fixture factory можно использовать всякий раз, когда мы хотим создать несколько объектов в любом месте внутри функций.
Мы используем обычное приспособление Pytest, но вместо того, чтобы возвращать готовый объект, мы возвращаем объект-функцию, принимая radius
в качестве аргумента и создавая соответствующий circle
. Также обратите внимание на правильную аннотацию mypy:
@pytest.fixture
def new_circle_factory() -> Callable[[float], Circle]:
def _create(radius: float) -> Circle:
return Circle(radius)
return _create
def test_get_area_v2(new_circle_factory: Callable[[float], Circle]) -> None:
assert new_circle_factory(5).get_area() == pytest.approx(31.4159265359)
assert new_circle_factory(15).get_area() == pytest.approx(94.2477796077)
Неявная параметризация
Другой метод возникает из-за того факта, что приспособления уже поддерживают параметризацию неявно. Это может быть использовано, когда мы хотим объединить параметризованные приспособления с другими типами параметризации.
Для этого мы добавляем некоторые аргументы в функцию фикстуры, а затем определяем эти аргументы в нашем «обычном» декораторе параметризации, но затем не включаем их в сигнатуру тестовой функции. Таким образом, pytest
неявно передаст этот аргумент фикстуре:
@pytest.fixture
def new_circle_parametrized(radius: float) -> Circle:
return Circle(radius)
@pytest.mark.parametrize(
"radius, expected_area", [(5, 31.4159265359), (15, 94.2477796077)]
)
def test_get_area_v3(new_circle_parametrized: Circle, expected_area: float) -> None:
assert new_circle_parametrized.get_area() == pytest.approx(expected_area)
Сравнение
Мы завершаем эту статью кратким обсуждением того, когда следует использовать оба метода: ни один из них не является лучшим или худшим, оба имеют свои оправданные варианты использования и сценарии применения. Вместо этого, как уже упоминалось в их соответствующем введении, это в основном зависит от того, где и как мы хотим использовать fixture. Вот несколько способов их использования:
- Когда мы хотим гибко использовать фикстуру для определения объектов, возможно, по запросу и во многих разных местах внутри одной или нескольких тестовых функций — нам нужен метод 1.
- Если нам нужна фикстура «вне области действия обычной программы», например, внутри еще одного параметризованного декоратора — мы должны прибегнуть к методу 2.