Фикстуры и параметризация 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] и т.д. — сразу видно, какой набор упал, и остальные не прерываются.
Сравнение параметризации
| unittest | pytest |
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как контекст.