В тесте нужно подменить отсутствующий атрибут конкретного объекта. Что произойдёт при использовании create=True?
from unittest.mock import patch
class Service:
def run(self):
return self.send()
service = Service()
def test_run():
with patch.object(service, "send", return_value="fake", create=True):
assert service.run() == "fake"
assert not hasattr(service, "send")
create=True разрешает patch.object создать отсутствующий атрибут на время действия контекстного менеджера. Внутри with вызов service.send() обращается к созданному mock-объекту, после выхода атрибут удаляется.
Без create=True тест завершился бы ошибкой AttributeError ещё при входе в patch.object, потому что send изначально отсутствует у объекта.
Обычная подмена через unittest.mock.patch рассчитана на уже существующие атрибуты: это помогает обнаруживать опечатки и несоответствие интерфейсу. Однако Python допускает динамическое добавление атрибутов, что используется в адаптерах, плагинах и объектах с динамическим API.
Параметр create=True предназначен для тестирования такого поведения. Он сознательно ослабляет проверку существования атрибута и потому должен применяться только там, где отсутствие атрибута является частью проверяемого сценария.
Если тестируемый код обращается к атрибуту, который появляется динамически, стандартный patch.object не сможет установить подмену. Это может затруднить тестирование объектов без заранее объявленного полного интерфейса.
Но бездумное использование create=True опасно: опечатка в имени атрибута тоже будет принята. Тест может пройти с подменой sennd, хотя production-код вызывает send, и реальная зависимость останется непроверенной.
При входе в with patch.object сохраняет исходное состояние объекта. Если атрибута не было и указан create=True, библиотека добавляет туда mock с заданным return_value.
В примере Service.run ищет send у экземпляра service. Поэтому во время контекста он находит созданный mock и возвращает строку fake. После выхода из контекста patch восстанавливает исходное состояние: поскольку атрибута раньше не существовало, он удаляется.
create=True не добавляет атрибут в класс Service: подмена в примере установлена на конкретном экземпляре. Если подменять атрибут класса через patch.object(Service, ...), область изменения была бы другой, но правило временного восстановления осталось бы тем же.
Этот параметр также не проверяет сигнатуру отсутствующего API. Если нужна защита от неправильных имён и совместимость с известным интерфейсом, лучше подменять существующий атрибут или использовать спецификацию mock там, где это возможно.
Команда тестировала объект-адаптер, которому плагин во время инициализации динамически добавлял метод emit. В unit-тесте плагин не запускался, поэтому emit отсутствовал.
Вариант с ручным присваиванием service.emit = Mock() был простым, но не гарантировал автоматическое восстановление состояния. Добавление метода в основной класс сделало бы интерфейс явнее, однако изменило production-код ради теста.
Выбрали patch.object(service, "emit", create=True): подмена была локальной, автоматически удалялась после теста, а вызов можно было проверить через mock. При этом имя атрибута проверили отдельным интеграционным тестом, чтобы снизить риск скрытой опечатки.
create=True?patch.object по умолчанию требует, чтобы атрибут уже существовал. При отсутствии send он выбросит AttributeError при установке patch, поэтому тело with не начнёт выполняться. Это полезное поведение: оно обнаруживает ошибочное имя атрибута до запуска тестируемого кода.
with?Да. Контекстный менеджер вызывает восстановление состояния при выходе как обычным путём, так и через исключение. Если атрибут был создан самим patch, он удаляется даже при падении теста; если атрибут существовал, ему возвращается прежнее значение.
create=True может скрыть ошибку в тесте?Потому что библиотека не проверяет, был ли такой атрибут частью реального интерфейса объекта. Например, опечатка recieve создаст новый mock, и проверка вызова этого имени может пройти, хотя production-код использует receive. Поэтому create=True оправдан для доказанно динамических атрибутов, но не должен быть универсальным параметром всех patch-вызовов.