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。 如果错误已修复,如何暂停模拟以便第一行通过?

文档

通常,对象的 __class__ 属性将返回其类型。对于具有规范的模拟对象, __class__ 返回规范类。这允许模拟对象通过 isinstance() 测试它们正在替换/伪装的对象:

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

原文由 CppLearner 发布,翻译遵循 CC BY-SA 4.0 许可协议

阅读 729
2 个回答

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

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

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

原文由 Ned Batchelder 发布,翻译遵循 CC BY-SA 3.0 许可协议

恕我直言,这是一个很好的问题,说“ _不要使用 isinstance ,而是使用鸭子打字_”是一个糟糕的答案。鸭子打字很棒,但不是灵丹妙药。有时 isinstance 是必要的,即使它不是 pythonic。例如,如果您使用一些非 pythonic 的库或遗留代码,则必须使用 isinstance 。这只是现实世界,而 mock 就是为适应这种工作而设计的。

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

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

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

在函数体内或 with 语句中,目标被修补为一个新 对象

That means when you patch the HelloWorld class object the reference to HelloWorld will be replaced by a MagicMock object for the context of the test_mock() 功能。

Then, when i_call_hello_world() is executed in if isinstance(hw_obj, HelloWorld): HelloWorld is a MagicMock() object and not a class (as the error suggests).

该行为是因为作为修补类引用的副作用, 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) 要测试的代码。最好的方法取决于您必须解决的真实案例。 In your example you can just create a Mock to use as HelloWorld object, use spec argument to dress it as HelloWorld instance and pass 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

单元测试部分的输出是

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

原文由 Michele d‘Amico 发布,翻译遵循 CC BY-SA 3.0 许可协议

撰写回答
你尚未登录,登录后可以
  • 和开发者交流问题的细节
  • 关注并接收问题和回答的更新提醒
  • 参与内容的编辑和改进,让解决方法与时俱进
推荐问题