Короткий ответ
Нужно поднимать реальный экземпляр СУБД (контейнер PostgreSQL) перед запуском интеграционных тестов, прогонять на нём миграции и настраивать приложение на подключение к этому контейнеру, а после прогона — останавливать и удалять контейнер.
Как это работает подробнее
Библиотека testcontainers программно поднимает Docker-контейнер (например, postgres:16) на случайном свободном порту прямо из тестовой фикстуры session-уровня, ждёт готовности БД, после чего тесты подключаются к ней как к обычной тестовой базе; в конце прогона контейнер автоматически останавливается.
Пример кода
testing_example_315.py
from testcontainers.postgres import PostgresContainer
import pytest
@pytest.fixture(scope="session")
def postgres_container():
with PostgresContainer("postgres:16") as postgres:
yield postgres
@pytest.fixture(scope="session")
def db_engine(postgres_container):
engine = create_engine(postgres_container.get_connection_url())
run_migrations(engine)
return engineЧто использовать на практике
- Практика. Такой подход даёт более высокую уверенность, чем SQLite-заглушка, так как использует ту же СУБД и версию, что и production, включая специфичные типы данных, ограничения и поведение транзакций. В CI это требует доступности Docker-демона на раннере и добавляет время на старт контейнера, которое стоит минимизировать кешированием образа.
Подводные камни
- Риск. Запускать такие тесты параллельно с общим портом или схемой БД без изоляции, из-за чего параллельные прогоны мешают друг другу и дают случайные падения. Не ограничивать время ожидания готовности контейнера, из-за чего при проблемах с Docker тесты зависают на долгое время вместо быстрого и понятного падения с ясной причиной.