В тестовом модуле фикстура pytest с областью действия «на модуль» используется тестами из двух классов. Как...

В тестовом модуле фикстура pytest с областью действия «на модуль» используется тестами из двух классов. Каков её жизненный цикл?

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

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

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

Класс, в котором находится тест, не создаёт отдельный экземпляр фикстуры: оба класса используют один экземпляр, если не меняются параметры или другие условия разрешения фикстуры.

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

Модель fixtures в pytest появилась как более явный и композиционный способ организовывать подготовку и очистку тестов. Она решает проблему жёстко связанных с классами или именами методов setup-функций, которые сложнее переиспользовать и комбинировать.

Область действия позволяет согласовать стоимость подготовки с изоляцией тестов: дорогой ресурс можно создавать реже, а изменяемое состояние — чаще сбрасывать.

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

Выбор области действия влияет одновременно на производительность и независимость тестов. Если ошибочно использовать область «на модуль» для изменяемого состояния, один тест может оставить данные, которые повлияют на следующий тест или на тесты другого класса.

Слишком узкая область действия тоже не всегда безопасна с точки зрения общей системы: частое создание соединений, контейнеров или клиентов может значительно замедлить набор тестов. Поэтому нужно разделять жизненный цикл ресурса и жизненный цикл его состояния.

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

Pytest кэширует результат фикстуры в соответствии с её областью действия. Для области «на модуль» ключевым контейнером является один файл тестов, поэтому порядок классов не порождает новые экземпляры.

Если фикстура содержит завершающую часть после yield, она выполняется после завершения всех тестов данного модуля, которые зависят от этой фикстуры. Зависимости фикстуры также учитываются: pytest сначала подготавливает нужные зависимости, а очищает их в обратном порядке.

import pytest @pytest.fixture(scope="module") def client(): resource = create_client() yield resource resource.close() def test_first(client): assert client.is_ready() def test_second(client): assert client.is_ready()

В примере client создаётся один раз для модуля, используется обоими тестами и закрывается после них. Такой подход не гарантирует изоляцию изменений внутри client; при изменяемом состоянии обычно добавляют фикстуру с областью «на функцию», которая сбрасывает состояние перед каждым тестом.

Для параметризованной фикстуры экземпляры создаются отдельно для разных параметров, поэтому утверждение «один раз» означает один раз на сочетание модуля и параметра. Кроме того, фикстура с более широкой областью действия не может напрямую зависеть от фикстуры с более узкой областью: иначе её кэшированный объект зависел бы от уже завершившего жизненный цикл ресурса.

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

Команда тестировала API-клиент, создание которого открывало дорогое соединение. Сначала использовали область «на функцию»: тесты были хорошо изолированы, но набор стал медленным из-за постоянного открытия и закрытия соединений.

Рассматривались три варианта. Область «на сеанс» давала лучшую скорость, но состояние и ошибки одного модуля могли влиять на весь запуск. Область «на функцию» обеспечивала изоляцию, но была дорогой. Область «на модуль» уменьшала стоимость подготовки, однако требовала контроля состояния.

Выбрали область «на модуль» для соединения и отдельную фикстуру «на функцию» для очистки данных перед тестом. В результате соединение переиспользовалось внутри модуля, а результаты тестов не зависели от порядка выполнения.

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

1. Что произойдёт, если два теста изменяют объект, возвращённый фикстурой с областью «на модуль»?

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

Надёжное решение — возвращать неизменяемое значение, создавать копию для каждого теста или использовать отдельную фикстуру с областью «на функцию», которая подготавливает и очищает состояние. Простое изменение порядка тестов проблему не устраняет.

2. Можно ли фикстуре с областью «на модуль» напрямую передать фикстуру с областью «на функцию»?

Нет, такая зависимость противоречит жизненному циклу областей. Модульная фикстура должна была бы удерживать результат функциональной фикстуры после завершения теста, для которого тот результат был создан.

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

3. Как параметризация влияет на обещание «фикстура создаётся один раз на модуль»?

Параметризация создаёт отдельный экземпляр фикстуры для каждого значения параметра. Поэтому при двух параметрах модульная фикстура обычно будет создана дважды — по одному разу для каждого параметризованного варианта, а не один раз на весь модуль.

Это важно для ресурсов, которые нельзя безопасно создавать повторно или которые имеют внешние побочные эффекты. Нужно учитывать число параметров при оценке времени запуска и явно очищать каждый созданный экземпляр.