isinstance и Mocking

class HelloWorld(object): def say_it(self): return 'Hello I am Hello World' def i_call_hello_world(hw_obj): print 'here... check type: %s' %type(HelloWorld) if isinstance(hw_obj, HelloWorld): print hw_obj.say_it() from mock import patch, MagicMock import unittest class TestInstance(unittest.TestCase): @patch('__main__.HelloWorld', spec=HelloWorld) def test_mock(self,MK): print type(MK) MK.say_it.return_value = 'I am fake' v = i_call_hello_world(MK) print v if __name__ == '__main__': c = HelloWorld() i_call_hello_world(c) print isinstance(c, HelloWorld) unittest.main() 

Вот трассировка

 here... check type: <type 'type'> Hello I am Hello World True <class 'mock.MagicMock'> here... check type: <class 'mock.MagicMock'> E ====================================================================== ERROR: test_mock (__main__.TestInstance) ---------------------------------------------------------------------- Traceback (most recent call last): File "/usr/local/lib/python2.7/dist-packages/mock.py", line 1224, in patched return func(*args, **keywargs) File "t.py", line 18, in test_mock v = i_call_hello_world(MK) File "t.py", line 7, in i_call_hello_world if isinstance(hw_obj, HelloWorld): TypeError: isinstance() arg 2 must be a class, type, or tuple of classes and types ---------------------------------------------------------------------- Ran 1 test in 0.002s 

Q1. Почему эта ошибка возникает? Они <class type='MagicMock>

Q2. Как приостановить насмешку, чтобы первая строка прошла, если ошибка исправлена?

Из документа

Обычно атрибут класса объекта возвращает его тип. Для макетного объекта с классом spec вместо этого возвращает класс spec. Это позволяет макетным объектам проходить тесты isststance для объекта, который они заменяют / маскируют как:

 mock = Mock(spec=3) isinstance(mock, int) True 

благодаря

4 Solutions collect form web for “isinstance и Mocking”

Не используйте isinstance , вместо этого проверяйте существование метода say_it . Если метод существует, назовите его:

 if hasattr(hw_obj, 'say_it'): print hw_obj.say_it() 

В любом случае, это лучший дизайн: использование информации о типе гораздо более хрупкое.

ИМХО, это хороший вопрос и говорит: « Не используйте isinstance , используйте вместо утиного набора текста » – это плохой ответ. Дайка печатается отлично, но не серебряная пуля. Иногда необходимо, даже если оно не является питоническим. Например, если вы работаете с какой-либо библиотекой или устаревшим кодом, который не является питоническим, вы должны играть с isinstance . Это просто реальный мир, и макет был разработан таким образом, чтобы соответствовать этой работе.

В коде большая ошибка – когда вы пишете:

 @patch('__main__.HelloWorld', spec=HelloWorld) def test_mock(self,MK): 

Из документации patch мы читаем (подчеркиваем, является моей):

Внутри тела функции или с выражением цель залатан новым объектом .

Это означает, что при установке объекта класса HelloWorld ссылка на HelloWorld будет заменена объектом MagicMock для контекста функции test_mock() .

Затем, когда i_call_hello_world() выполняется в if isinstance(hw_obj, HelloWorld): HelloWorld является MagicMock() а не классом (как показывает ошибка).

Это поведение связано с тем, что в качестве побочного эффекта исправления ссылки на класс второй isinstance(hw_obj, HelloWorld) становится объектом (экземпляр MagicMock ). Это не class или не type . Простым экспериментом для понимания этого поведения является изменение i_call_hello_world() следующим образом:

 HelloWorld_cache = HelloWorld def i_call_hello_world(hw_obj): print 'here... check type: %s' %type(HelloWorld_cache) if isinstance(hw_obj, HelloWorld_cache): print hw_obj.say_it() 

Ошибка исчезнет, ​​потому что исходная ссылка на класс HelloWorld сохраняется в HelloWorld_cache при загрузке модуля. Когда применяется патч, он изменит только HelloWorld а не HelloWorld_cache .

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

Хорошей новостью является то, что вы можете что-то сделать, но вы не можете просто patch ссылку HelloWord в модуле, где у вас есть isinstace(o,HelloWord) для тестирования. Лучший способ зависит от реального случая, который вы должны решить. В вашем примере вы можете просто создать Mock для использования в качестве объекта HelloWorld , использовать аргумент spec чтобы одеть его как экземпляр HelloWorld и передать тест isinstance . Это точно одна из целей, для которых разработана spec . Ваш тест будет написан следующим образом:

 def test_mock(self): MK = MagicMock(spec=HelloWorld) #The hw_obj passed to i_call_hello_world print type(MK) MK.say_it.return_value = 'I am fake' v = i_call_hello_world(MK) print v 

И выход только части unittest

 <class 'mock.MagicMock'> here... check type: <type 'type'> I am fake None 

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

 from mock import patch, mock class Foo(object): pass # Cache the Foo class so it will be available for isinstance assert. FooCache = Foo with patch('__main__.Foo', spec=Foo): foo = Foo() assert isinstance(foo, FooCache) assert isinstance(foo, mock.mock.NonCallableMagicMock) # This will cause error from question: # TypeError: isinstance() arg 2 must be a class, type, or tuple of classes and types assert isinstance(foo, Foo) 

Я занимался этим сам в последнее время, когда писал некоторые модульные тесты. Одно из потенциальных решений – фактически не пытаться издеваться над всем классом HelloWorld, а вместо этого выкрикивать методы класса, которые вызывается тестируемым кодом. Например, что-то вроде этого должно работать:

 class HelloWorld(object): def say_it(self): return 'Hello I am Hello World' def i_call_hello_world(hw_obj): if isinstance(hw_obj, HelloWorld): return hw_obj.say_it() from mock import patch, MagicMock import unittest class TestInstance(unittest.TestCase): @patch.object(HelloWorld, 'say_it') def test_mock(self, mocked_say_it): mocked_say_it.return_value = 'I am fake' v = i_call_hello_world(HelloWorld()) self.assertEquals(v, 'I am fake') 
  • Выделите класс в Python, чтобы вызвать один из его методов
  • как пропустить unittest case в python 2.6
  • Попытка реализовать python TestSuite
  • Как настроить обмен сообщениями и промежуточное ПО сеанса в Django RequestFactory во время модульного тестирования
  • Monkey-patch встроенная функция для единичного теста?
  • Как выполнить тестирование функции Python, которая рисует графику PDF?
  • Почему не assertRaises ловить мою ошибку атрибута с помощью python unittest?
  • Ошибка кэширования базы данных Django REST
  • Python - лучший язык программирования в мире.