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

В фикстуре pytest очистка зарегистрирована через addfinalizer до создания ресурса. Будет ли вызвана очистка...

В фикстуре pytest очистка зарегистрирована через addfinalizer до создания ресурса. Будет ли вызвана очистка, если создание ресурса завершится исключением?

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

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

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

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

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

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

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

Регистрация финализатора не связана с успешным завершением оставшейся части фикстуры. После вызова addfinalizer pytest считает функцию очистки зарегистрированной и планирует её выполнение при завершении фикстуры.

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

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

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

Безопасный порядок действий — сначала создать ресурс, затем зарегистрировать очистку:

import pytest @pytest.fixture def resource(request): handle = open_resource() request.addfinalizer(handle.close) return handle

Если зарегистрировать финализатор заранее необходимо, его код должен проверять состояние ресурса:

@pytest.fixture def resource(request): handle = None def cleanup(): if handle is not None: handle.close() request.addfinalizer(cleanup) handle = open_resource() return handle

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

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

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

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

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

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

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

  1. Что произойдёт, если финализатор зарегистрирован, но тест вообще не начал выполняться?

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

  2. Почему регистрация финализатора после создания ресурса обычно безопаснее, чем yield с той же очисткой?

    В yield-фикстуре код после yield выполняется только после успешного достижения yield. Если подготовка завершилась раньше исключением, teardown-код после yield не запускается. У addfinalizer граница иная: финализатор начинает действовать с момента регистрации, поэтому преждевременная регистрация может вызвать очистку ресурса, которого ещё нет.

  3. Что изменится, если одна фикстура зарегистрирует несколько финализаторов?

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