Фикстуры (fixtures) в Pytest — это мощный и удобный механизм для подготовки данных или состояния окружения перед выполнением тестов. Проще говоря, фикстуры отвечают на два важных вопроса:
- Откуда взять данные для тестов?
- Как настроить "поляну" для тестирования?
Зачем нужны фикстуры?
- Автоматизация рутинных задач: повторяющийся код по инициализации данных выносится в одно место.
- Организация тестов: каждая фикстура выполняет одну чёткую задачу, улучшая читаемость и поддержку тестов.
- Повторное использование: одна фикстура может быть использована сразу в нескольких тестах.
- Быстрый старт: даже если тестов десятки или сотни, подготовка окружения происходит без лишних усилий.
Базовое создание фикстур
Фикстуры в Pytest — это функции, помеченные декоратором @pytest.fixture. Давайте начнем с простого примера.
Пример 1: фиксированные данные
Допустим, у нас есть API для управления пользователями. Мы пишем тест для регистрации нового пользователя. Чтобы не пересоздавать данные вручную, мы определяем фикстуру user_data:
# tests/conftest.py
import pytest
@pytest.fixture
def user_data():
"""Фикстура для создания тестовых данных пользователя"""
return {"username": "test_user", "password": "secure_password"}
Теперь мы можем использовать эту фикстуру в любом тесте:
# tests/test_users.py
from fastapi.testclient import TestClient
from main import app
client = TestClient(app)
def test_user_registration(user_data):
"""Тестируем регистрацию нового пользователя"""
response = client.post("/register", json=user_data)
assert response.status_code == 201
assert response.json()["username"] == user_data["username"]
Когда вы указываете название фикстуры (user_data) в аргументах функции теста, Pytest автоматически вызывает эту фикстуру и передает результат в тест.
Пример 2: подключение к базе данных
Очень часто фикстуры используются для подготовки сложного окружения — например, подключения к базе данных.
# tests/conftest.py
import pytest
from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker
from myapp.database import Base
DATABASE_URL = "sqlite:///./test.db"
@pytest.fixture(scope="session")
def db_engine():
"""Фикстура для создания движка базы данных"""
engine = create_engine(DATABASE_URL)
Base.metadata.create_all(bind=engine)
yield engine
Base.metadata.drop_all(bind=engine)
@pytest.fixture
def db_session(db_engine):
"""Фикстура для создания сессии базы данных"""
SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=db_engine)
session = SessionLocal()
yield session
session.close()
Здесь мы:
- Создаем движок базы данных один раз на весь тестовый запуск благодаря
scope="session". - Создаем и подготавливаем таблицы перед стартом тестов.
- Убираем таблицы после завершения всех тестов (cleanup).
- Генерируем отдельные сессии базы данных для каждого теста.
Пример 3: использование нескольких фикстур
Фикстуры можно комбинировать. Например, если нам нужна база данных и заранее созданный пользователь:
# tests/conftest.py
@pytest.fixture
def test_user(db_session):
"""Создание тестового пользователя в базе данных"""
new_user = User(username="test_user", password="secure_password")
db_session.add(new_user)
db_session.commit()
return new_user
Теперь мы можем использовать test_user в любом тесте:
# tests/test_users.py
def test_get_user(test_user, client):
response = client.get(f"/users/{test_user.id}")
assert response.status_code == 200
assert response.json()["username"] == test_user.username
Пример 4: параметризованные фикстуры
Иногда хочется протестировать один и тот же эндпоинт с разными вариациями данных. Для этого Pytest предлагает параметризованные фикстуры.
# tests/conftest.py
@pytest.fixture(params=[
{"username": "user1", "password": "pass1"},
{"username": "user2", "password": "pass2"},
{"username": "user3", "password": "pass3"}
])
def user_data(request):
"""Фикстура с разными наборами данных"""
return request.param
Теперь каждый вызов теста будет использовать новый набор данных:
# tests/test_users.py
def test_multiple_users(user_data, client):
response = client.post("/register", json=user_data)
assert response.status_code == 201
assert response.json()["username"] == user_data["username"]
Советы и трюки для работы с фикстурами
- Используйте файл
conftest.py: Поместите часто используемые фикстуры в файлtests/conftest.py, чтобы они автоматически подгружались во всех тестах. - Настройте
scope: Фикстуры можно выполнять для:function(по умолчанию) — на каждый тест.class— для всех тестов внутри тестового класса.module— для всех тестов внутри файла.session— для всех тестов одного запуска.
- Используйте
yield: Для действий, требующих "уборки" после тестов (например, удаление данных из базы), используйтеyieldдля разделения этапов setup и teardown. - Импорты внутри фикстур: Если фикстура использует нечасто используемый модуль, импортируйте его внутри самой функции, чтобы не загружать лишнюю память во время запуска тестов.
Типичные ошибки при работе с фикстурами
Давайте разберем, с какими проблемами вы можете столкнуться:
- Забыли указать фикстуру в аргументах теста. Например, вы определили фикстуру, но не использовали её в тесте. Pytest просто её проигнорирует.
- Неправильное использование
scope. Если вы настроили фикстуру какsession, а данные в ней не являются "потокобезопасными", это может вызвать неожиданные баги. - Неправильное завершение работы фикстур. Если забыть удалить временные данные или закрыть соединение, это может привести к "утечкам" в тестовом окружении.
Резюме
Фикстуры Pytest значительно упрощают жизнь разработчикам, избавляя от рутинного создания данных и настройки окружения. На следующей лекции мы разберем запуск тестов и генерацию отчётов о покрытии, чтобы вы смогли оценить, насколько качественно покрыт ваш код. А пока используйте фикстуры и наслаждайтесь магией автоматизации!
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ