isinstance和Mocking

Cpp*_*ner 22 python unit-testing 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()
Run Code Online (Sandbox Code Playgroud)

这是追溯

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
Run Code Online (Sandbox Code Playgroud)

Q1.为什么抛出这个错误?他们是<class type='MagicMock>

Q2.如果错误得到修复,如何暂停模拟,以便第一行通过?

来自doc

通常,对象的class属性将返回其类型.对于具有spec 的模拟对象,请返回spec类.这允许模拟对象为它们替换/伪装的对象传递isinstance测试:

mock = Mock(spec=3)
isinstance(mock, int)
True
Run Code Online (Sandbox Code Playgroud)

谢谢

Mic*_*ico 41

恕我直言,这是一个很好的问题,并说" 不使用isinstance,改用鸭子打字 "是一个糟糕的答案.鸭子打字很棒,但不是银弹.有时isinstance是必要的,即使它不是pythonic.例如,如果您使用的是某些不是pythonic的库或遗留代码,则必须使用isinstance.它只是现实世界,而mock是为了适应这种工作而设计的.

在代码中,最大的错误是你写的时候:

@patch('__main__.HelloWorld', spec=HelloWorld)
def test_mock(self,MK):
Run Code Online (Sandbox Code Playgroud)

从我们阅读的patch文档(强调是我的):

在函数体或with语句的主体内部,使用新对象修补目标.

这意味着当您修补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()
Run Code Online (Sandbox Code Playgroud)

错误将消失,因为加载模块时HelloWorld会保存对类的原始引用HelloWorld_cache.当应用补丁时,它会改变HelloWorld而不是HelloWorld_cache.

不幸的是,之前的实验并没有给我们任何方式来处理像你这样的案例,因为你不能改变库或遗留代码来引入像这样的技巧.而且,这些是我们希望在代码中看不到的那种技巧.

好消息是,你可以做一些事情,但你不能随便patchHelloWord,你必须在模块中引用 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
Run Code Online (Sandbox Code Playgroud)

而单位测试部分的输出是

<class 'mock.MagicMock'>
here... check type: <type 'type'>
I am fake
None
Run Code Online (Sandbox Code Playgroud)


kle*_*ell 9

Michele d'Amico 在我看来提供了正确答案,我强烈建议您阅读.但是我花了一段时间才知道,并且,我相信我将来会回到这个问题,我认为一个最小的代码示例将有助于澄清解决方案并提供快速参考:

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)
Run Code Online (Sandbox Code Playgroud)


Ned*_*der -7

不要使用isinstance,而是检查该say_it方法是否存在。如果该方法存在,则调用它:

if hasattr(hw_obj, 'say_it'):
    print hw_obj.say_it()
Run Code Online (Sandbox Code Playgroud)

无论如何,这是一个更好的设计:依赖类型信息要脆弱得多。

  • 投反对票,建议不要使用“isinstance”是一个糟糕的答案。用 Mock 来做正确的事情,就像这样:http://stackoverflow.com/a/11283173/169153 (7认同)
  • 不要使用 `isinstance()` 对我来说不是一个解决方案:我尝试模拟 datetime.datetime 并且它在外部库中使用。就我而言,`django` 使用它。 (3认同)