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。如果错误得到修复,如何暂停模拟以使第一行通过?
来自doc
通常,对象的类属性将返回其类型。 对于具有spec 类的模拟对象,将返回spec类 代替。这允许模拟对象通过isinstance测试 他们正在替换/伪装的对象:
mock = Mock(spec=3)
isinstance(mock, int)
True
由于
答案 0 :(得分:38)
恕我直言,这是一个很好的问题并且说&#34; 不要使用isinstance
,而是使用鸭子打字&#34;是一个糟糕的答案。鸭子打字很棒,但不是银弹。有时isinstance
是必要的,即使它不是pythonic。例如,如果您使用的是某些不是pythonic的库或遗留代码,则必须使用isinstance
。它只是现实世界,而mock是为了适应这种工作而设计的。
在代码中,最重要的错误是你写的时候:
@patch('__main__.HelloWorld', spec=HelloWorld)
def test_mock(self,MK):
从patch
documentation我们读到(强调是我的):
在函数体或with语句中,使用新的对象修补目标。
这意味着当您修补HelloWorld
类对象时,对HelloWorld
的引用将被MagicMock
对象替换为{{1}的上下文功能。
然后,在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
答案 1 :(得分:6)
Michele d'Amico在我的观点中提供了correct answer,我强烈建议您阅读它。但是我花了一段时间才知道,并且,我相信将来我会回到这个问题,我认为一个最小的代码示例将有助于澄清解决方案并提供快速参考:
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)
答案 2 :(得分:2)
您可以通过从MagicMock
类继承并覆盖__subclasscheck__
方法来执行此操作:
class BaseMagicMock(MagicMock):
def __subclasscheck__(self, subclass):
# I couldn't find another way to get the IDs
self_id = re.search("id='(.+?)'", self.__repr__()).group(1)
subclass_id = re.search("id='(.+?)'", subclass.__repr__()).group(1)
return self_id == subclass_id
然后你可以将这个类与@patch
装饰器一起使用:
class FooBarTestCase(TestCase):
...
@patch('app.services.ClassB', new_callable=BaseMagicMock)
@patch('app.services.ClassA', new_callable=BaseMagicMock)
def test_mock_for_issubclass_support(self, ClassAMock, ClassBMock):
check_for_subclasses(ClassAMock)
就是这样!
说明:
您必须模拟使用issubclass
进行比较的所有类。
示例:
def check_for_subclasses(class_1):
if issubclass(class_1, ClassA): # it's mocked above using BaseMagicMock
print("This is Class A")
if issubclass(class_1, ClassB): # it's mocked above using BaseMagicMock
print("This is Class B")
if issubclass(class_1, ClassC): # it's not mocked with @patch
print("This is Class C")
issubclass(class_1, ClassC)
会导致错误
{TypeError}issubclass() arg 1 must be a class
因为ClassC
包含默认的__issubclass__
方法。然后我们应该像这样处理测试:
class FooBarTestCase(TestCase):
...
@patch('app.services.ClassC', new_callable=BaseMagicMock)
@patch('app.services.ClassB', new_callable=BaseMagicMock)
@patch('app.services.ClassA', new_callable=BaseMagicMock)
def test_mock_for_issubclass_support(self, ClassAMock, ClassBMock):
check_for_subclasses(ClassAMock)
答案 3 :(得分:0)
我在编写一些单元测试时最近一直在与自己搏斗。一个可能的解决方案是不实际尝试模拟整个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')
答案 4 :(得分:0)
我认为使用freezegun是安全的。在那里进行了正确模拟datetime
模块的所有必要准备工作。另外,isinstance
检查对我而言不会失败。
它是这样的:
@freeze_time("2019-05-15")
def test_that_requires_frozen_time(): ...
答案 5 :(得分:0)
只需用以下方法修补isinstance方法:
@patch('__main__.isinstance', return_value=True)
因此,您将获得预期的行为和覆盖范围,可以始终断言已调用了模拟方法,请参见下面的测试用例示例:
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 unittest.mock import patch, MagicMock
import unittest
class TestInstance(unittest.TestCase):
@patch('__main__.isinstance', return_value=True)
def test_mock(self,MK):
print(type(MK))
MK.say_it.return_value = 'I am fake'
v = i_call_hello_world(MK)
print(v)
self.assertTrue(MK.say_it.called)
@patch('__main__.isinstance', return_value=False)
def test_not_call(self, MK):
print(type(MK))
MK.say_it.return_value = 'I am fake'
v = i_call_hello_world(MK)
print(v)
self.assertFalse(MK.say_it.called)
答案 6 :(得分:0)
isinstance
是一个内置函数,如 this answer 中所述,修补内置函数并不是一个好主意。为了让 isinstance
返回你想要的值,并避免这个错误:
TypeError: isinstance() arg 2 必须是一个类型或类型的元组
您可以在被测模块中修补 isinstance
。我还鼓励您在 patch
语句中使用 with
作为上下文管理器,如下所示:
from mock import patch
def test_your_test(self):
# your test set up
with patch('your_module.isinstance', return_value=True): # or False
# logic that uses isinstance
使用 patch
作为上下文管理器允许您仅在要模拟的特定函数/方法中进行模拟,而不是在整个测试中进行模拟。
答案 7 :(得分:-6)
请勿使用isinstance
,而是检查是否存在say_it
方法。如果该方法存在,请将其命名为:
if hasattr(hw_obj, 'say_it'):
print hw_obj.say_it()
无论如何,这是一个更好的设计:依赖类型信息会更加脆弱。