Как указать макет Python, который по умолчанию использует AttributeError() без явного присвоения?

#python #unit-testing #mocking #python-unittest

#питон #модульное тестирование #осмеяние #python-unittest

Вопрос:

У меня есть вопрос, связанный с Python unittest.mock.Mock и spec_set функциональными возможностями.

Моя цель-создать макет со следующими функциями:

  1. У него есть спецификация произвольного класса, который я решаю во время создания.
  2. Я должен иметь возможность назначать на макет только атрибуты или методы в соответствии со спецификацией пункта 1
  3. Макет должен вызывать ошибку атрибута в следующих ситуациях:
    • Я пытаюсь присвоить атрибут, которого нет в спецификации
    • Я вызываю или извлекаю свойство , которое либо отсутствует в spec_set , либо присутствует в spec_set , но назначено в соответствии с вышеуказанным пунктом.

Некоторые примеры поведения, которые я хотел бы:

 class MyClass:  property: int = 5  def func() -gt; int:  pass  # MySpecialMock is the Mock with the functionalities I am dreaming about :D mock = MyMySpecialMock(spec_set=MyClass)  mock.not_existing # Raise AttributeError mock.func() # Raise AttributeError mock.func = lambda: "it works" mock.func() # Returns "it works"  

Я перепробовал несколько решений, но безуспешно или не будучи явно многословным. Ниже приведены некоторые примеры:

  1. Использование Mock(spec_set=...) , но это не вызывает ошибок в случае, если я вызываю определенный атрибут, который я явно не устанавливал
  2. Использование Mock(spec_set=...) и явное переопределение каждого атрибута функцией с побочным эффектом исключения, но это довольно многословно, так как я должен повторить все атрибуты…

Моя цель-найти способ автоматизировать 2, но у меня нет чистого способа сделать это. Сталкивались ли вы когда-нибудь с такой проблемой и решали ли ее?

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

Заранее благодарю вас!

Ответ №1:

spec_set определяет mock объект, который совпадает с классом, но затем не позволяет вносить в него какие-либо изменения, поскольку он определяет специальные __getattr__ и __setattr__ . Это означает, что первый тест (вызов несуществующего attr) завершится неудачей, как и ожидалось, но затем будет предпринята попытка установить attr:

 from unitest import mock  class X:  pass  m = mock.Mock(spec_set=X) m.func() # __getattr__: AttributeError: Mock object has no attribute 'func' m.func = lambda: "it works" # __setattr__: AttributeError: Mock object has no attribute 'func'  

Вместо этого вы можете использовать create_autospec() , который копирует существующую функцию и добавляет к mock ней функции, но не влияет на __setattr__ :

 n = mock.create_autospec(X) n.func() # __getattr__: AttributeError: Mock object has no attribute 'func' n.func = lambda: "it works" n.func() # 'it works'  

Комментарии:

1. Спасибо вам за ответ! Боюсь, это будет создавать различное поведение, чем то, что я описал; моя цель в том, чтобы у этого сорта X , как: « класс X: деф кнопку func(): пройти « со следующим поведением: « mock_x.как func() # функцией getattr, : AttributeError: макет объект не имеет атрибута ‘Функ-Н.функции = лямбда: «это работает» Н.как func() # ‘работает’ « я к тому, что func , действительно, должно быть собственностью спецификаций, но getattr должны работать только после того, как setattr был призван.

Ответ №2:

Я думаю, что нашел удовлетворительный ответ на свою проблему, используя этот dir метод.

Чтобы создать макет с требованиями, которые я перечислил выше, должно быть достаточно выполнить следующее:

 def create_mock(spec: Any) -gt; Mock:  mock = Mock(spec_set=spec)   attributes_to_override = dir(spec)  for attr in filter(lambda name: not name.startswith("__"), attributes_to_override):  setattr(mock, attr, Mock(side_effect=AttributeError(f"{attr} not implemented")))   return mock