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

Тест с фикстурой monkeypatch завершается исключением: останутся ли внесённые ею изменения после завершения ...

Тест с фикстурой monkeypatch завершается исключением: останутся ли внесённые ею изменения после завершения теста?

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

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

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

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

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

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

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

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

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

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

Фикстура monkeypatch регистрирует каждое изменение. После завершения теста pytest выполняет teardown фикстуры, а monkeypatch восстанавливает прежние значения или удаляет объекты, которых раньше не существовало.

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

import os def test_mode_is_temporary(monkeypatch): monkeypatch.setenv("APP_MODE", "test") assert os.environ["APP_MODE"] == "test" raise RuntimeError("ошибка теста")

После завершения этого теста переменная APP_MODE вернётся к прежнему состоянию либо будет удалена, если до теста её не было. Само исключение при этом не отменяется: monkeypatch отвечает за очистку, а не за обработку ошибки.

Гарантия действует только для изменений, сделанных через данный экземпляр monkeypatch. Если тест напрямую присвоил значение атрибуту или изменил словарь без monkeypatch, pytest этого изменения не знает и автоматически его не отменит.

Стандартная фикстура monkeypatch имеет область действия function. Поэтому её нельзя напрямую запросить из фикстуры с более широкой областью, например module или session: это приведёт к конфликту областей действия. Для более узкого участка кода можно использовать отдельный контекст MonkeyPatch.context(), явно ограничив время действия подмены.

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

Тест проверяет обработку режима обслуживания и временно задаёт переменную окружения. Внутри теста возникает исключение до ручного восстановления значения.

Рассматривались три варианта. Прямое присваивание с восстановлением в конце проще, но не очищается при исключении. Конструкция try/finally надёжнее, однако при большом количестве изменений становится многословной и легко допускает ошибку в сохранении исходного состояния. unittest.mock.patch хорошо подходит для объектов и вызовов, но не так удобно покрывает переменные окружения, каталоги и системный путь.

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

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

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

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

    Поэтому прямое присваивание нужно либо заменить на операцию monkeypatch, либо обернуть ручным try/finally. Смешивание двух подходов опасно: часть состояния будет очищаться автоматически, а часть останется на ответственности автора теста.

  2. Можно ли использовать стандартную фикстуру monkeypatch внутри session-scoped фикстуры?

    Ответ: напрямую нельзя. Стандартная фикстура monkeypatch имеет function scope, а фикстура с областью session живёт дольше. Запрос более узкой фикстуры из более широкой нарушает правила разрешения зависимостей pytest и приводит к ScopeMismatch.

    Если изменение действительно должно жить дольше одного теста, обычно лучше явно управлять им в session-scoped фикстуре с помощью объекта MonkeyPatch и гарантированно вызвать его откат в teardown. Однако широкая область увеличивает риск влияния на другие тесты, поэтому предпочтительнее сохранять область подмены минимальной.

  3. Что произойдёт с подменой, если тест запускает фоновый поток, который продолжает работу после его завершения?

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

    Поэтому фоновые задачи нужно явно остановить и дождаться их завершения до выхода из теста. Автоматический откат monkeypatch не синхронизирует потоки и не продлевает срок жизни подмены; он гарантирует только восстановление состояния в момент teardown.