替换类

时间:2017-03-12 12:49:45

标签: python mocking metaclass

如何使用unittest.mock.MagicMock实例覆盖Python类的元类

我有一个函数,其工作涉及使用参数的元类:

# lorem.py

class Foo(object):
    pass

def quux(existing_class):
    …
    metaclass = type(existing_class)
    new_class = metaclass(…)

此函数的单元测试需要断言调用 元类按预期运行,没有实际调用真正的类 对象

注意:测试用例不关心有关元类的行为;它关心quux检索该元类(使用type(existing_class))并使用正确的参数调用元类。

因此,要为此函数编写单元测试,我想传递一个类对象,其对象的元类是一个模拟对象。例如,这将允许对如何调用元类进行断言,并确保没有不必要的副作用。

# test_lorem.py

import unittest
import unittest.mock

import lorem

class stub_metaclass(type):
    def __new__(metaclass, name, bases, namespace):
        return super().__new__(metaclass, name, bases, namespace)

class quux_TestCase(unittest.TestCase):

    @unittest.mock.patch.object(
                lorem.Foo, '__class__', side_effect=stub_metaclass)
    def test_calls_expected_metaclass_with_class_name(
            self,
            mock_foo_metaclass,
            ):
        expected_name = 'Foo'
        expected_bases = …
        expected_namespace = …
        lorem.quux(lorem.Foo)
        mock_foo_metaclass.assert_called_with(
                expected_name, expected_bases, expected_namespace)

但是,当我尝试模拟现有类的__class__属性时,我收到此错误:

  File "/usr/lib/python3/dist-packages/mock/mock.py", line 1500, in start
    result = self.__enter__()
  File "/usr/lib/python3/dist-packages/mock/mock.py", line 1460, in __enter__
    setattr(self.target, self.attribute, new_attr)
TypeError: __class__ must be set to a class, not 'MagicMock' object

这告诉我unittest.mock.patch正在尝试将__class__属性临时设置为MagicMock实例,正如我想要的那样;但Python拒绝使用TypeError

将模拟对象作为元类放置正是我要做的事情:在unittest.mock.MagicMock属性中放置一个__class__实例,以便模拟对象将完成它所做的一切:记录调用,假装有效行为等。

如何设置模拟对象来代替Foo类的__class__属性,以便检测Foo并测试我的代码使用{ {1}}的元类是否正确?

1 个答案:

答案 0 :(得分:1)

你无法完全按照自己的意愿行事。正如您所看到的,对象的__class__属性在Python中非常特殊,即使对于普通实例,也会在运行时进行检查以验证它是否已分配给正确的类型。 当你进入一个班级__class__时,这就更严格了。

可能的方法:

要做的一件事是 not 将一个类传递给你的测试 - 但是一个对象是来自精心设计的普通类的实例,它将具有一个人为的__class__属性。即便是这样,您也必须将代码从调用type(existing_class)更改为直接执行existing_class.__class__。对于"伪造"的实例对象无论如何,你必须将__class__作为其类的属性实现(或覆盖__class__;(类本身将报告其真正的元类,但实例可以返回编码的任何内容在__getattribute__财产上。

__class__

实际建议:

但是,既然你是这样,也许最简单的事情就是模仿class Foo: @property def __class__(self): return stub_metaclass 而不是定义type的目标模块。

quux

还有另一种方法

可以做的另一件事是制作一个完整的" MockMetaclass"在#34;旧时尚":没有unittest.magicmock,相反,使用intrumented class MockType: def __init__(self): self.mock = mock.Mock() def __call__(self, *args): return self.mock ... class ...: ... def test_calls_expected_metaclass_with_class_name( self, ): try: new_type = MockType() # This creates "type" on the module "lorem" namespace # as a global variable. It will then override the built-in "type" lorem.type = new_type lorem.quux(lorem.Foo) finally: del lorem.type # un-shadows the built-in type on the module new_type.mock.assert_called_with( 'Foo', unittest.mock.ANY, unittest.mock.ANY) 和其他相关方法来记录被调用的参数,并作为你通过的类的真正元类运行在参数中。

关于正在做什么的考虑

到达此处的人请注意,不应该自己测试类创建(和元类)机制。可以假设Python运行时已经进行了这些工作和测试。