Программирование PythonТестированиеPython-разработчик серверных приложений

Как unittest.mock.patch.dict восстанавливает словарь после выхода из контекста, если тест изменил его содер...

Как unittest.mock.patch.dict восстанавливает словарь после выхода из контекста, если тест изменил его содержимое?

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

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

unittest.mock.patch.dict сохраняет исходное содержимое словаря, применяет временные изменения, а при завершении контекста очищает словарь и восстанавливает сохранённые пары ключ-значение. Восстановление выполняется даже если внутри контекста возникло исключение.

Это обычно поверхностное сохранение: сам словарь восстанавливается, но изменения во вложенных изменяемых объектах автоматически не откатываются.

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

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

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

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

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

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

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

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

При clear=True словарь сначала очищается, после чего в него помещаются переданные значения. Это не меняет принцип восстановления: после завершения контекста возвращается исходное содержимое.

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

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

Минимальный пример:

from unittest.mock import patch settings = {'mode': 'prod'} with patch.dict(settings, {'mode': 'test', 'debug': True}): assert settings == {'mode': 'test', 'debug': True} assert settings == {'mode': 'prod'}

Внутри контекста изменяется тот же объект settings. После выхода добавленный ключ удалён, а исходное значение mode восстановлено.

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

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

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

patch.dict выбран потому, что он изменяет исходный объект на время теста и гарантирует восстановление при исключении. Если обработчик содержит изменяемое внутреннее состояние, его создают заново для каждого теста или используют глубокое копирование; одного patch.dict для такой изоляции недостаточно.

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

  1. Вопрос: Что произойдёт с ключом, добавленным внутри patch.dict, после выхода из контекста?

    Ответ: Он будет удалён, потому что восстановление начинается с очистки текущего содержимого и затем возвращает сохранённое состояние. Это относится и к режиму clear=False, и к режиму clear=True.

  2. Вопрос: Откатит ли patch.dict изменение списка, который был значением восстановленного ключа?

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

  3. Вопрос: Что увидит функция, сохранившая ссылку на словарь до входа в patch.dict?

    Ответ: Тот же словарь с временным содержимым, потому что patch.dict изменяет объект на месте. После выхода из контекста эта ссылка снова увидит восстановленные данные. Это отличается от присваивания нового словаря имени, которое не изменило бы ранее сохранённые ссылки.