Приложение работает нормально. Открываешь вторую вкладку — данные не те. Тесты проходят по отдельности, но падают вместе. Причина — объект на уровне модуля.
Проблема 1: слишком много sharing
Типичный паттерн в FastAPI-проектах:
# database.py
engine = create_engine("sqlite:///database.db")
engine создаётся в момент импорта. Все модули, которые импортируют из database.py, используют один и тот же engine, один пул соединений, один файл БД.Тесты чистят базу через
setup/teardown, но состояние всё равно утекает: автоинкремент ID, кэш метаданных, внутреннее состояние SQLite. Тест assert id == 1 проходит первым, но падает если запустить после другого теста.Фикс — создавать engine на каждый тест:
@pytest.fixture
def engine():
engine = create_engine("sqlite://", echo=False)
SQLModel.metadata.create_all(engine)
yield engine
engine.dispose()
In-memory БД, никакого shared state, никакой уборки.
Проблема 2: слишком мало sharing
Обратная ситуация. Зависимость FastAPI создаёт новый экземпляр на каждый запрос:
def get_data_source() -> DataSource:
return FakeDataSource(...) # новый объект каждый раз
Каждая вкладка браузера получает свой
FakeDataSource со своим состоянием с нуля. Вкладка А показывает одно, вкладка Б — другое. Никакой общей реальности нет.Фикс —
app.state:
@asynccontextmanager
async def lifespan(app: FastAPI):
app.state.data_source = get_data_source()
yield
def get_data_source(request: Request) -> DataSource:
return request.app.state.data_source
Один экземпляр на весь процесс, легко подменяется в тестах через
dependency_overrides.Правило одной строкой
Если объект хранит мутабельное состояние — выбирай его скоуп осознанно. Слишком широкий (модуль) — тесты текут друг в друга. Слишком узкий (каждый запрос) — нет общего состояния. Константы и чистые значения на уровне модуля — ок. Ресурсы (DB engine, HTTP клиент, кэш, пул соединений) — нет.
📍 Навигация: Вакансии • Задачи • Собесы
Библиотека питониста
#буст