补丁的位置
patch()
通过(临时性地)修改某一个对象的 名称 指向另一个对象来发挥作用。 可以有多个名称指向任意单独对象,因此要让补丁起作用你必须确保已为被测试的系统所使用的名称打上补丁。
基本原则是你要在对象 被查找 的地方打补丁,这不一定就是它被定义的地方。 一组示例将有助于厘清这一点。
想像我们有一个想要测试的具有如下结构的项目:
- a.py
- -> Defines SomeClass
- b.py
- -> from a import SomeClass
- -> some_function instantiates SomeClass
现在我们要测试 some_function
但我们想使用 patch()
来模拟 SomeClass
。 问题在于当我们导入模块 b 时,我们将必须让它从模块 a 导入 SomeClass
。 如果我们使用 patch()
来模拟 a.SomeClass
那么它将不会对我们的测试造成影响;模块 b 已经拥有对 真正的 SomeClass
的引用因此看上去我们的补丁不会有任何影响。
关键在于对 SomeClass
打补丁操作是在它被使用(或它被查找)的地方。 在此情况下实际上 some_function
将在模块 b 中查找 SomeClass
,而我们已经在那里导入了它。 补丁看上去应该是这样:
- @patch('b.SomeClass')
但是,再考虑另一个场景,其中不是 from a import SomeClass
而是模块 b 执行了 import a
并且 some_function
使用了 a.SomeClass
。 这两个导入形式都很常见。 在这种情况下我们要打补丁的类将在该模块中被查找因而我们必须改为对 a.SomeClass
打补丁:
- @patch('a.SomeClass')
对描述器和代理对象打补丁
patch 和 patch.object 都能正确地对描述器打补丁并恢复:包含类方法、静态方法和特征属性。 你应当在 类 而不是实例上为它们打补丁。 它们也适用于代理属性访问的 部分 对象,例如 django settings object [https://web.archive.org/web/20200603181648/http://www.voidspace.org.uk/python/weblog/arch_d7_2010_12_04.shtml#e1198]。
MagicMock 与魔术方法支持
模拟魔术方法
Mock
支持模拟 Python 协议方法,或称 "魔术方法"。 这允许 mock 对象替代容器或其他实现了 Python 协议的对象。
因为查找魔术方法的方式不同于普通方法 [2],这种支持采用了特别的实现。 这意味着只有特定的魔术方法受到支持。 受支持的是 几乎 所有魔术方法。 如果有你需要但被遗漏的请告知我们。
你可以通过在某个函数或 mock 实例中设置魔术方法来模拟它们。 如果你是使用函数则它 必须 接受 self
作为第一个参数 [3]。
- >>> def __str__(self):
- ... return 'fooble'
- ...
- >>> mock = Mock()
- >>> mock.__str__ = __str__
- >>> str(mock)
- 'fooble'
- >>> mock = Mock()
- >>> mock.__str__ = Mock()
- >>> mock.__str__.return_value = 'fooble'
- >>> str(mock)
- 'fooble'
- >>> mock = Mock()
- >>> mock.__iter__ = Mock(return_value=iter([]))
- >>> list(mock)
- []
一个这样的应用场景是在 with
语句中模拟作为上下文管理器的对象:
- >>> mock = Mock()
- >>> mock.__enter__ = Mock(return_value='foo')
- >>> mock.__exit__ = Mock(return_value=False)
- >>> with mock as m:
- ... assert m == 'foo'
- ...
- >>> mock.__enter__.assert_called_with()
- >>> mock.__exit__.assert_called_with(None, None, None)
对魔术方法的调用不会在 method_calls
中出现,但它们会被记录在 mock_calls
中。
备注
如果你使用 spec 关键字参数来创建 mock 那么尝试设置不包含在 spec 中的魔术方法将引发 AttributeError
。
受支持魔术方法的完整列表如下:
__hash__
,__sizeof__
,__repr__
和__str__
__dir__
,__format__
和__subclasses__
__round__
,__floor__
,__trunc__
和__ceil__
比较运算:
__lt__
,__gt__
,__le__
,__ge__
,__eq__
和__ne__
容器方法:
__getitem__
,__setitem__
,__delitem__
,__contains__
,__len__
,__iter__
,__reversed__
和__missing__
上下文管理器:
__enter__
,__exit__
,__aenter__
和__aexit__
单目数值运算方法:
__neg__
,__pos__
和__invert__
数值运算方法(包括右侧和原地两种形式):
__add__
,__sub__
,__mul__
,__matmul__
,__truediv__
,__floordiv__
,__mod__
,__divmod__
,__lshift__
,__rshift__
,__and__
,__xor__
,__or__
和__pow__
数值转换方法:
__complex__
,__int__
,__float__
和__index__
描述器方法:
_get_
,__set__
and__delete__
封存方法:
__reduce__
,__reduce_ex__
,__getinitargs__
,__getnewargs__
,__getstate__
和__setstate__
文件系统路径表示:
__fspath__
异步迭代方法:
__aiter__
and__anext__
在 3.8 版本发生变更: 增加了对 os.PathLike.__fspath__()
的支持。
在 3.8 版本发生变更: 增加了对 __aenter__
, __aexit__
, __aiter__
和 __anext__
的支持。
下列方法均存在但是 不受 支持,因为它们或者被 mock 所使用,或者无法动态设置,或者可能导致问题:
__getattr__
,__setattr__
,__init__
和__new__
__prepare__
,__instancecheck__
,__subclasscheck__
,__del__
MagicMock
存在两个版本的 MagicMock
: MagicMock
和 NonCallableMagicMock
.
- class unittest.mock.MagicMock(args, *kw)
MagicMock
是具有大部分 魔术方法 的默认实现的Mock
的子类。 你可以使用MagicMock
而无法自行配置这些魔术方法。
构造器形参的含义与 Mock
的相同。
如果你使用了 spec 或 spec_set 参数则将 只有 存在于 spec 中的魔术方法会被创建。
- class unittest.mock.NonCallableMagicMock(args, *kw)
MagicMock
的不可调用版本。
其构造器的形参具有与 MagicMock
相同的含义,区别在于 return_value 和 side_effect 在不可调用的 mock 上没有意义。
魔术方法是通过 MagicMock
对象来设置的,因此你可以用通常的方式来配置它们并使用它们:
- >>> mock = MagicMock()
- >>> mock[3] = 'fish'
- >>> mock.__setitem__.assert_called_with(3, 'fish')
- >>> mock.__getitem__.return_value = 'result'
- >>> mock[2]
- 'result'
在默认情况下许多协议方法都需要返回特定类型的对象。 这些方法都预先配置了默认的返回值,以便它们在你对返回值不感兴趣时可以不做任何事就能被使用。 如果你想要修改默认值则你仍然可以手动 设置 返回值。
方法及其默认返回值:
__lt__
:NotImplemented
__gt__
:NotImplemented
__le__
:NotImplemented
__ge__
:NotImplemented
__int__
:1
__contains__
:False
__len__
:0
__iter__
:iter([])
__exit__
:False
__aexit__
:False
__complex__
:1j
__float__
:1.0
__bool__
:True
__index__
:1
__hash__
: mock 的默认 hash__str__
: mock 的默认 str__sizeof__
: mock 的默认 sizeof
例如:
- >>> mock = MagicMock()
- >>> int(mock)
- 1
- >>> len(mock)
- 0
- >>> list(mock)
- []
- >>> object() in mock
- False
两个相等性方法 __eq__()
和 __ne__()
是特殊的。 它们会基于标识号进行默认的相等性比较,使用 side_effect
属性,除非你修改它们的返回值以返回其他内容:
- >>> MagicMock() == 3
- False
- >>> MagicMock() != 3
- True
- >>> mock = MagicMock()
- >>> mock.__eq__.return_value = True
- >>> mock == 3
- True
MagicMock.__iter__()
的返回值可以是任意可迭代对象而不要求必须是迭代器:
- >>> mock = MagicMock()
- >>> mock.__iter__.return_value = ['a', 'b', 'c']
- >>> list(mock)
- ['a', 'b', 'c']
- >>> list(mock)
- ['a', 'b', 'c']
如果返回值 是 迭代器,则对其执行一次迭代就会将它耗尽因而后续执行的迭代将会输出空列表:
- >>> mock.__iter__.return_value = iter(['a', 'b', 'c'])
- >>> list(mock)
- ['a', 'b', 'c']
- >>> list(mock)
- []
MagicMock
已配置了所有受支持的魔术方法,只有某些晦涩和过时的魔术方法是例外。 如果你需要仍然可以设置它们。
在 MagicMock
中受到支持但默认未被设置的魔术方法有:
__subclasses__
__dir__
__format__
_get_
,__set__
和__delete__
__reversed__
和__missing__
__reduce__
,__reduce_ex__
,__getinitargs__
,__getnewargs__
,__getstate__
和__setstate__
__getformat__
[2] 魔术方法 应当 是在类中而不是在实例中查找。 不同的 Python 版本对这个规则的应用并不一致。 受支持的协议方法应当适用于所有受支持的 Python 版本。
[3]
该函数基本上是与类挂钩的,但每个 Mock
实例都会与其他实例保持隔离。
辅助对象
sentinel
- unittest.mock.sentinel
sentinel
对象提供了一种为你的测试提供独特对象的便捷方式。
属性是在你通过名称访问它们时按需创建的。 访问相同的属性将始终返回相同的对象。 返回的对象会有一个合理的 repr 以使测试失败消息易于理解。
在 3.7 版本发生变更: 现在 sentinel
属性会在它们被 copy
或 pickle
时保存其标识。
在测试时你可能需要测试是否有一个特定的对象作为参数被传给了另一个方法,或是被其返回。 通常的做法是创建一个指定名称的 sentinel 对象来执行这种测试。 sentinel
提供了一种创建和测试此类对象的标识的便捷方式。
在这个示例中我们为 method
打上便捷补丁以返回 sentinel.some_object
:
- >>> real = ProductionClass()
- >>> real.method = Mock(name="method")
- >>> real.method.return_value = sentinel.some_object
- >>> result = real.method()
- >>> assert result is sentinel.some_object
- >>> result
- sentinel.some_object
DEFAULT
- unittest.mock.DEFAULT
DEFAULT
对象是一个预先创建的 sentinel (实际为sentinel.DEFAULT
)。 它可被side_effect
函数用来指明其应当使用正常的返回值。
call
- unittest.mock.call(args, *kwargs)
call()
是一个可创建更简单断言的辅助对象,用于同call_args
,call_args_list
,mock_calls
和method_calls
进行比较。call()
也可配合assert_has_calls()
使用。
- >>> m = MagicMock(return_value=None)
- >>> m(1, 2, a='foo', b='bar')
- >>> m()
- >>> m.call_args_list == [call(1, 2, a='foo', b='bar'), call()]
- True
- call.call_list()
- 对于代表多个调用的 call 对象,
call_list()
将返回一个包含所有中间调用以及最终调用的列表。
call_list
特别适用于创建针对“链式调用”的断言。 链式调用是指在一行代码中执行的多个调用。 这将使得一个 mock 的中存在多个条目 mock_calls
。 手动构造调用的序列将会很烦琐。
call_list()
可以根据同一个链式调用构造包含多个调用的序列:
- >>> m = MagicMock()
- >>> m(1).method(arg='foo').other('bar')(2.0)
- <MagicMock name='mock().method().other()()' id='...'>
- >>> kall = call(1).method(arg='foo').other('bar')(2.0)
- >>> kall.call_list()
- [call(1),
- call().method(arg='foo'),
- call().method().other('bar'),
- call().method().other()(2.0)]
- >>> m.mock_calls == kall.call_list()
- True
根据构造方式的不同,call
对象可以是一个 (位置参数, 关键字参数) 或 (名称, 位置参数, 关键字参数) 元组。 当你自行构造它们时这没有什么关系,但是 Mock.call_args
, Mock.call_args_list
和 Mock.mock_calls
属性当中的 call
对象可以被反查以获取它们所包含的单个参数。
Mock.call_args
和 Mock.call_args_list
中的 call
对象是 (位置参数, 关键字参数) 二元组而 Mock.mock_calls
中以及你自行构造的 call
对象则是 (名称, 位置参数, 关键字参数) 三元组。
你可以使用它们作为“元组”的特性来为更复杂的内省和断言功能获取单个参数。 位置参数是一个元组(如无位置参数则为空元组)而关键字参数是一个字典:
- >>> m = MagicMock(return_value=None)
- >>> m(1, 2, 3, arg='one', arg2='two')
- >>> kall = m.call_args
- >>> kall.args
- (1, 2, 3)
- >>> kall.kwargs
- {'arg': 'one', 'arg2': 'two'}
- >>> kall.args is kall[0]
- True
- >>> kall.kwargs is kall[1]
- True
- >>> m = MagicMock()
- >>> m.foo(4, 5, 6, arg='two', arg2='three')
- <MagicMock name='mock.foo()' id='...'>
- >>> kall = m.mock_calls[0]
- >>> name, args, kwargs = kall
- >>> name
- 'foo'
- >>> args
- (4, 5, 6)
- >>> kwargs
- {'arg': 'two', 'arg2': 'three'}
- >>> name is m.mock_calls[0][0]
- True
create_autospec
- unittest.mock.create_autospec(spec, spec_set=False, instance=False, **kwargs)
- 使用另一对象作为 spec 来创建 mock 对象。 mock 的属性将使用 spec 对象上的对应属性作为其 spec。
被模拟的函数或方法的参数将会被检查以确保它们是附带了正确的签名被调用的。
如果 spec_set 为 True
则尝试设置不存在于 spec 对象中的属性将引发 AttributeError
。
如果将类用作 spec 则 mock(该类的实例)的返回值将为这个 spec。 你可以通过传入 instance=True
来将某个类用作一个实例对象的 spec。 被返回的 mock 将仅在该 mock 的实例为可调用对象时才会是可调用对象。
create_autospec()
也接受被传入所创建 mock 的构造器的任意关键字参数。
请参阅 自动 spec 来了解如何通过 create_autospec()
以及 patch()
的 autospec 参数来自动设置 spec。
在 3.8 版本发生变更: 如果目标为异步函数那么 create_autospec()
现在将返回一个 AsyncMock
。
ANY
- unittest.mock.ANY
有时你可能需要设置关于要模拟的调用中的 某些 参数的断言,但是又不想理会其他参数或者想要从 call_args
中单独拿出它们并针对它们设置更复杂的断言。
为了忽略某些参数你可以传入与 任意对象 相等的对象。 这样再调用 assert_called_with()
和 assert_called_once_with()
时无论传入什么参数都将执行成功。
- >>> mock = Mock(return_value=None)
- >>> mock('foo', bar=object())
- >>> mock.assert_called_once_with('foo', bar=ANY)
ANY
也可被用在与 mock_calls
这样的调用列表相比较的场合:
- >>> m = MagicMock(return_value=None)
- >>> m(1)
- >>> m(1, 2)
- >>> m(object())
- >>> m.mock_calls == [call(1), call(1, 2), ANY]
- True
ANY
并不仅限于同调用对象的比较而是也可被用于测试断言:
- class TestStringMethods(unittest.TestCase):
- def test_split(self):
- s = 'hello world'
- self.assertEqual(s.split(), ['hello', ANY])
FILTER_DIR
- unittest.mock.FILTER_DIR
FILTER_DIR
是一个控制 mock 对象响应 dir()
的方式的模块组变量。 默认值为 True
,这将使用下文所描述的过滤操作,以便只显示有用的成员。 如果你不喜欢这样的过滤,或是出于诊断目的需要将其关闭,则应设置 mock.FILTER_DIR = False
。
当启用过滤时,dir(some_mock)
将只显示有用的属性并将包括正常情况下不会被显示的任何动态创建的属性。 如果 mock 是使用 spec (当然也可以是 autospec) 创建的则原有的所有属性都将被显示,即使它们还未被访问过:
- >>> dir(Mock())
- ['assert_any_call',
- 'assert_called',
- 'assert_called_once',
- 'assert_called_once_with',
- 'assert_called_with',
- 'assert_has_calls',
- 'assert_not_called',
- 'attach_mock',
- ...
- >>> from urllib import request
- >>> dir(Mock(spec=request))
- ['AbstractBasicAuthHandler',
- 'AbstractDigestAuthHandler',
- 'AbstractHTTPHandler',
- 'BaseHandler',
- ...
许多不太有用的(是 Mock
的而不是被模拟对象的私有成员)开头带有下划线和双下划线的属性已从在 Mock
上对 dir()
的调用的结果中被过滤。 如果你不喜欢此行为你可以通过设置模块级开关 FILTER_DIR
来将其关闭:
- >>> from unittest import mock
- >>> mock.FILTER_DIR = False
- >>> dir(mock.Mock())
- ['NonCallableMockgetreturn_value',
- 'NonCallableMockgetside_effect',
- 'NonCallableMock_return_value_doc',
- 'NonCallableMock_set_return_value',
- 'NonCallableMock_set_side_effect',
- '__call__',
- '__class__',
- ...
或者你也可以直接使用 vars(my_mock)
(实例成员) 和 dir(type(my_mock))
(类型成员) 来绕过不考虑 FILTER_DIR
的过滤。
mock_open
- unittest.mock.mock_open(mock=None, read_data=None)
- 创建 mock 来代替使用
open()
的辅助函数。 它对于open()
被直接调用或被用作上下文管理器的情况都适用。
mock 参数是要配置的 mock 对象。 如为 None
(默认值) 则将为你创建一个 MagicMock
,其 API 会被限制为可用于标准文件处理的方法或属性。
read_data 是供文件句柄的 read()
, readline()
和 readlines()
方法返回的字符串。 调用这些方法将会从 read_data 获取数据直到它被耗尽。 对这些方法的模拟是相当简化的:每次 mock 被调用时,read_data 都将从头开始。 如果你需要对你提供给测试代码的数据有更多控制那么你将需要自行定制这个 mock。 如果这还不够用,那么通过一些 PyPI [https://pypi.org] 上的内存文件系统包可以提供更真实的测试用文件系统。
在 3.4 版本发生变更: 增加了对 readline()
和 readlines()
的支持。 对 read()
的模拟被改为消耗 read_data 而不是每次调用时返回它。
在 3.5 版本发生变更: read_data 现在会在每次 mock 的调用时重置。
在 3.8 版本发生变更: 为实现增加了 __iter__()
以便迭代操作(例如在 for 循环中)能正确地使用 read_data。
将 open()
用作上下文管理器一确保你的文件处理被正确关闭的最佳方式因而十分常用:
- with open('somepath', 'w') as f:
- f.write('something')
这里的问题在于即使你模拟了对 open()
的调用但被用作上下文管理器(并调用其 __enter__()
和 __exit__()
方法)的却是 被返回的对象。
通过 MagicMock
来模拟上下文管理器是十分常见且十分麻烦的因此需要使用一个辅助函数。
- >>> m = mock_open()
- >>> with patch('__main__.open', m):
- ... with open('foo', 'w') as h:
- ... h.write('some stuff')
- ...
- >>> m.mock_calls
- [call('foo', 'w'),
- call().__enter__(),
- call().write('some stuff'),
- call().__exit__(None, None, None)]
- >>> m.assert_called_once_with('foo', 'w')
- >>> handle = m()
- >>> handle.write.assert_called_once_with('some stuff')
以及针对读取文件:
- >>> with patch('__main__.open', mock_open(read_data='bibble')) as m:
- ... with open('foo') as h:
- ... result = h.read()
- ...
- >>> m.assert_called_once_with('foo')
- >>> assert result == 'bibble'
自动 spec
自动 spec 是基于 mock 现有的 spec
特性。 它将 mock 的 api 限制为原始对象(spec)的 api,但它是递归(惰性实现)的因而 mock 的属性只有与 spec 的属性相同的 api。 除此之外被模拟的函数 / 方法具有与原对应物相同的调用签名因此如果它们被不正确地调用时会引发 TypeError
。
在我开始解释自动 spec 如何运作之前,先说明一下它的必要性。
Mock
是个非常强大和灵活的对象,但对 mock 操作来说有一个普遍存在的缺陷。 如果你重构了你的部分代码,如修改成员的名称等,则针对仍然使用 旧 API 但其使用的是 mock 而非真实对象的代码的测试仍将通过。 这意味着即使你的代码已被破坏你的测试却仍可能全部通过。
在 3.5 版本发生变更: 在 3.5 之前,在词断言中带有拼写错误应当引发错误的测试将会静默地通过。 你仍然可通过向 Mock 传入 unsafe=True
来实现此行为。
请注意这是为什么你在单元测试之外还需要集成测试的原因之一。 孤立地测试每个部分时全都正常而顺滑,但是如果你没有测试你的各个单元“联成一体”的情况如何那么就仍然存在测试可以发现大量错误的空间。
unittest.mock
已经提供了一个对此有帮助的特性,称为 spec 控制。 如果你使用类或实例作为一个 mock 的 spec
那么你将仅能访问 mock 中只存在于实际的类中的属性:
- >>> from urllib import request
- >>> mock = Mock(spec=request.Request)
- >>> mock.assret_called_with # Intentional typo!
- Traceback (most recent call last): ...
- AttributeError: Mock object has no attribute 'assret_called_with'
这个 spec 仅会应用于 mock 本身,因此对于 mock 中的任意方法我们仍然面临相同的问题:
- >>> mock.has_data()
- <mock.Mock object at 0x...>
- >>> mock.has_data.assret_called_with() # 故意的拼写错误!
自动 spec 解决了这个问题。 你可以将 autospec=True
传给 patch()
/ patch.object()
或是使用 create_autospec()
函数来创建带有 spec 的 mock。 如果你是将 autospec=True
参数传给 patch()
那么被替代的那个对象将被用作 spec 对象。 因为 spec 控制是“惰性地”执行的(spec 在 mock 中的属性被访问时才会被创建)所以即使是非常复杂或深度嵌套的对象(例如需要导入本身已导入了多个模块的模块)你也可以使用它而不会有太大的性能损失。
以下是一个实际应用的示例:
- >>> from urllib import request
- >>> patcher = patch('__main__.request', autospec=True)
- >>> mock_request = patcher.start()
- >>> request is mock_request
- True
- >>> mock_request.Request
- <MagicMock name='request.Request' spec='Request' id='...'>
你可以看到 request.Request
有一个 spec。 request.Request
构造器接受两个参数 (其中一个是 self)。 如果我们尝试不正确地调用它就会是这样的结果:
- >>> req = request.Request()
- Traceback (most recent call last): ...
- TypeError: <lambda>() takes at least 2 arguments (1 given)
该 spec 也将应用于被实例化的类(即附带i.e. the return value of spec 的 mock 的返回值):
- >>> req = request.Request('foo')
- >>> req
- <NonCallableMagicMock name='request.Request()' spec='Request' id='...'>
Request
对象不是可调用对象,因此实例化我们的被模拟 request.Request
的返回值是一个不可调用的 mock。 有了这个 spec 我们的断言中出现的任何拼写问题都将引发正确的报错:
- >>> req.add_header('spam', 'eggs')
- <MagicMock name='request.Request().add_header()' id='...'>
- >>> req.add_header.assret_called_with # 故意的拼写错误!
- Traceback (most recent call last): ...
- AttributeError: Mock object has no attribute 'assret_called_with'
- >>> req.add_header.assert_called_with('spam', 'eggs')
在许多情况下你将只需将 autospec=True
添加到你现有的 patch()
调用中即可防止拼写错误和 api 变化所导致的问题。
除了通过 patch()
来使用 autospec 还有一个 create_autospec()
可以直接创建带有自动 spec 的 mock:
- >>> from urllib import request
- >>> mock_request = create_autospec(request)
- >>> mock_request.Request('foo', 'bar')
- <NonCallableMagicMock name='mock.Request()' spec='Request' id='...'>
不过这并非没有缺点和限制,这也就是为什么它不是默认行为。 为了知道在 spec 对象上有哪些属性是可用的,autospec 必须对 spec 进行自省(访问其属性)。 当你遍历 mock 上的属性时在原始对象上的对应遍历也将在底层进行。 如果你的任何带 spec 的对象具有可触发代码执行的特征属性或描述器则你可能会无法使用 autospec。 在另一方面更好的的做法是将你的对象设计为可以安全地执行自省 [4]。
一个更严重的问题在于实例属性通常是在 __init__()
方法中被创建而在类中完全不存在。 autospec 无法获取动态创建的属性而使得 api 被限制于可见的属性。
- >>> class Something:
- ... def __init__(self):
- ... self.a = 33
- ...
- >>> with patch('__main__.Something', autospec=True):
- ... thing = Something()
- ... thing.a
- ...
- Traceback (most recent call last): ...
- AttributeError: Mock object has no attribute 'a'
要解决这个问题有几种不同的方式。 最容易但多少有些烦扰的方式是简单地在 mock 创建完成后再设置所需的属性。 Just because autospec 只是不允许你获取不存在于 spec 上的属性但并不会阻止你设置它们:
- >>> with patch('__main__.Something', autospec=True):
- ... thing = Something()
- ... thing.a = 33
- ...
spec 和 autospec 都有更严格的版本 确实能 阻止你设置不存在的属性。 这在你希望确保你的代码只能 设置 有效的属性时也很有用,但显然它会阻止下面这个特定的应用场景:
- >>> with patch('__main__.Something', autospec=True, spec_set=True):
- ... thing = Something()
- ... thing.a = 33
- ...
- Traceback (most recent call last): ...
- AttributeError: Mock object has no attribute 'a'
解决此问题的最好方式可能是添加类属性作为在 __init__()
中初始化的实例属性的默认值。 请注意如果你只在. Note that if you are only setting default attributes in __init__()
中设置默认属性那么通过类属性来提供它们(当然会在实例之间共享)也将有更快的速度。 例如
- class Something:
- a = 33
这带来了另一个问题。 为今后将变为不同类型对象的那些成员提供默认值 None
是比较常见的做法。 None
作为 spec 是没有用处的,因为它会使你无法访问 any 任何属性或方法。 由于 None
作为 spec 将 永远不会 有任何用处,并且有可能要指定某个通常为其他类型的成员,因此 autospec 不会为被设为 None
的成员使用 spec。 它们将为普通的 mock (嗯 —— 应为 MagicMocks):
- >>> class Something:
- ... member = None
- ...
- >>> mock = create_autospec(Something)
- >>> mock.member.foo.bar.baz()
- <MagicMock name='mock.member.foo.bar.baz()' id='...'>
如果你不喜欢修改你的生产类来添加默认值那么还有其他的选项。 其中之一是简单地使用一个实例而非类作为 spec。 另一选项则是创建一个生产类的子类并向该子类添加默认值而不影响到生产类。 这两个选项都需要你使用一个替代对象作为 spec。 值得庆幸的是 patch()
支持这样做 —— 你可以简单地传入替代对象作为 autospec 参数:
- >>> class Something:
- ... def __init__(self):
- ... self.a = 33
- ...
- >>> class SomethingForTest(Something):
- ... a = 33
- ...
- >>> p = patch('__main__.Something', autospec=SomethingForTest)
- >>> mock = p.start()
- >>> mock.a
- <NonCallableMagicMock name='Something.a' spec='int' id='...'>
[4]
这仅适用于类或已实例化的对象。 调用一个被模拟的类来创建一个 mock 实例 不会 创建真的实例。 只有属性查找 —— 以及对 dir()
的调用 —— 会被执行。
将 mock 封包
- unittest.mock.seal(mock)
- 封包将在访问被封包的 mock 的属性或其任何已经被递归地模拟的属性时禁止自动创建 mock。
如果一个带有名称或 spec 的 mock 实例被分配给一个属性则将不会在封包链中处理它。 这可以防止人们对 mock 对象的固定部分执行封包。
- >>> mock = Mock()
- >>> mock.submock.attribute1 = 2
- >>> mock.not_submock = mock.Mock(name="sample_name")
- >>> seal(mock)
- >>> mock.new_attribute # 这将引发 AttributeError。
- >>> mock.submock.attribute2 # 这将引发 AttributeError。
- >>> mock.not_submock.attribute2 # 这不会引发异常。
Added in version 3.7.
side_effect
, return_value
和 wraps 的优先顺序
它们的优先顺序是:
wraps
如果三个均已设置,模拟对象将返回来自 side_effect
的值,完全忽略 return_value
和被包装的对象。 如果设置任意两个,则具有更高优先级的那个将返回值。 无论设置的顺序是哪个在前,优先级顺序将保持不变。
- >>> from unittest.mock import Mock
- >>> class Order:
- ... @staticmethod
- ... def get_value():
- ... return "third"
- ...
- >>> order_mock = Mock(spec=Order, wraps=Order)
- >>> order_mock.get_value.side_effect = ["first"]
- >>> order_mock.get_value.return_value = "second"
- >>> order_mock.get_value()
- 'first'
由于 None
是 side_effect
的默认值,如果你将其值重新赋为 None
,则优先级顺序将在 return_value
和被包装的对象之间进行检查,并忽略 side_effect
。
- >>> order_mock.get_value.side_effect = None
- >>> order_mock.get_value()
- 'second'
如果 side_effect
所返回的值为 DEFAULT
,它将被忽略并且优先级顺序将移至后继者来获取要返回的值。
- >>> from unittest.mock import DEFAULT
- >>> order_mock.get_value.side_effect = [DEFAULT]
- >>> order_mock.get_value()
- 'second'
当 Mock
包装一个对象时,return_value
的默认值将为 DEFAULT
。
- >>> order_mock = Mock(spec=Order, wraps=Order)
- >>> order_mock.return_value
- sentinel.DEFAULT
- >>> order_mock.get_value.return_value
- sentinel.DEFAULT
优先级顺序将忽略该值并且它将移至末尾的后继者即被包装的对象。
由于真正调用的是被包装的对象,创建该模拟对象的实例将返回真正的该类实例。 被包装的对象所需要的任何位置参数都必须被传入。
- >>> order_mock_instance = order_mock()
- >>> isinstance(order_mock_instance, Order)
- True
- >>> order_mock_instance.get_value()
- 'third'
- >>> order_mock.get_value.return_value = DEFAULT
- >>> order_mock.get_value()
- 'third'
- >>> order_mock.get_value.return_value = "second"
- >>> order_mock.get_value()
- 'second'
但是如果你将其赋值为 None
,由于它是一个显式赋值所以不会被忽略。 因此,优先级顺序将不会移至被包装的对象。
- >>> order_mock.get_value.return_value = None
- >>> order_mock.get_value() is None
- True
即使你在初始化模拟对象时立即立即全部设置这三者,优先级顺序仍会保持原样:
- >>> order_mock = Mock(spec=Order, wraps=Order,
- ... **{"get_value.side_effect": ["first"],
- ... "get_value.return_value": "second"}
- ... )
- ...
- >>> order_mock.get_value()
- 'first'
- >>> order_mock.get_value.side_effect = None
- >>> order_mock.get_value()
- 'second'
- >>> order_mock.get_value.return_value = DEFAULT
- >>> order_mock.get_value()
- 'third'
如果 side_effect
已耗尽,优先级顺序将不会导致从后续者获取值。 而是会引发 StopIteration
异常。
- >>> order_mock = Mock(spec=Order, wraps=Order)
- >>> order_mock.get_value.side_effect = ["first side effect value",
- ... "another side effect value"]
- >>> order_mock.get_value.return_value = "second"
- >>> order_mock.get_value()
- 'first side effect value'
- >>> order_mock.get_value()
- 'another side effect value'
- >>> order_mock.get_value()
- Traceback (most recent call last): ...
- StopIteration