40
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
4

9 回答 9

64

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

在代码中,最大的错误是您编写时:

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

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

在函数体或 with 语句中,目标被一个新的object修补。

这意味着当您修补HelloWorld 类对象时,引用HelloWorld将被函数MagicMock上下文的对象替换test_mock()

然后, wheni_call_hello_world()被执行的if isinstance(hw_obj, HelloWorld): HelloWorld是一个MagicMock()对象而不是一个类(如错误所示)。

这种行为是因为作为修补类引用的副作用,第二个参数isinstance(hw_obj, HelloWorld)变成了一个对象(一个MagicMock实例)。这既不是 aclass也不是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

不幸的是,之前的实验没有给我们任何方法来处理像您这样的案例,因为您无法更改库或遗留代码来引入这样的技巧。此外,这些是我们不希望在代码中看到的技巧。

好消息是你可以做一些事情,但你不能只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

而只是 unittest 部分的输出是

<class 'mock.MagicMock'>
here... check type: <type 'type'>
I am fake
None
于 2014-10-25T22:08:20.813 回答
12

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)
于 2016-01-06T22:20:21.050 回答
4

您可以通过从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

    # def __instancecheck__(self, instance) for `isinstance`

然后你可以将这个类与@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)
于 2018-04-24T14:05:21.390 回答
1

只需使用以下方法修补 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)
于 2020-10-07T11:36:52.207 回答
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')
于 2015-02-04T23:03:23.663 回答
0

我认为使用freezegun是安全的。正确模拟模块的所有必要准备工作datetime都在那里完成。此外,isinstance检查对我来说并没有失败。

它是这样工作的:

@freeze_time("2019-05-15")
def test_that_requires_frozen_time(): ...
于 2019-11-19T10:34:33.700 回答
0

isinstance是一个内置函数,因此修补内置函数不是一个好主意,因为它在这个答案中有所解释。为了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允许您仅在要模拟它的特定函数/方法中进行模拟,而不是在整个测试中模拟它。

于 2021-05-21T04:32:44.690 回答
0

我想可能的解决方案可能是检查对象的子类。

issubclass(hw_obj.__class__, HelloWorld)

例子:

from unittest.mock import patch, MagicMock
import unittest


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) or issubclass(hw_obj.__class__, HelloWorld):
        print(hw_obj.say_it())


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)


if __name__ == '__main__':
    unittest.main()
于 2021-12-29T18:22:52.650 回答
-6

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

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

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

于 2012-06-21T21:09:55.847 回答