Программирование PythonТестированиеPython-разработчик, пишущий автотесты

Разберите ситуацию: фикстура pytest возвращает фабрику, которая создаёт временные объекты при каждом вызове...

Разберите ситуацию: фикстура pytest возвращает фабрику, которая создаёт временные объекты при каждом вызове. Что определяет, будут ли эти объекты общими между тестами?

Проходите собеседования с ИИ помощником Hintsage

Краткий ответ

Область действия фикстуры определяет, как долго pytest хранит сам объект-фабрику, но не управляет объектами, которые фабрика создаёт позже. Каждый вызов фабрики обычно создаёт новый объект; общими между тестами они станут только при явном кэшировании внутри фабрики или её замыкании.

Исторический контекст

Фикстуры появились как способ отделить подготовку окружения теста от самого сценария и централизовать его очистку. Однако одному тесту нередко требуется создать несколько однотипных объектов с разными параметрами, поэтому фикстура может возвращать не готовый объект, а функцию-фабрику.

Такой подход объединяет внедрение зависимостей pytest с обычной фабрикой Python. Он позволяет использовать единые настройки и очистку, сохраняя возможность создавать независимые экземпляры внутри теста.

Постановка проблемы

Важно различать два жизненных цикла: жизненный цикл результата фикстуры и жизненный цикл продуктов фабрики. Если этого не сделать, разработчик может ошибочно ожидать, что module- или session-scoped фикстура автоматически сделает все созданные ею объекты общими.

Обратная ошибка тоже опасна: function-scoped фикстура не гарантирует независимость объектов, если фабрика использует внешнее изменяемое состояние или возвращает один и тот же кэшированный экземпляр. Это приводит к утечке состояния между тестами, нестабильности и зависимости от порядка выполнения.

Подробное решение

pytest кэширует результат фикстуры в пределах её области действия. Если результатом является вызываемая функция, кэшируется именно эта функция. Вызовы функции-фабрики не становятся отдельными фикстурами и не отслеживаются pytest как самостоятельные ресурсы.

Например:

import pytest @pytest.fixture(scope="module") def make_user(): def factory(name): return {"name": name} return factory def test_users(make_user): first = make_user("Анна") second = make_user("Борис") assert first is not second assert first["name"] == "Анна"

В этом примере в рамках модуля используется один объект factory, но каждый его вызов создаёт новый словарь. Если фабрика замыкает список, словарь или кэш, состояние этого замыкания будет жить столько же, сколько живёт сама фикстура.

Очистка продуктов фабрики также не выполняется автоматически. Её нужно организовать явно: зарегистрировать созданные ресурсы внутри фикстуры и освободить их после yield, использовать контекстный менеджер или вернуть объект, у которого есть собственный механизм очистки.

Выбор области действия зависит от требуемой изоляции. Более широкая область уменьшает стоимость подготовки общей конфигурации, но увеличивает риск общего изменяемого состояния; function scope обычно безопаснее для состояния, но может быть дороже.

Ситуация из практики

Тесты API используют фабрику, создающую записи в тестовой базе. Изначально фабрика была session-scoped и сохраняла созданные записи в замыкании. В результате один тест видел записи, созданные другим, а сбой зависел от порядка запуска.

Рассматривались два варианта. Сделать фикстуру function-scoped было проще и усиливало изоляцию, но повторная инициализация подключения к базе заметно замедляла набор тестов. Оставить session scope и полагаться на очистку в каждом тесте было быстрее, но легко получить утечку при аварийном завершении или забытом удалении.

Выбрали session-scoped подключение и function-scoped фикстуру-фабрику, которая создаёт записи с уникальным идентификатором и регистрирует их для удаления в teardown. Так дорогое подключение осталось общим, а данные каждого теста очищались независимо; нестабильность исчезла без существенного увеличения времени выполнения.

Что кандидаты часто упускают

  1. Сделает ли session-scoped фикстура все результаты фабрики общими?

Нет. Общим будет результат фикстуры — объект-фабрика. Результаты её вызовов будут отдельными, если сама фабрика не использует кэш или другое общее состояние. Поэтому scope влияет на доступность и состояние фабрики, а не автоматически на создаваемые ею значения.

  1. Кто отвечает за удаление объектов, созданных функцией-фабрикой?

За это отвечает код фикстуры или сама фабрика, а не pytest. Обычно фикстура хранит созданные ресурсы и освобождает их в коде после yield; альтернативой служит контекстный менеджер или специализированный объект с методом закрытия. Если ресурс создаётся, но нигде не регистрируется, teardown pytest не узнает о нём и автоматически его не очистит.

  1. Что изменится, если параметризовать саму фикстуру-фабрику?

Для каждого параметра pytest создаст отдельный экземпляр результата фикстуры в соответствии с её областью действия. Внутри каждого такого экземпляра многократные вызовы фабрики по-прежнему не создаются pytest отдельно и подчиняются только логике фабрики. Следовательно, параметризация разделяет варианты конфигурации фабрики, но не заменяет контроль жизненного цикла её продуктов.