如何使用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}}的元类是否正确?
答案 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运行时已经进行了这些工作和测试。