В одной pytest фикстуре зарегистрировали два финализатора через request.addfinalizer. В каком порядке pytes...

В одной pytest-фикстуре зарегистрировали два финализатора через request.addfinalizer. В каком порядке pytest выполнит их?

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

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

Финализаторы, зарегистрированные через request.addfinalizer, выполняются в обратном порядке регистрации: последним добавленный запускается первым. Это поведение похоже на стек и позволяет корректно освобождать вложенные ресурсы.

import pytest @pytest.fixture def resource(request): request.addfinalizer(lambda: print("первый")) request.addfinalizer(lambda: print("второй")) return object()

После теста сначала выполнится финализатор, печатающий «второй», затем финализатор, печатающий «первый».

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

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

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

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

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

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

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

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

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

Для простой линейной подготовки часто понятнее использовать yield-фикстуру: код после yield играет роль teardown. Несколько независимых финализаторов удобны, когда очистка регистрируется динамически или ресурс создаётся поэтапно.

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

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

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

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

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

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

  1. Что произойдёт, если фикстура завершится исключением после регистрации финализатора?

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

  2. Как взаимодействуют финализатор фикстуры и код после yield?

    Код после yield также является частью teardown. Если в одной фикстуре используется yield, а через addfinalizer добавлен отдельный callback, они входят в общий порядок очистки; финализатор, зарегистрированный последним, выполняется раньше. Точный порядок следует проектировать явно, а не полагаться на случайное расположение строк.

  3. Почему несколько маленьких фикстур часто безопаснее нескольких финализаторов в одной?

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