补丁的位置

patch() 通过(临时性地)修改某一个对象的 名称 指向另一个对象来发挥作用。 可以有多个名称指向任意单独对象,因此要让补丁起作用你必须确保已为被测试的系统所使用的名称打上补丁。

基本原则是你要在对象 被查找 的地方打补丁,这不一定就是它被定义的地方。 一组示例将有助于厘清这一点。

想像我们有一个想要测试的具有如下结构的项目:

  1. a.py
  2. -> Defines SomeClass
  3.  
  4. b.py
  5. -> from a import SomeClass
  6. -> some_function instantiates SomeClass

现在我们要测试 some_function 但我们想使用 patch() 来模拟 SomeClass。 问题在于当我们导入模块 b 时,我们将必须让它从模块 a 导入 SomeClass。 如果我们使用 patch() 来模拟 a.SomeClass 那么它将不会对我们的测试造成影响;模块 b 已经拥有对 真正的 SomeClass 的引用因此看上去我们的补丁不会有任何影响。

关键在于对 SomeClass 打补丁操作是在它被使用(或它被查找)的地方。 在此情况下实际上 some_function 将在模块 b 中查找 SomeClass,而我们已经在那里导入了它。 补丁看上去应该是这样:

  1. @patch('b.SomeClass')

但是,再考虑另一个场景,其中不是 from a import SomeClass 而是模块 b 执行了 import a 并且 some_function 使用了 a.SomeClass。 这两个导入形式都很常见。 在这种情况下我们要打补丁的类将在该模块中被查找因而我们必须改为对 a.SomeClass 打补丁:

  1. @patch('a.SomeClass')

对描述器和代理对象打补丁

patchpatch.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]

  1. >>> def __str__(self):
  2. ... return 'fooble'
  3. ...
  4. >>> mock = Mock()
  5. >>> mock.__str__ = __str__
  6. >>> str(mock)
  7. 'fooble'
  1. >>> mock = Mock()
  2. >>> mock.__str__ = Mock()
  3. >>> mock.__str__.return_value = 'fooble'
  4. >>> str(mock)
  5. 'fooble'
  1. >>> mock = Mock()
  2. >>> mock.__iter__ = Mock(return_value=iter([]))
  3. >>> list(mock)
  4. []

一个这样的应用场景是在 with 语句中模拟作为上下文管理器的对象:

  1. >>> mock = Mock()
  2. >>> mock.__enter__ = Mock(return_value='foo')
  3. >>> mock.__exit__ = Mock(return_value=False)
  4. >>> with mock as m:
  5. ... assert m == 'foo'
  6. ...
  7. >>> mock.__enter__.assert_called_with()
  8. >>> 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: MagicMockNonCallableMagicMock.

  • 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 对象来设置的,因此你可以用通常的方式来配置它们并使用它们:

  1. >>> mock = MagicMock()
  2. >>> mock[3] = 'fish'
  3. >>> mock.__setitem__.assert_called_with(3, 'fish')
  4. >>> mock.__getitem__.return_value = 'result'
  5. >>> mock[2]
  6. '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

例如:

  1. >>> mock = MagicMock()
  2. >>> int(mock)
  3. 1
  4. >>> len(mock)
  5. 0
  6. >>> list(mock)
  7. []
  8. >>> object() in mock
  9. False

两个相等性方法 __eq__()__ne__() 是特殊的。 它们会基于标识号进行默认的相等性比较,使用 side_effect 属性,除非你修改它们的返回值以返回其他内容:

  1. >>> MagicMock() == 3
  2. False
  3. >>> MagicMock() != 3
  4. True
  5. >>> mock = MagicMock()
  6. >>> mock.__eq__.return_value = True
  7. >>> mock == 3
  8. True

MagicMock.__iter__() 的返回值可以是任意可迭代对象而不要求必须是迭代器:

  1. >>> mock = MagicMock()
  2. >>> mock.__iter__.return_value = ['a', 'b', 'c']
  3. >>> list(mock)
  4. ['a', 'b', 'c']
  5. >>> list(mock)
  6. ['a', 'b', 'c']

如果返回值 迭代器,则对其执行一次迭代就会将它耗尽因而后续执行的迭代将会输出空列表:

  1. >>> mock.__iter__.return_value = iter(['a', 'b', 'c'])
  2. >>> list(mock)
  3. ['a', 'b', 'c']
  4. >>> list(mock)
  5. []

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 属性会在它们被 copypickle 时保存其标识。

在测试时你可能需要测试是否有一个特定的对象作为参数被传给了另一个方法,或是被其返回。 通常的做法是创建一个指定名称的 sentinel 对象来执行这种测试。 sentinel 提供了一种创建和测试此类对象的标识的便捷方式。

在这个示例中我们为 method 打上便捷补丁以返回 sentinel.some_object:

  1. >>> real = ProductionClass()
  2. >>> real.method = Mock(name="method")
  3. >>> real.method.return_value = sentinel.some_object
  4. >>> result = real.method()
  5. >>> assert result is sentinel.some_object
  6. >>> result
  7. sentinel.some_object

DEFAULT

  • unittest.mock.DEFAULT
  • DEFAULT 对象是一个预先创建的 sentinel (实际为 sentinel.DEFAULT)。 它可被 side_effect 函数用来指明其应当使用正常的返回值。

call

  1. >>> m = MagicMock(return_value=None)
  2. >>> m(1, 2, a='foo', b='bar')
  3. >>> m()
  4. >>> m.call_args_list == [call(1, 2, a='foo', b='bar'), call()]
  5. True
  • call.call_list()
  • 对于代表多个调用的 call 对象,call_list() 将返回一个包含所有中间调用以及最终调用的列表。

call_list 特别适用于创建针对“链式调用”的断言。 链式调用是指在一行代码中执行的多个调用。 这将使得一个 mock 的中存在多个条目 mock_calls。 手动构造调用的序列将会很烦琐。

call_list() 可以根据同一个链式调用构造包含多个调用的序列:

  1. >>> m = MagicMock()
  2. >>> m(1).method(arg='foo').other('bar')(2.0)
  3. <MagicMock name='mock().method().other()()' id='...'>
  4. >>> kall = call(1).method(arg='foo').other('bar')(2.0)
  5. >>> kall.call_list()
  6. [call(1),
  7. call().method(arg='foo'),
  8. call().method().other('bar'),
  9. call().method().other()(2.0)]
  10. >>> m.mock_calls == kall.call_list()
  11. True

根据构造方式的不同,call 对象可以是一个 (位置参数, 关键字参数) 或 (名称, 位置参数, 关键字参数) 元组。 当你自行构造它们时这没有什么关系,但是 Mock.call_args, Mock.call_args_listMock.mock_calls 属性当中的 call 对象可以被反查以获取它们所包含的单个参数。

Mock.call_argsMock.call_args_list 中的 call 对象是 (位置参数, 关键字参数) 二元组而 Mock.mock_calls 中以及你自行构造的 call 对象则是 (名称, 位置参数, 关键字参数) 三元组。

你可以使用它们作为“元组”的特性来为更复杂的内省和断言功能获取单个参数。 位置参数是一个元组(如无位置参数则为空元组)而关键字参数是一个字典:

  1. >>> m = MagicMock(return_value=None)
  2. >>> m(1, 2, 3, arg='one', arg2='two')
  3. >>> kall = m.call_args
  4. >>> kall.args
  5. (1, 2, 3)
  6. >>> kall.kwargs
  7. {'arg': 'one', 'arg2': 'two'}
  8. >>> kall.args is kall[0]
  9. True
  10. >>> kall.kwargs is kall[1]
  11. True
  1. >>> m = MagicMock()
  2. >>> m.foo(4, 5, 6, arg='two', arg2='three')
  3. <MagicMock name='mock.foo()' id='...'>
  4. >>> kall = m.mock_calls[0]
  5. >>> name, args, kwargs = kall
  6. >>> name
  7. 'foo'
  8. >>> args
  9. (4, 5, 6)
  10. >>> kwargs
  11. {'arg': 'two', 'arg2': 'three'}
  12. >>> name is m.mock_calls[0][0]
  13. 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() 时无论传入什么参数都将执行成功。

  1. >>> mock = Mock(return_value=None)
  2. >>> mock('foo', bar=object())
  3. >>> mock.assert_called_once_with('foo', bar=ANY)

ANY 也可被用在与 mock_calls 这样的调用列表相比较的场合:

  1. >>> m = MagicMock(return_value=None)
  2. >>> m(1)
  3. >>> m(1, 2)
  4. >>> m(object())
  5. >>> m.mock_calls == [call(1), call(1, 2), ANY]
  6. True

ANY 并不仅限于同调用对象的比较而是也可被用于测试断言:

  1. class TestStringMethods(unittest.TestCase):
  2.  
  3. def test_split(self):
  4. s = 'hello world'
  5. 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) 创建的则原有的所有属性都将被显示,即使它们还未被访问过:

  1. >>> dir(Mock())
  2. ['assert_any_call',
  3. 'assert_called',
  4. 'assert_called_once',
  5. 'assert_called_once_with',
  6. 'assert_called_with',
  7. 'assert_has_calls',
  8. 'assert_not_called',
  9. 'attach_mock',
  10. ...
  11. >>> from urllib import request
  12. >>> dir(Mock(spec=request))
  13. ['AbstractBasicAuthHandler',
  14. 'AbstractDigestAuthHandler',
  15. 'AbstractHTTPHandler',
  16. 'BaseHandler',
  17. ...

许多不太有用的(是 Mock 的而不是被模拟对象的私有成员)开头带有下划线和双下划线的属性已从在 Mock 上对 dir() 的调用的结果中被过滤。 如果你不喜欢此行为你可以通过设置模块级开关 FILTER_DIR 来将其关闭:

  1. >>> from unittest import mock
  2. >>> mock.FILTER_DIR = False
  3. >>> dir(mock.Mock())
  4. ['NonCallableMockgetreturn_value',
  5. 'NonCallableMockgetside_effect',
  6. 'NonCallableMock_return_value_doc',
  7. 'NonCallableMock_set_return_value',
  8. 'NonCallableMock_set_side_effect',
  9. '__call__',
  10. '__class__',
  11. ...

或者你也可以直接使用 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() 用作上下文管理器一确保你的文件处理被正确关闭的最佳方式因而十分常用:

  1. with open('somepath', 'w') as f:
  2. f.write('something')

这里的问题在于即使你模拟了对 open() 的调用但被用作上下文管理器(并调用其 __enter__()__exit__() 方法)的却是 被返回的对象

通过 MagicMock 来模拟上下文管理器是十分常见且十分麻烦的因此需要使用一个辅助函数。

  1. >>> m = mock_open()
  2. >>> with patch('__main__.open', m):
  3. ... with open('foo', 'w') as h:
  4. ... h.write('some stuff')
  5. ...
  6. >>> m.mock_calls
  7. [call('foo', 'w'),
  8. call().__enter__(),
  9. call().write('some stuff'),
  10. call().__exit__(None, None, None)]
  11. >>> m.assert_called_once_with('foo', 'w')
  12. >>> handle = m()
  13. >>> handle.write.assert_called_once_with('some stuff')

以及针对读取文件:

  1. >>> with patch('__main__.open', mock_open(read_data='bibble')) as m:
  2. ... with open('foo') as h:
  3. ... result = h.read()
  4. ...
  5. >>> m.assert_called_once_with('foo')
  6. >>> 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 中只存在于实际的类中的属性:

  1. >>> from urllib import request
  2. >>> mock = Mock(spec=request.Request)
  3. >>> mock.assret_called_with # Intentional typo!
  4. Traceback (most recent call last): ...
  5. AttributeError: Mock object has no attribute 'assret_called_with'

这个 spec 仅会应用于 mock 本身,因此对于 mock 中的任意方法我们仍然面临相同的问题:

  1. >>> mock.has_data()
  2. <mock.Mock object at 0x...>
  3. >>> mock.has_data.assret_called_with() # 故意的拼写错误!

自动 spec 解决了这个问题。 你可以将 autospec=True 传给 patch() / patch.object() 或是使用 create_autospec() 函数来创建带有 spec 的 mock。 如果你是将 autospec=True 参数传给 patch() 那么被替代的那个对象将被用作 spec 对象。 因为 spec 控制是“惰性地”执行的(spec 在 mock 中的属性被访问时才会被创建)所以即使是非常复杂或深度嵌套的对象(例如需要导入本身已导入了多个模块的模块)你也可以使用它而不会有太大的性能损失。

以下是一个实际应用的示例:

  1. >>> from urllib import request
  2. >>> patcher = patch('__main__.request', autospec=True)
  3. >>> mock_request = patcher.start()
  4. >>> request is mock_request
  5. True
  6. >>> mock_request.Request
  7. <MagicMock name='request.Request' spec='Request' id='...'>

你可以看到 request.Request 有一个 spec。 request.Request 构造器接受两个参数 (其中一个是 self)。 如果我们尝试不正确地调用它就会是这样的结果:

  1. >>> req = request.Request()
  2. Traceback (most recent call last): ...
  3. TypeError: <lambda>() takes at least 2 arguments (1 given)

该 spec 也将应用于被实例化的类(即附带i.e. the return value of spec 的 mock 的返回值):

  1. >>> req = request.Request('foo')
  2. >>> req
  3. <NonCallableMagicMock name='request.Request()' spec='Request' id='...'>

Request 对象不是可调用对象,因此实例化我们的被模拟 request.Request 的返回值是一个不可调用的 mock。 有了这个 spec 我们的断言中出现的任何拼写问题都将引发正确的报错:

  1. >>> req.add_header('spam', 'eggs')
  2. <MagicMock name='request.Request().add_header()' id='...'>
  3. >>> req.add_header.assret_called_with # 故意的拼写错误!
  4. Traceback (most recent call last): ...
  5. AttributeError: Mock object has no attribute 'assret_called_with'
  6. >>> req.add_header.assert_called_with('spam', 'eggs')

在许多情况下你将只需将 autospec=True 添加到你现有的 patch() 调用中即可防止拼写错误和 api 变化所导致的问题。

除了通过 patch() 来使用 autospec 还有一个 create_autospec() 可以直接创建带有自动 spec 的 mock:

  1. >>> from urllib import request
  2. >>> mock_request = create_autospec(request)
  3. >>> mock_request.Request('foo', 'bar')
  4. <NonCallableMagicMock name='mock.Request()' spec='Request' id='...'>

不过这并非没有缺点和限制,这也就是为什么它不是默认行为。 为了知道在 spec 对象上有哪些属性是可用的,autospec 必须对 spec 进行自省(访问其属性)。 当你遍历 mock 上的属性时在原始对象上的对应遍历也将在底层进行。 如果你的任何带 spec 的对象具有可触发代码执行的特征属性或描述器则你可能会无法使用 autospec。 在另一方面更好的的做法是将你的对象设计为可以安全地执行自省 [4]

一个更严重的问题在于实例属性通常是在 __init__() 方法中被创建而在类中完全不存在。 autospec 无法获取动态创建的属性而使得 api 被限制于可见的属性。

  1. >>> class Something:
  2. ... def __init__(self):
  3. ... self.a = 33
  4. ...
  5. >>> with patch('__main__.Something', autospec=True):
  6. ... thing = Something()
  7. ... thing.a
  8. ...
  9. Traceback (most recent call last): ...
  10. AttributeError: Mock object has no attribute 'a'

要解决这个问题有几种不同的方式。 最容易但多少有些烦扰的方式是简单地在 mock 创建完成后再设置所需的属性。 Just because autospec 只是不允许你获取不存在于 spec 上的属性但并不会阻止你设置它们:

  1. >>> with patch('__main__.Something', autospec=True):
  2. ... thing = Something()
  3. ... thing.a = 33
  4. ...

specautospec 都有更严格的版本 确实能 阻止你设置不存在的属性。 这在你希望确保你的代码只能 设置 有效的属性时也很有用,但显然它会阻止下面这个特定的应用场景:

  1. >>> with patch('__main__.Something', autospec=True, spec_set=True):
  2. ... thing = Something()
  3. ... thing.a = 33
  4. ...
  5. Traceback (most recent call last): ...
  6. AttributeError: Mock object has no attribute 'a'

解决此问题的最好方式可能是添加类属性作为在 __init__() 中初始化的实例属性的默认值。 请注意如果你只在. Note that if you are only setting default attributes in __init__() 中设置默认属性那么通过类属性来提供它们(当然会在实例之间共享)也将有更快的速度。 例如

  1. class Something:
  2. a = 33

这带来了另一个问题。 为今后将变为不同类型对象的那些成员提供默认值 None 是比较常见的做法。 None 作为 spec 是没有用处的,因为它会使你无法访问 any 任何属性或方法。 由于 None 作为 spec 将 永远不会 有任何用处,并且有可能要指定某个通常为其他类型的成员,因此 autospec 不会为被设为 None 的成员使用 spec。 它们将为普通的 mock (嗯 —— 应为 MagicMocks):

  1. >>> class Something:
  2. ... member = None
  3. ...
  4. >>> mock = create_autospec(Something)
  5. >>> mock.member.foo.bar.baz()
  6. <MagicMock name='mock.member.foo.bar.baz()' id='...'>

如果你不喜欢修改你的生产类来添加默认值那么还有其他的选项。 其中之一是简单地使用一个实例而非类作为 spec。 另一选项则是创建一个生产类的子类并向该子类添加默认值而不影响到生产类。 这两个选项都需要你使用一个替代对象作为 spec。 值得庆幸的是 patch() 支持这样做 —— 你可以简单地传入替代对象作为 autospec 参数:

  1. >>> class Something:
  2. ... def __init__(self):
  3. ... self.a = 33
  4. ...
  5. >>> class SomethingForTest(Something):
  6. ... a = 33
  7. ...
  8. >>> p = patch('__main__.Something', autospec=SomethingForTest)
  9. >>> mock = p.start()
  10. >>> mock.a
  11. <NonCallableMagicMock name='Something.a' spec='int' id='...'>

[4] 这仅适用于类或已实例化的对象。 调用一个被模拟的类来创建一个 mock 实例 不会 创建真的实例。 只有属性查找 —— 以及对 dir() 的调用 —— 会被执行。

将 mock 封包

  • unittest.mock.seal(mock)
  • 封包将在访问被封包的 mock 的属性或其任何已经被递归地模拟的属性时禁止自动创建 mock。

如果一个带有名称或 spec 的 mock 实例被分配给一个属性则将不会在封包链中处理它。 这可以防止人们对 mock 对象的固定部分执行封包。

  1. >>> mock = Mock()
  2. >>> mock.submock.attribute1 = 2
  3. >>> mock.not_submock = mock.Mock(name="sample_name")
  4. >>> seal(mock)
  5. >>> mock.new_attribute # 这将引发 AttributeError。
  6. >>> mock.submock.attribute2 # 这将引发 AttributeError。
  7. >>> mock.not_submock.attribute2 # 这不会引发异常。

Added in version 3.7.

side_effect, return_valuewraps 的优先顺序

它们的优先顺序是:

如果三个均已设置,模拟对象将返回来自 side_effect 的值,完全忽略 return_value 和被包装的对象。 如果设置任意两个,则具有更高优先级的那个将返回值。 无论设置的顺序是哪个在前,优先级顺序将保持不变。

  1. >>> from unittest.mock import Mock
  2. >>> class Order:
  3. ... @staticmethod
  4. ... def get_value():
  5. ... return "third"
  6. ...
  7. >>> order_mock = Mock(spec=Order, wraps=Order)
  8. >>> order_mock.get_value.side_effect = ["first"]
  9. >>> order_mock.get_value.return_value = "second"
  10. >>> order_mock.get_value()
  11. 'first'

由于 Noneside_effect 的默认值,如果你将其值重新赋为 None,则优先级顺序将在 return_value 和被包装的对象之间进行检查,并忽略 side_effect

  1. >>> order_mock.get_value.side_effect = None
  2. >>> order_mock.get_value()
  3. 'second'

如果 side_effect 所返回的值为 DEFAULT,它将被忽略并且优先级顺序将移至后继者来获取要返回的值。

  1. >>> from unittest.mock import DEFAULT
  2. >>> order_mock.get_value.side_effect = [DEFAULT]
  3. >>> order_mock.get_value()
  4. 'second'

Mock 包装一个对象时,return_value 的默认值将为 DEFAULT

  1. >>> order_mock = Mock(spec=Order, wraps=Order)
  2. >>> order_mock.return_value
  3. sentinel.DEFAULT
  4. >>> order_mock.get_value.return_value
  5. sentinel.DEFAULT

优先级顺序将忽略该值并且它将移至末尾的后继者即被包装的对象。

由于真正调用的是被包装的对象,创建该模拟对象的实例将返回真正的该类实例。 被包装的对象所需要的任何位置参数都必须被传入。

  1. >>> order_mock_instance = order_mock()
  2. >>> isinstance(order_mock_instance, Order)
  3. True
  4. >>> order_mock_instance.get_value()
  5. 'third'
  1. >>> order_mock.get_value.return_value = DEFAULT
  2. >>> order_mock.get_value()
  3. 'third'
  1. >>> order_mock.get_value.return_value = "second"
  2. >>> order_mock.get_value()
  3. 'second'

但是如果你将其赋值为 None,由于它是一个显式赋值所以不会被忽略。 因此,优先级顺序将不会移至被包装的对象。

  1. >>> order_mock.get_value.return_value = None
  2. >>> order_mock.get_value() is None
  3. True

即使你在初始化模拟对象时立即立即全部设置这三者,优先级顺序仍会保持原样:

  1. >>> order_mock = Mock(spec=Order, wraps=Order,
  2. ... **{"get_value.side_effect": ["first"],
  3. ... "get_value.return_value": "second"}
  4. ... )
  5. ...
  6. >>> order_mock.get_value()
  7. 'first'
  8. >>> order_mock.get_value.side_effect = None
  9. >>> order_mock.get_value()
  10. 'second'
  11. >>> order_mock.get_value.return_value = DEFAULT
  12. >>> order_mock.get_value()
  13. 'third'

如果 side_effect 已耗尽,优先级顺序将不会导致从后续者获取值。 而是会引发 StopIteration 异常。

  1. >>> order_mock = Mock(spec=Order, wraps=Order)
  2. >>> order_mock.get_value.side_effect = ["first side effect value",
  3. ... "another side effect value"]
  4. >>> order_mock.get_value.return_value = "second"
  1. >>> order_mock.get_value()
  2. 'first side effect value'
  3. >>> order_mock.get_value()
  4. 'another side effect value'
  1. >>> order_mock.get_value()
  2. Traceback (most recent call last): ...
  3. StopIteration