Программирование PythonТестированиеИнженер по автоматизации тестирования на Python

Циклическая зависимость двух фикстур pytest: какой результат увидит разработчик при запуске теста?

Циклическая зависимость двух фикстур pytest: какой результат увидит разработчик при запуске теста?

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

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

Тест завершится ошибкой подготовки фикстур до выполнения тела теста. pytest обнаружит цикл при разрешении графа зависимостей и сообщит о рекурсивной зависимости; порядок областей действия фикстур этот цикл не устраняет.

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

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

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

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

Например, фикстура first требует second, а second в свою очередь требует first. Чтобы создать first, нужно сначала создать second, но для этого снова требуется first.

Тело теста в такой ситуации не запускается. Попытка скрыть проблему изменением scope, добавлением autouse или перестановкой аргументов не помогает: эти настройки меняют область действия или способ подключения фикстуры, но не разрывают граф зависимостей.

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

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

Минимальная схема выглядит так:

import pytest @pytest.fixture def first(second): return object() @pytest.fixture def second(first): return object() def test_example(first): pass

Выполнение остановится на этапе подготовки first и second. Точный текст сообщения зависит от версии и контекста запуска, но смысл ошибки — recursive dependency или рекурсивная зависимость фикстур.

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

  • resource создаёт общий ресурс;
  • first использует resource;
  • second использует resource.

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

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

В интеграционных тестах фикстура client создавала HTTP-клиент и требовала server_url, а server_url пыталась получить URL из client. Разработчик пробовал повысить scope client до session, но ошибка сохранялась: scope не меняет направленность зависимости.

Рассматривались два варианта. Можно было оставить получение URL внутри client, но тогда server_url пришлось бы вычислять отдельно, что создавало риск расхождения конфигурации. Другой вариант — выделить фикстуру server_config, которая независимо подготавливает адрес и параметры сервера, а затем передать её и client, и server_url.

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

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

  1. Дополнительный вопрос: Обнаружит ли pytest цикл, если он проходит через три фикстуры, а не через две?

    Ответ: Да. Для pytest не имеет значения длина цикла. Цепочка first → second → third → first также не может быть разрешена, потому что при обработке third обнаруживается уже активная first. Важно анализировать весь граф зависимостей, а не только непосредственные пары фикстур.

  2. Дополнительный вопрос: Можно ли устранить циклическую зависимость, изменив scope одной из фикстур?

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

  3. Дополнительный вопрос: Что произойдёт с очисткой фикстур, если цикл обнаружен до создания ресурсов?

    Ответ: Тело циклических фикстур не будет выполнено, поэтому ресурсы, которые они должны были создать, не появятся. Соответственно, их teardown-код после yield не выполняется. Очистка уже созданных независимых фикстур, подготовленных до момента ошибки, обрабатывается отдельно по обычным правилам pytest; рассчитывать на очистку ресурса, создание которого не состоялось, нельзя.