Фикстуры и параметризация pytest

Фикстуры и параметризация в pytest: ещё две вещи, которые делают тесты короче.

Фикстура pytest — функция, помеченная @pytest.fixture, которая готовит данные и «подаётся» в тест через аргумент с тем же именем.

Фикстуры через аргументы

В unittest подготовку кладут в setUp, и она общая для всего класса. В pytest подход другой и часто удобнее: фикстура — это именованная функция, а тест просто перечисляет нужные фикстуры в параметрах. pytest сам их найдёт и подставит.

import pytest

@pytest.fixture
def sample_cart():
    # подготовка: вернём готовую корзину
    return ["книга", "ручка"]

def test_cart_size(sample_cart):
    # pytest сам подставит результат фикстуры в аргумент
    assert len(sample_cart) == 2

def test_cart_contains(sample_cart):
    assert "книга" in sample_cart

Преимущество: тест берёт ровно те фикстуры, что ему нужны. Не нужно тащить общий setUp туда, где он не требуется. Фикстуры легко комбинируются и переиспользуются между файлами.

Уборка через yield

Если фикстуре нужна «уборка» (аналог tearDown), используют yield: код до yield — подготовка, после — уборка, которая выполнится по завершении теста.

import pytest

@pytest.fixture
def temp_file(tmp_path):
    path = tmp_path / "data.txt"
    path.write_text("hello")
    yield path            # отдаём ресурс тесту
    path.unlink()         # уборка после теста

Параметризация: @pytest.mark.parametrize

Это аналог subTest, но ещё лаконичнее. Декоратор задаёт таблицу входов и ожиданий, а pytest сам прогоняет тест по каждой строке — как отдельный тест со своим статусом.

import pytest

def fizzbuzz(n):
    if n % 15 == 0: return "FizzBuzz"
    if n % 3 == 0:  return "Fizz"
    if n % 5 == 0:  return "Buzz"
    return str(n)

@pytest.mark.parametrize("n, expected", [
    (1, "1"),
    (3, "Fizz"),
    (5, "Buzz"),
    (15, "FizzBuzz"),
])
def test_fizzbuzz(n, expected):
    assert fizzbuzz(n) == expected

Четыре строки таблицы превратятся в четыре независимых теста. В отчёте они выглядят как test_fizzbuzz[1-1], test_fizzbuzz[3-Fizz] и т.д. — сразу видно, какой набор упал, и остальные не прерываются.

Сравнение параметризации

unittestpytest
subTest внутри цикла@pytest.mark.parametrize
один тест, под-проверкиотдельный тест на каждый набор
подпись вручную (n=n)подпись из значений автоматически

Проверка исключений: pytest.raises

Аналог assertRaises в pytest называется pytest.raises и тоже работает контекстным менеджером:

import pytest

def divide(a, b):
    if b == 0:
        raise ZeroDivisionError("на ноль нельзя")
    return a / b

def test_divide_by_zero():
    with pytest.raises(ZeroDivisionError):
        divide(1, 0)

conftest.py: общие фикстуры

Если фикстура нужна нескольким файлам тестов, её выносят в специальный файл conftest.py. pytest автоматически подхватывает фикстуры оттуда — импортировать ничего не нужно. Это удобный способ держать общую подготовку (тестовая база, клиент API, тестовые данные) в одном месте и переиспользовать во всём проекте. В unittest аналога нет — там общий код обычно выносят в базовый класс-наследник TestCase.

Область видимости фикстуры (scope)

У фикстур pytest есть параметр scope, который управляет тем, как часто они пересоздаются: function (по умолчанию — на каждый тест), module (раз на файл), session (раз на весь прогон). Это прямой аналог выбора между setUp и setUpClass в unittest, только гибче. Дорогую подготовку (поднять тестовую БД) делают с широким scope, чтобы не повторять её на каждый тест, а лёгкие данные оставляют на уровне функции ради изоляции.

Итог

  • Фикстуры pytest — функции @pytest.fixture, подаются в тест через аргументы.
  • Уборку оформляют через yield внутри фикстуры.
  • @pytest.mark.parametrize превращает таблицу в набор отдельных тестов.
  • pytest.raises — аналог assertRaises как контекст.
Проверьте себя
1. Как тест получает фикстуру в pytest?
AЧерез наследование
BЧерез аргумент с именем фикстуры — pytest подставит её автоматически
CЧерез global
DЧерез setUp
2. Как в фикстуре pytest сделать уборку (аналог tearDown)?
AЧерез метод cleanup
BЧерез yield: код до yield — подготовка, после — уборка
CЧерез return None
DУборка невозможна
3. Что делает @pytest.mark.parametrize?
AПропускает тест
BПрогоняет тест по таблице входов как набор отдельных тестов
CЗамеряет время
DМокает зависимости
Поддержать проект