Разработчик создаёт Mock со спецификацией класса и затем присваивает ему новый атрибут. Какое ограничение даёт spec_set по сравнению с spec?
spec_set запрещает не только обращение к атрибутам, которых нет в спецификации, но и присваивание таких атрибутов. Обычный spec ограничивает чтение неизвестных атрибутов, однако позволяет случайно создать новый атрибут присваиванием.
Обычный Mock намеренно гибок: любой неизвестный атрибут может стать новым вложенным mock-объектом. Это удобно при быстром написании тестов, но опечатка в имени метода может незаметно превратить проверку реального поведения в проверку вымышленного интерфейса.
Параметры spec и spec_set появились как средства связать mock с интерфейсом реального объекта. Они помогают обнаруживать расхождение теста с кодом ещё во время выполнения теста, а не после изменения реализации.
Предположим, у класса есть метод send, но в тесте из-за опечатки создаётся атрибут senс с похожим написанием. Без ограничения mock может принять такую ошибку: тест настроит несуществующий атрибут и продолжит выполняться.
spec не полностью решает проблему, если тест присваивает атрибуту значение. Поэтому выбор между spec и spec_set влияет на строгость проверки интерфейса и на совместимость тестов с объектами, которые динамически расширяются.
При создании mock с spec набор допустимых атрибутов берётся из переданного объекта или класса. Попытка получить неизвестный атрибут завершается ошибкой AttributeError, но присваивание нового атрибута обычно разрешено.
spec_set использует тот же принцип проверки, но запрещает и чтение, и присваивание атрибутов, отсутствующих в спецификации. Поэтому он лучше защищает от опечаток при настройке mock-объекта.
В примере символ с в ошибочном имени визуально похож на латинскую букву, но это всё равно другой атрибут; spec_set обнаруживает такое присваивание. В реальном тесте следует использовать обычные имена без подобных визуальных замен — здесь это только наглядная демонстрация ошибки имени.
Спецификация проверяет наличие атрибутов, но не гарантирует корректность бизнес-логики и не заменяет проверку реального объекта. Кроме того, если передать класс, атрибуты, создаваемые только в __init__, могут отсутствовать в спецификации класса; для таких случаев иногда нужен экземпляр или другой способ формирования интерфейса.
spec_set также не является полной проверкой сигнатур вызовов. Для контроля аргументов вызовов обычно применяют autospec или create_autospec. Это отдельный механизм, который проверяет соответствие вызываемых методов сигнатурам исходного объекта.
Главный компромисс таков: строгий mock лучше выявляет ошибки в тесте, но требует поддерживать спецификацию в соответствии с публичным интерфейсом. Если код легально добавляет атрибуты динамически, чрезмерно строгая спецификация может сделать тест хрупким.
В тестах клиента отправки сообщений использовался обычный Mock. После переименования метода send в publish часть тестов продолжала проходить: настройка ошибочного атрибута создавала новый дочерний mock, поэтому тест фактически не проверял вызов клиента.
Рассматривались два варианта. spec ограничивал получение неизвестных атрибутов, но не защищал от ошибочных присваиваний при настройке; отказ от спецификации сохранял гибкость, однако оставлял риск тихих опечаток. В качестве решения выбрали spec_set для mock-объектов внешних клиентов, а для проверки сигнатур — autospec там, где это требовалось.
После этого переименование метода стало приводить к немедленной ошибке подготовки теста. Для объектов с действительно динамическими атрибутами строгую спецификацию не применяли автоматически, а явно фиксировали допустимый интерфейс в отдельном адаптере.
Нет. spec_set запрещает создавать или получать атрибуты, которых нет в спецификации, но не запрещает присваивать новое значение уже допустимому атрибуту. Например, существующий метод mock можно заменить другим mock-объектом или настроить через return_value.
Нет, его основная задача — контроль имён атрибутов. Наличие метода в спецификации ещё не означает, что mock проверит число и имена переданных аргументов. Для этого нужен autospec или создание mock с явно заданной сигнатурой; даже тогда проверяется форма вызова, а не корректность результата бизнес-операции.
При спецификации классом доступный набор строится по интерфейсу самого класса. Атрибут, который появляется только внутри конструктора экземпляра и не объявлен на уровне класса, может не попасть в этот набор. Если такой атрибут является частью допустимого интерфейса, спецификацию следует строить по экземпляру либо выбрать более явную границу тестирования, например mock адаптера с объявленными атрибутами.