Программирование PythonТестированиеPython-разработчик, отвечающий за unit-тестирование

В тесте нужно подменить отсутствующий атрибут конкретного объекта. Что произойдёт при использовании create=...

В тесте нужно подменить отсутствующий атрибут конкретного объекта. Что произойдёт при использовании 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")
Проходите собеседования с ИИ помощником Hintsage

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

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 восстанавливает исходное состояние: поскольку атрибута раньше не существовало, он удаляется.

from unittest.mock import patch class Service: def run(self): return self.send() service = Service() with patch.object(service, "send", return_value="fake", create=True) as send: assert service.run() == "fake" send.assert_called_once_with() assert not hasattr(service, "send")

create=True не добавляет атрибут в класс Service: подмена в примере установлена на конкретном экземпляре. Если подменять атрибут класса через patch.object(Service, ...), область изменения была бы другой, но правило временного восстановления осталось бы тем же.

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

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

Команда тестировала объект-адаптер, которому плагин во время инициализации динамически добавлял метод emit. В unit-тесте плагин не запускался, поэтому emit отсутствовал.

Вариант с ручным присваиванием service.emit = Mock() был простым, но не гарантировал автоматическое восстановление состояния. Добавление метода в основной класс сделало бы интерфейс явнее, однако изменило production-код ради теста.

Выбрали patch.object(service, "emit", create=True): подмена была локальной, автоматически удалялась после теста, а вызов можно было проверить через mock. При этом имя атрибута проверили отдельным интеграционным тестом, чтобы снизить риск скрытой опечатки.

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

  1. Что произойдёт без create=True?

patch.object по умолчанию требует, чтобы атрибут уже существовал. При отсутствии send он выбросит AttributeError при установке patch, поэтому тело with не начнёт выполняться. Это полезное поведение: оно обнаруживает ошибочное имя атрибута до запуска тестируемого кода.

  1. Удаляется ли созданный атрибут после исключения внутри with?

Да. Контекстный менеджер вызывает восстановление состояния при выходе как обычным путём, так и через исключение. Если атрибут был создан самим patch, он удаляется даже при падении теста; если атрибут существовал, ему возвращается прежнее значение.

  1. Почему create=True может скрыть ошибку в тесте?

Потому что библиотека не проверяет, был ли такой атрибут частью реального интерфейса объекта. Например, опечатка recieve создаст новый mock, и проверка вызова этого имени может пройти, хотя production-код использует receive. Поэтому create=True оправдан для доказанно динамических атрибутов, но не должен быть универсальным параметром всех patch-вызовов.