Короткий ответ
Каждый тест оборачивают в транзакцию (или savepoint) на реальном соединении с тестовой БД, а после теста транзакцию откатывают вместо того, чтобы пересоздавать схему или удалять данные вручную.
Как это работает подробнее
Фикстура открывает соединение, начинает транзакцию и создаёт сессию ORM, привязанную к этому соединению; все операции теста выполняются в её рамках. После теста fixture делает rollback — все изменения, включая вставки и обновления, отменяются, а схема и исходные данные (если были) остаются нетронутыми для следующего теста.
Пример кода
testing_example_310.py
@pytest.fixture
def db_session(db_engine):
connection = db_engine.connect()
trans = connection.begin()
session = Session(bind=connection, join_transaction_mode="create_savepoint")
yield session
session.close()
trans.rollback()
connection.close()
def test_create_order_is_rolled_back(db_session):
db_session.add(Order(total=100))
db_session.flush()
assert db_session.query(Order).count() == 1
# после теста fixture откатит транзакцию, и следующий тест увидит 0 заказовЧто использовать на практике
- Практика. Такой подход значительно быстрее, чем пересоздание всей схемы БД между тестами, и хорошо работает с большинством тестов, но ломается, если тестируемый код сам управляет транзакциями (commit/rollback) — вложенные savepoint нужно настраивать аккуратно, либо использовать отдельный engine для такого кода.
Подводные камни
- Риск. Не учитывать, что некоторые операции (например, DDL в части СУБД, или явный commit внутри тестируемого кода) автоматически завершают внешнюю транзакцию, из-за чего откат не срабатывает так, как ожидалось, и данные остаются в БД. Переиспользовать одну и ту же сессию между несвязанными тестами без отката, что приводит к накоплению «мусорных» данных и падениям, зависящим от порядка запуска.