Короткий ответ
Тест запускают на изолированной тестовой БД, каждый тест оборачивают в транзакцию, которая откатывается после теста, чтобы тесты не влияли друг на друга.
Как это работает подробнее
Django предоставляет класс TestCase, который оборачивает каждый тест в транзакцию и делает ROLLBACK в конце; для SQLAlchemy аналогичный паттерн реализуют вручную через фикстуры pytest, открывающие транзакцию на тестовую БД.
Пример кода
example.py
import pytest
from myapp.models import User
from myapp.services import register_user
@pytest.mark.django_db
def test_register_user_creates_record():
user = register_user(email="bob@example.com", name="Bob")
assert user.id is not None
assert User.objects.filter(email="bob@example.com").count() == 1
# pytest-django автоматически оборачивает тест в транзакцию и откатывает её после тестаЧто использовать на практике
- Практика. Тестовую БД настраивают через отдельные переменные окружения/настройки, отличные от production, и никогда не указывают реальный production DSN в тестовом окружении.
Подводные камни
- Ошибка. Используют один и тот же экземпляр БД для тестов и разработки без изоляции транзакций, из-за чего тесты влияют друг на друга; мокают слишком много уровней БД, из-за чего тест не проверяет реальное SQL-поведение (например, constraint).