Ответ
Основная цель при тестировании работы с БД — изоляция тестов. Каждый тест должен выполняться в предсказуемом, чистом окружении, не зависящем от результатов предыдущих тестов. В pytest это достигается с помощью комбинации фикстур с разной областью видимости (scope) и механизма транзакций.
Паттерн состоит из двух ключевых фикстур:
- Фикстура для подключения (
scope="session"): Создает одно подключение к базе данных на всю тестовую сессию. Это экономит время, так как не нужно устанавливать новое соединение для каждого теста. - Фикстура для транзакции (
scope="function"): Выполняется для каждого теста. Она начинает транзакцию, передает сессию в тест, а после его завершения откатывает транзакцию. Откат (rollback) отменяет все изменения (INSERT, UPDATE, DELETE), сделанные в тесте.
Пример реализации для SQLAlchemy
# conftest.py
import pytest
from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker, Session
# 1. Фикстура для создания движка, выполняется один раз за сессию
@pytest.fixture(scope="session")
def db_engine():
# Используем БД в памяти для скорости
engine = create_engine("sqlite:///:memory:")
# Здесь можно создать таблицы
# Base.metadata.create_all(engine)
yield engine
engine.dispose()
# 2. Фикстура для создания сессии в транзакции для каждого теста
@pytest.fixture(scope="function")
def db_session(db_engine):
connection = db_engine.connect()
# Начинаем транзакцию
transaction = connection.begin()
# Создаем сессию
SessionLocal = sessionmaker(bind=connection)
session: Session = SessionLocal()
yield session
# После завершения теста откатываем транзакцию
session.close()
transaction.rollback()
connection.close()
Пример использования в тесте
# test_user.py
from models import User # Предполагается, что есть модель User
def test_user_creation(db_session):
# Arrange: создаем пользователя
new_user = User(name="testuser", email="test@example.com")
# Act: добавляем в сессию и коммитим (в рамках транзакции)
db_session.add(new_user)
db_session.commit()
# Assert: проверяем, что пользователь появился в БД
user_in_db = db_session.query(User).filter_by(name="testuser").first()
assert user_in_db is not None
assert user_in_db.email == "test@example.com"
# Следующий тест начнется с пустой БД, так как транзакция test_user_creation будет отменена.
Преимущества этого подхода:
- Изоляция: Тесты не влияют друг на друга.
- Скорость: Откат транзакции намного быстрее, чем удаление и пересоздание таблиц для каждого теста.
- Чистота: Код теста работает с сессией так же, как и основной код приложения.