Pytest.fixture: примеры (PYTHON)

pytest.fixture в Python для создания тестовых данных
Раздел: Тестирование, Фикстуры
pytest.fixture(scope, params, autouse, ids, name): function

Основные сведения о pytest.fixture

Декоратор @pytest.fixture используется в тестовом фреймворке pytest для создания фикстур. Фикстуры представляют собой функции, которые подготавливают данные, состояние или ресурсы для тестов и выполняют очистку после их завершения. Их применение позволяет избежать дублирования кода и повышает читаемость тестов.

Фикстуры используются, когда нескольким тестам требуются одинаковые подготовительные действия, такие как создание объектов, подключение к базам данных, настройка временных файлов или мокирование внешних сервисов.

Аргументы декоратора pytest.fixture

  • scope: определяет время жизни фикстуры. Допустимые значения: 'function' (по умолчанию, выполняется для каждого теста), 'class' (для каждого класса), 'module' (для каждого модуля), 'package' (для каждого пакета), 'session' (один раз за сессию).
  • params: необязательный итерируемый объект для параметризации фикстуры. Каждое значение вызывает новый экземпляр теста, зависящий от фикстуры.
  • autouse: булево значение (по умолчанию False). Если True, фикстура активируется автоматически для всех тестов в её области видимости без явного объявления в аргументах теста.
  • ids: список строковых идентификаторов для параметризованных фикстур. Упрощает идентификацию каждого набора параметров.
  • name: строка, задающая альтернативное имя для фикстуры. По умолчанию используется имя функции.

Фикстура возвращает значение, которое передается в тестовую функцию или другую фикстуру в качестве аргумента. При использовании yield код после него выполняется как завершающая часть (teardown).

Простые примеры применения

Пример фикстуры с областью видимости на уровне функции, которая возвращает данные.

import pytest

@pytest.fixture
def sample_data():
    return {'key': 'value', 'number': 42}

def test_data(sample_data):
    assert sample_data['number'] == 42
Тест проходит успешно.

Фикстура с параметризацией через аргумент params.

@pytest.fixture(params=[1, 2, 3])
def parametrized_fixture(request):
    return request.param

def test_parametrized(parametrized_fixture):
    assert parametrized_fixture > 0
Тест выполняется три раза для каждого значения параметра.

Автоматически используемая фикстура с autouse=True.

@pytest.fixture(autouse=True)
def auto_fixture():
    print('\nФикстура запущена автоматически')
    yield
    print('Очистка после теста')

def test_autouse():
    assert True
Фикстура запущена автоматически
Очистка после теста
Тест проходит.

Изменение имени фикстуры с помощью аргумента name.

@pytest.fixture(name='custom_name')
def original_name():
    return 'данные'

def test_with_name(custom_name):
    assert custom_name == 'данные'
Тест проходит, фикстура доступна под именем custom_name.

Похожие инструменты в Python

В стандартном модуле unittest используются методы setUp и tearDown (а также setUpClass, setUpModule). Они привязаны к классам или модулям и менее гибкие, чем фикстуры pytest, но не требуют установки дополнительных библиотек.

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

Фабричные функции без декораторов просто возвращают данные, но не обеспечивают автоматическую очистку и сложнее в поддержке при зависимостях.

Фикстуры pytest предпочтительнее для комплексных тестовых наборов из-за гибкости областей видимости, параметризации и автоматического управления зависимостями.

Аналоги в других языках программирования

В JavaScript (Jest) используются хуки, такие как beforeEach, afterEach.

// Jest пример
beforeEach(() => {
    initializeData();
});
Функция выполняется перед каждым тестом.

В Java (JUnit 5) применяются аннотации @BeforeEach, @AfterEach. Параметризация осуществляется через @ParameterizedTest.

@BeforeEach
void setUp() {
    data = new ArrayList<>();
}

В PHP (PHPUnit) существуют методы setUp и tearDown.

protected function setUp(): void {
    $this->stack = [];
}

В C# (NUnit, xUnit) используются атрибуты, такие как [SetUp] и [TearDown]. xUnit применяет конструктор и IDisposable для настройки и очистки.

[SetUp]
public void Setup() {
    _service = new MyService();
}

В Go (стандартная библиотека testing) настройка часто выполняется в начале тестовой функции, а очистка через defer. Отсутствуют встроенные фикстуры, но есть сторонние решения.

func TestExample(t *testing.T) {
    db := setupDB()
    defer db.Close()
    // тест
}

В Kotlin (KotlinTest) поддерживаются фикстуры, похожие на pytest, с помощью интерфейсов, таких как BeforeTest и AfterTest.

override fun beforeTest() {
    println("Настройка")
}

Основное отличие pytest.fixture - декларативный подход с использованием декораторов и автоматическое разрешение зависимостей, что часто делает код компактнее.

Распространенные ошибки

Циклические зависимости между фикстурами приводят к ошибке.

import pytest

@pytest.fixture
def fixture_a(fixture_b):
    return 1

@pytest.fixture
def fixture_b(fixture_a):
    return 2
pytest.fixture.FixtureLookupError: циклическая зависимость обнаружена

Использование неподдерживаемого значения для scope вызывает исключение.

@pytest.fixture(scope='invalid')
def bad_fixture():
    return None
ValueError: несмотря на объявление области видимости 'invalid', фикстура может быть вызвана только на уровне 'function'

Передача параметра напрямую в фикстуру без использования request.param.

@pytest.fixture(params=[1,2])
def fix(param):  # Ошибка: param не определен
    return param
TypeError: фикстура() принимает 1 позиционный аргумент, но задано 0

Попытка использовать фикстуру вне тестового контекста, например, в обычной функции.

fix = sample_data()  # sample_data - фикстура
print(fix)
Ошибка: фикстура 'sample_data' не может быть вызвана напрямую. Фикстуры предназначены для тестов.

Изменения в новых версиях

В pytest версии 5.0 появилась поддержка асинхронных фикстур. Для их создания используется @pytest.fixture с асинхронными функциями.

import pytest

@pytest.fixture
async def async_data():
    return await fetch_data()

@pytest.mark.asyncio
async def test_async(async_data):
    assert async_data == 'result'

Начиная с версии 6.2, улучшена обработка ошибок в фикстурах, включая более информативные сообщения при неудачах в фазах setup/teardown.

В версии 7.0 добавлена возможность использовать фикстуры в хуках, таких как pytest_fixture_setup, для более глубокой интеграции.

Параметр scope='package' стал стабильным и рекомендуется для использования.

Расширенные примеры применения

Фикстура с использованием yield для разделения кода настройки и очистки.

Пример python
@pytest.fixture
def resource():
    print('Настройка ресурса')
    data = ['важные', 'данные']
    yield data
    print('Очистка ресурса')

def test_resource(resource):
    assert 'данные' in resource
Настройка ресурса
Очистка ресурса
Тест проходит.

Фикстура, зависящая от другой фикстуры, демонстрирующая автоматическое разрешение зависимостей.

Пример python
@pytest.fixture
def db_connection():
    return 'подключение к БД'

@pytest.fixture
def user(db_connection):
    return {'name': 'Алексей', 'db': db_connection}

def test_user(user):
    assert user['db'] == 'подключение к БД'
Тест проходит, фикстура user получает db_connection автоматически.

Параметризованная фикстура с использованием ids для понятных имен тестов.

Пример python
@pytest.fixture(params=[
    (1, 2, 3),
    (5, 5, 10)
], ids=['сложение 1+2', 'сложение 5+5'])
def numbers(request):
    return request.param

def test_sum(numbers):
    a, b, expected = numbers
    assert a + b == expected
Тесты выполняются с указанными идентификаторами в отчете.

Фикстура с областью видимости на уровне модуля, которая кэширует данные.

Пример python
@pytest.fixture(scope='module')
def cached_data():
    print('Вычисление данных для модуля')
    return complex_calculation()  # предположим, функция существует

def test_one(cached_data):
    assert cached_data is not None

def test_two(cached_data):
    assert cached_data == cached_data
'Вычисление данных для модуля' выводится один раз, данные используются в обоих тестах.

Использование фикстуры с фабрикой для создания динамических данных.

Пример python
@pytest.fixture
def make_item():
    created_items = []
    def _make(name):
        item = {'id': len(created_items), 'name': name}
        created_items.append(item)
        return item
    yield _make
    print(f'Создано элементов: {len(created_items)}')

def test_factory(make_item):
    item1 = make_item('первый')
    item2 = make_item('второй')
    assert item1['id'] != item2['id']
Создано элементов: 2
Тест проходит.

Асинхронная фикстура с использованием asyncio.

Пример python
import asyncio
import pytest

@pytest.fixture
async def async_fixture():
    await asyncio.sleep(0.1)
    return 'асинхронный результат'

@pytest.mark.asyncio
async def test_async_fixture(async_fixture):
    result = await async_fixture
    assert 'асинхронный' in result
Тест проходит после короткой задержки.

питон pytest.fixture function comments

En
Pytest.fixture Mark function as fixture