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

Представьте, что тест использует yield фикстуру, а сам тест завершается исключением. Выполнится ли код посл...

Представьте, что тест использует yield-фикстуру, а сам тест завершается исключением. Выполнится ли код после yield?

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

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

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

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

Фикстуры появились как способ отделить подготовку окружения теста от его проверки и централизовать освобождение ресурсов. Это уменьшает дублирование по сравнению с ручным setup-кодом в каждом тесте и делает жизненный цикл ресурса явным.

Конструкция с yield особенно удобна для ресурсов, у которых есть симметричные действия: открыть и закрыть файл, создать и удалить временные данные, начать и откатить транзакцию.

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

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

Неверно считать, что код после yield выполняется только при успешном тесте. Однако защита не абсолютна: принудительное завершение процесса, авария интерпретатора или остановка на уровне операционной системы не позволяют pytest запустить teardown.

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

При вызове yield-фикстуры pytest выполняет её тело до yield и сохраняет состояние генератора. Значение, переданное через yield, становится доступным тесту. После завершения теста pytest возобновляет генератор, поэтому выполняется код после yield независимо от результата теста.

import pytest events = [] @pytest.fixture def resource(): events.append('setup') yield 'value' events.append('teardown') def test_failure(resource): events.append('test') assert False

В этом примере после завершения теста с ошибкой в events попадёт и значение teardown. Само утверждение останется проваленным, но очистка всё равно будет предпринята.

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

Для нескольких фикстур pytest обычно выполняет их завершение в обратном порядке подготовки. Поэтому зависимый ресурс освобождается раньше ресурса, от которого он зависит. Область действия фикстуры (function, class, module, session) определяет, когда наступит её teardown, а не только частоту создания объекта.

Альтернатива — зарегистрировать очистку через request.addfinalizer. Она полезна, когда действие очистки нужно добавить условно или в несколько этапов, но требует аккуратно регистрировать finalizer только после успешного создания соответствующего ресурса. В простом случае yield обычно читается лучше.

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

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

Рассматривались три варианта. Ручной вызов отката в конце каждого теста дублирует код и пропускает очистку при исключении. Глобальное удаление всех тестовых данных после набора тестов быстрее, но ухудшает изоляцию и усложняет поиск источника загрязнения. Фикстура с yield локализует ресурс и гарантирует попытку отката после каждого теста.

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

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

  1. Что произойдёт, если ошибка возникла до yield?

Код после yield не будет выполнен, потому что фикстура не завершила фазу подготовки и не передала управление тесту. Поэтому ресурсы, созданные до места ошибки, нужно защищать отдельно: например, создавать их поэтапно с немедленной регистрацией очистки или использовать вложенный try/finally.

  1. Считается ли тест успешным, если проверка прошла, но teardown завершился с ошибкой?

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

  1. Гарантирует ли yield-фикстура очистку при любом завершении процесса?

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