DevGang
Авторизоваться

Параметризация 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.
#Python
Комментарии
Чтобы оставить комментарий, необходимо авторизоваться

Присоединяйся в тусовку

В этом месте могла бы быть ваша реклама

Разместить рекламу