Разберите ситуацию: фикстура pytest возвращает фабрику, которая создаёт временные объекты при каждом вызове. Что определяет, будут ли эти объекты общими между тестами?
Область действия фикстуры определяет, как долго pytest хранит сам объект-фабрику, но не управляет объектами, которые фабрика создаёт позже. Каждый вызов фабрики обычно создаёт новый объект; общими между тестами они станут только при явном кэшировании внутри фабрики или её замыкании.
Фикстуры появились как способ отделить подготовку окружения теста от самого сценария и централизовать его очистку. Однако одному тесту нередко требуется создать несколько однотипных объектов с разными параметрами, поэтому фикстура может возвращать не готовый объект, а функцию-фабрику.
Такой подход объединяет внедрение зависимостей pytest с обычной фабрикой Python. Он позволяет использовать единые настройки и очистку, сохраняя возможность создавать независимые экземпляры внутри теста.
Важно различать два жизненных цикла: жизненный цикл результата фикстуры и жизненный цикл продуктов фабрики. Если этого не сделать, разработчик может ошибочно ожидать, что module- или session-scoped фикстура автоматически сделает все созданные ею объекты общими.
Обратная ошибка тоже опасна: function-scoped фикстура не гарантирует независимость объектов, если фабрика использует внешнее изменяемое состояние или возвращает один и тот же кэшированный экземпляр. Это приводит к утечке состояния между тестами, нестабильности и зависимости от порядка выполнения.
pytest кэширует результат фикстуры в пределах её области действия. Если результатом является вызываемая функция, кэшируется именно эта функция. Вызовы функции-фабрики не становятся отдельными фикстурами и не отслеживаются pytest как самостоятельные ресурсы.
Например:
В этом примере в рамках модуля используется один объект factory, но каждый его вызов создаёт новый словарь. Если фабрика замыкает список, словарь или кэш, состояние этого замыкания будет жить столько же, сколько живёт сама фикстура.
Очистка продуктов фабрики также не выполняется автоматически. Её нужно организовать явно: зарегистрировать созданные ресурсы внутри фикстуры и освободить их после yield, использовать контекстный менеджер или вернуть объект, у которого есть собственный механизм очистки.
Выбор области действия зависит от требуемой изоляции. Более широкая область уменьшает стоимость подготовки общей конфигурации, но увеличивает риск общего изменяемого состояния; function scope обычно безопаснее для состояния, но может быть дороже.
Тесты API используют фабрику, создающую записи в тестовой базе. Изначально фабрика была session-scoped и сохраняла созданные записи в замыкании. В результате один тест видел записи, созданные другим, а сбой зависел от порядка запуска.
Рассматривались два варианта. Сделать фикстуру function-scoped было проще и усиливало изоляцию, но повторная инициализация подключения к базе заметно замедляла набор тестов. Оставить session scope и полагаться на очистку в каждом тесте было быстрее, но легко получить утечку при аварийном завершении или забытом удалении.
Выбрали session-scoped подключение и function-scoped фикстуру-фабрику, которая создаёт записи с уникальным идентификатором и регистрирует их для удаления в teardown. Так дорогое подключение осталось общим, а данные каждого теста очищались независимо; нестабильность исчезла без существенного увеличения времени выполнения.
Нет. Общим будет результат фикстуры — объект-фабрика. Результаты её вызовов будут отдельными, если сама фабрика не использует кэш или другое общее состояние. Поэтому scope влияет на доступность и состояние фабрики, а не автоматически на создаваемые ею значения.
За это отвечает код фикстуры или сама фабрика, а не pytest. Обычно фикстура хранит созданные ресурсы и освобождает их в коде после yield; альтернативой служит контекстный менеджер или специализированный объект с методом закрытия. Если ресурс создаётся, но нигде не регистрируется, teardown pytest не узнает о нём и автоматически его не очистит.
Для каждого параметра pytest создаст отдельный экземпляр результата фикстуры в соответствии с её областью действия. Внутри каждого такого экземпляра многократные вызовы фабрики по-прежнему не создаются pytest отдельно и подчиняются только логике фабрики. Следовательно, параметризация разделяет варианты конфигурации фабрики, но не заменяет контроль жизненного цикла её продуктов.