Короткий ответ
Scope определяет, как часто пересоздаётся фикстура: function — перед каждым тестом, session — один раз за весь прогон; для тестовой БД обычно создают соединение/схему на уровне session, а очистку данных — на уровне function, чтобы не терять изоляцию тестов.
Как это работает подробнее
pytest кеширует результат фикстуры в рамках её scope и переиспользует его для всех подходящих тестов, пока scope не завершится, вызывая фикстуру заново только при смене области видимости. Более широкий scope (session, module) снижает накладные расходы на дорогие операции (поднятие контейнера с БД, подключение к сети), а узкий scope (function) обеспечивает полную изоляцию между тестами ценой повторной инициализации.
Пример кода
testing_example_307.py
import pytest
@pytest.fixture(scope="session")
def db_engine():
engine = create_engine(TEST_DATABASE_URL)
run_migrations(engine)
yield engine
engine.dispose()
@pytest.fixture
def db_session(db_engine):
connection = db_engine.connect()
transaction = connection.begin()
session = Session(bind=connection)
yield session
session.close()
transaction.rollback() # откат изменений конкретного теста
connection.close()Что использовать на практике
- Практика. Частый паттерн: фикстура session-уровня поднимает engine и применяет миграции один раз за весь прогон, а фикстура function-уровня оборачивает каждый тест в транзакцию и откатывает её после теста (через savepoint или rollback), совмещая скорость дорогой подготовки с полной изоляцией данных между тестами.
Подводные камни
- Риск. Использовать session-scope для фикстуры, которая мутирует общее состояние (например, вставляет строки в таблицу), без последующей очистки — тесты начинают зависеть от порядка выполнения и ломаются при параллельном запуске через pytest-xdist. Делать всё function-scope без необходимости, из-за чего дорогие операции (поднятие контейнера) повторяются на каждый тест и сильно замедляют весь прогон без пользы для изоляции.