26.5. unittest.mock — 模拟对象库 — Python 文档

来自菜鸟教程
Python/docs/3.6/library/unittest.mock
跳转至:导航、​搜索

26.5. 单元测试.mock — 模拟对象库

3.3 版中的新功能。


源代码: :source:`Lib/unittest/mock.py`



unittest.mock 是一个用于在 Python 中进行测试的库。 它允许您用模拟对象替换被测系统的某些部分,并对它们的使用方式做出断言。

unittest.mock 提供了一个核心 Mock 类,无需在整个测试套件中创建大量存根。 执行操作后,您可以断言使用了哪些方法/属性以及调用它们的参数。 您还可以以正常方式指定返回值并设置所需的属性。

此外,mock 提供了一个 patch() 装饰器,用于处理测试范围内的补丁模块和类级别属性,以及用于创建唯一对象的 sentinel。 有关如何使用 MockMagicMockpatch() 的一些示例,请参阅 快速指南

Mock 非常易于使用,专为与 unittest 一起使用而设计。 Mock 基于'action -> assertion' 模式,而不是许多模拟框架使用的'record -> replay'。

对于早期版本的 Python,有一个 unittest.mock 的向后移植,可用作 PyPI 上的 mock。

26.5.1. 快速指南

MockMagicMock 对象在您访问它们时创建所有属性和方法,并存储它们如何使用的详细信息。 您可以配置它们,以指定返回值或限制可用的属性,然后断言它们的使用方式:

>>> from unittest.mock import MagicMock
>>> thing = ProductionClass()
>>> thing.method = MagicMock(return_value=3)
>>> thing.method(3, 4, 5, key='value')
3
>>> thing.method.assert_called_with(3, 4, 5, key='value')

side_effect 允许您执行副作用,包括在调用模拟时引发异常:

>>> mock = Mock(side_effect=KeyError('foo'))
>>> mock()
Traceback (most recent call last):
 ...
KeyError: 'foo'
>>> values = {'a': 1, 'b': 2, 'c': 3}
>>> def side_effect(arg):
...     return values[arg]
...
>>> mock.side_effect = side_effect
>>> mock('a'), mock('b'), mock('c')
(1, 2, 3)
>>> mock.side_effect = [5, 4, 3, 2, 1]
>>> mock(), mock(), mock()
(5, 4, 3)

Mock 有许多其他方法可以配置它并控制其行为。 例如, spec 参数将模拟配置为从另一个对象获取其规范。 尝试访问规范中不存在的模拟上的属性或方法将失败,并显示 AttributeError

patch() 装饰器/上下文管理器可以轻松模拟被测模块中的类或对象。 您指定的对象将在测试期间替换为模拟(或其他对象)并在测试结束时恢复:

>>> from unittest.mock import patch
>>> @patch('module.ClassName2')
... @patch('module.ClassName1')
... def test(MockClass1, MockClass2):
...     module.ClassName1()
...     module.ClassName2()
...     assert MockClass1 is module.ClassName1
...     assert MockClass2 is module.ClassName2
...     assert MockClass1.called
...     assert MockClass2.called
...
>>> test()

笔记

当您嵌套补丁装饰器时,模拟以它们应用的相同顺序传递给装饰函数(应用装饰器的正常 python 顺序)。 这意味着自下而上,因此在上面的示例中,首先传入 module.ClassName1 的模拟。

使用 patch() 重要的是您在查找它们的命名空间中修补对象。 这通常很简单,但要获得快速指南,请阅读 在哪里打补丁


除了装饰器 patch() 也可以在 with 语句中用作上下文管理器:

>>> with patch.object(ProductionClass, 'method', return_value=None) as mock_method:
...     thing = ProductionClass()
...     thing.method(1, 2, 3)
...
>>> mock_method.assert_called_once_with(1, 2, 3)

还有 patch.dict() 用于在范围内设置字典中的值,并在测试结束时将字典恢复到其原始状态:

>>> foo = {'key': 'value'}
>>> original = foo.copy()
>>> with patch.dict(foo, {'newkey': 'newvalue'}, clear=True):
...     assert foo == {'newkey': 'newvalue'}
...
>>> assert foo == original

Mock 支持对 Python 魔术方法 的模拟。 使用魔术方法的最简单方法是使用 MagicMock 类。 它允许您执行以下操作:

>>> mock = MagicMock()
>>> mock.__str__.return_value = 'foobarbaz'
>>> str(mock)
'foobarbaz'
>>> mock.__str__.assert_called_with()

Mock 允许您将函数(或其他 Mock 实例)分配给魔术方法,它们将被适当地调用。 MagicMock 类只是一个 Mock 变体,它具有为您预先创建的所有魔术方法(好吧,无论如何都是有用的)。

以下是在普通 Mock 类中使用魔术方法的示例:

>>> mock = Mock()
>>> mock.__str__ = Mock(return_value='wheeeeee')
>>> str(mock)
'wheeeeee'

为确保测试中的模拟对象与它们正在替换的对象具有相同的 API,您可以使用 auto-speccing。 自动指定可以通过 autospec 参数补丁或 create_autospec() 函数来完成。 自动指定创建的模拟对象与它们正在替换的对象具有相同的属性和方法,并且任何函数和方法(包括构造函数)都具有与真实对象相同的调用签名。

这可确保您的模拟在使用不当时会以与生产代码相同的方式失败:

>>> from unittest.mock import create_autospec
>>> def function(a, b, c):
...     pass
...
>>> mock_function = create_autospec(function, return_value='fishy')
>>> mock_function(1, 2, 3)
'fishy'
>>> mock_function.assert_called_once_with(1, 2, 3)
>>> mock_function('wrong arguments')
Traceback (most recent call last):
 ...
TypeError: <lambda>() takes exactly 3 arguments (1 given)

create_autospec() 也可用于类,它复制 __init__ 方法的签名,以及可调用对象,它复制 __call__ 方法的签名。


26.5.2. 模拟课

Mock 是一个灵活的模拟对象,旨在取代整个代码中存根和测试替身的使用。 模拟是可调用的,并在您访问它们时将属性创建为新模拟 1。 访问相同的属性将始终返回相同的模拟。 Mocks 记录你如何使用它们,允许你断言你的代码对它们做了什么。

MagicMockMock 的子类,所有魔法方法都已预先创建并可以使用。 还有一些不可调用的变体,当你模拟不可调用的对象时很有用: NonCallableMockNonCallableMagicMock

patch() 装饰器可以很容易地用 Mock 对象临时替换特定模块中的类。 默认情况下,patch() 会为你创建一个 MagicMock。 您可以使用 patch()new_callable 参数指定 Mock 的替代类。

class unittest.mock.Mock(spec=None, side_effect=None, return_value=DEFAULT, wraps=None, name=None, spec_set=None, unsafe=False, **kwargs)

创建一个新的 Mock 对象。 Mock 接受几个可选的参数来指定 Mock 对象的行为:

  • spec:这可以是字符串列表或充当模拟对象规范的现有对象(类或实例)。 如果您传入一个对象,则通过在该对象上调用 dir 来形成一个字符串列表(不包括不受支持的魔法属性和方法)。 访问不在此列表中的任何属性将引发 AttributeError

    如果 spec 是一个对象(而不是字符串列表),则 __class__ 返回 spec 对象的类。 这允许模拟通过 isinstance() 测试。

  • spec_setspec 的更严格变体。 如果使用,尝试 set 或在模拟上获取不在作为 spec_set 传递的对象上的属性将引发 AttributeError

  • side_effect:每当调用 Mock 时都会调用的函数。 请参阅 side_effect 属性。 用于引发异常或动态更改返回值。 该函数使用与模拟相同的参数调用,除非它返回 DEFAULT,否则将使用该函数的返回值作为返回值。

    或者 side_effect 可以是异常类或实例。 在这种情况下,调用模拟时将引发异常。

    如果 side_effect 是一个可迭代对象,那么每次调用模拟都会从可迭代对象返回下一个值。

    side_effect 可以通过将其设置为 None 来清除。

  • return_value:调用mock时返回的值。 默认情况下,这是一个新的 Mock(在第一次访问时创建)。 请参阅 return_value 属性。

  • unsafe:默认情况下,如果任何以 assertassret 开头的属性将引发 AttributeError。 传递 unsafe=True 将允许访问这些属性。

    3.5 版中的新功能。

  • wraps:模拟对象要包装的项目。 如果 wraps 不是 None 则调用 Mock 会将调用传递给被包装的对象(返回真实结果)。 模拟上的属性访问将返回一个 Mock 对象,该对象包装了被包装对象的相应属性(因此尝试访问不存在的属性将引发 AttributeError)。

    如果模拟具有显式的 return_value 设置,则调用不会传递给包装的对象,而是返回 return_value

  • name:如果模拟有一个名字,那么它将被用在模拟的repr中。 这对于调试很有用。 该名称传播到子模拟。

也可以使用任意关键字参数调用模拟。 这些将用于在创建后在模拟上设置属性。 有关详细信息,请参阅 configure_mock() 方法。

assert_called(*args, **kwargs)

断言模拟至少被调用一次。

>>> mock = Mock()
>>> mock.method()
<Mock name='mock.method()' id='...'>
>>> mock.method.assert_called()

3.6 版中的新功能。

assert_called_once(*args, **kwargs)

断言模拟只被调用了一次。

>>> mock = Mock()
>>> mock.method()
<Mock name='mock.method()' id='...'>
>>> mock.method.assert_called_once()
>>> mock.method()
<Mock name='mock.method()' id='...'>
>>> mock.method.assert_called_once()
Traceback (most recent call last):
...
AssertionError: Expected 'method' to have been called once. Called 2 times.

3.6 版中的新功能。

assert_called_with(*args, **kwargs)

此方法是断言以特定方式进行调用的便捷方法:

>>> mock = Mock()
>>> mock.method(1, 2, 3, test='wow')
<Mock name='mock.method()' id='...'>
>>> mock.method.assert_called_with(1, 2, 3, test='wow')
assert_called_once_with(*args, **kwargs)

断言模拟只被调用了一次,并且该调用具有指定的参数。

>>> mock = Mock(return_value=None)
>>> mock('foo', bar='baz')
>>> mock.assert_called_once_with('foo', bar='baz')
>>> mock('other', bar='values')
>>> mock.assert_called_once_with('other', bar='values')
Traceback (most recent call last):
  ...
AssertionError: Expected 'mock' to be called once. Called 2 times.
assert_any_call(*args, **kwargs)

断言已经使用指定的参数调用了模拟。

如果模拟有 ever 被调用,则断言通过,不像 assert_Called_with()assert_Called_once_with() 只有在调用是最近的调用时才通过,并且在 assert_Called_once_with() 的情况下,它也必须是唯一的调用。

>>> mock = Mock(return_value=None)
>>> mock(1, 2, arg='thing')
>>> mock('some', 'thing', 'else')
>>> mock.assert_any_call(1, 2, arg='thing')
assert_has_calls(calls, any_order=False)

断言已经使用指定的调用调用了模拟。 检查 mock_calls 列表中的调用。

如果 any_order 为 false(默认值),则调用必须是顺序的。 在指定的调用之前或之后可以有额外的调用。

如果 any_order 为真,则调用可以按任意顺序排列,但它们必须全部出现在 mock_calls 中。

>>> mock = Mock(return_value=None)
>>> mock(1)
>>> mock(2)
>>> mock(3)
>>> mock(4)
>>> calls = [call(2), call(3)]
>>> mock.assert_has_calls(calls)
>>> calls = [call(4), call(2), call(3)]
>>> mock.assert_has_calls(calls, any_order=True)
assert_not_called()

断言模拟从未被调用。

>>> m = Mock()
>>> m.hello.assert_not_called()
>>> obj = m.hello()
>>> m.hello.assert_not_called()
Traceback (most recent call last):
  ...
AssertionError: Expected 'hello' to not have been called. Called 1 times.

3.5 版中的新功能。

reset_mock(*, return_value=False, side_effect=False)

reset_mock 方法重置模拟对象上的所有调用属性:

>>> mock = Mock(return_value=None)
>>> mock('hello')
>>> mock.called
True
>>> mock.reset_mock()
>>> mock.called
False

3.6 版更改: 在 reset_mock 函数中添加了两个仅关键字参数。

当您想要进行一系列重用同一对象的断言时,这可能很有用。 请注意, reset_mock() 不会 清除返回值、side_effect 或您默认使用正常分配设置的任何子属性。 如果您想重置 return_valueside_effect,则将相应的参数作为 True 传递。 子模拟和返回值模拟(如果有)也被重置。

笔记

return_valueside_effect 是仅关键字参数。

mock_add_spec(spec, spec_set=False)

向模拟添加规范。 spec 可以是一个对象,也可以是一个字符串列表。 只有 spec 上的属性可以作为属性从模拟中获取。

如果 spec_set 为真,则只能设置规范上的属性。

attach_mock(mock, attribute)

附加一个模拟作为这个的属性,替换其名称和父级。 对附加模拟的调用将记录在该模拟的 method_callsmock_calls 属性中。

configure_mock(**kwargs)

通过关键字参数在模拟上设置属性。

可以使用标准点表示法在子模拟上设置属性加上返回值和副作用,并在方法调用中解压字典:

>>> mock = Mock()
>>> attrs = {'method.return_value': 3, 'other.side_effect': KeyError}
>>> mock.configure_mock(**attrs)
>>> mock.method()
3
>>> mock.other()
Traceback (most recent call last):
  ...
KeyError

同样的事情可以在对模拟的构造函数调用中实现:

>>> attrs = {'method.return_value': 3, 'other.side_effect': KeyError}
>>> mock = Mock(some_attribute='eggs', **attrs)
>>> mock.some_attribute
'eggs'
>>> mock.method()
3
>>> mock.other()
Traceback (most recent call last):
  ...
KeyError

configure_mock() 的存在是为了在创建模拟后更容易进行配置。

__dir__()

Mock 对象将 dir(some_mock) 的结果限制为有用的结果。 对于具有 spec 的模拟,这包括模拟的所有允许属性。

请参阅 FILTER_DIR 了解此过滤的作用以及如何将其关闭。

_get_child_mock(**kw)

为属性和返回值创建子模拟。 默认情况下,子模拟将与父模拟具有相同的类型。 Mock 的子类可能希望覆盖它以自定义制作子模拟的方式。

对于不可调用的模拟,将使用可调用的变体(而不是任何自定义子类)。

called

一个布尔值,表示模拟对象是否已被调用:

>>> mock = Mock(return_value=None)
>>> mock.called
False
>>> mock()
>>> mock.called
True
call_count

一个整数,告诉你模拟对象被调用了多少次:

>>> mock = Mock(return_value=None)
>>> mock.call_count
0
>>> mock()
>>> mock()
>>> mock.call_count
2
return_value

设置此项以配置通过调用模拟返回的值:

>>> mock = Mock()
>>> mock.return_value = 'fish'
>>> mock()
'fish'

默认返回值是一个模拟对象,您可以按正常方式配置它:

>>> mock = Mock()
>>> mock.return_value.attribute = sentinel.Attribute
>>> mock.return_value()
<Mock name='mock()()' id='...'>
>>> mock.return_value.assert_called_with()

return_value 也可以在构造函数中设置:

>>> mock = Mock(return_value=3)
>>> mock.return_value
3
>>> mock()
3
side_effect

这可以是在调用模拟时要调用的函数、可迭代或要引发的异常(类或实例)。

如果您传入一个函数,它将使用与模拟相同的参数调用,除非该函数返回 DEFAULT 单例,否则对模拟的调用将返回函数返回的任何内容。 如果函数返回 DEFAULT,则模拟将返回其正常值(来自 return_value)。

如果您传入一个可迭代对象,它将用于检索一个迭代器,该迭代器必须在每次调用时产生一个值。 该值可以是要引发的异常实例,也可以是从调用模拟返回的值(DEFAULT 处理与函数情况相同)。

引发异常的模拟示例(用于测试 API 的异常处理):

>>> mock = Mock()
>>> mock.side_effect = Exception('Boom!')
>>> mock()
Traceback (most recent call last):
  ...
Exception: Boom!

使用 side_effect 返回值序列:

>>> mock = Mock()
>>> mock.side_effect = [3, 2, 1]
>>> mock(), mock(), mock()
(3, 2, 1)

使用可调用:

>>> mock = Mock(return_value=3)
>>> def side_effect(*args, **kwargs):
...     return DEFAULT
...
>>> mock.side_effect = side_effect
>>> mock()
3

side_effect 可以在构造函数中设置。 这是一个示例,该示例将调用模拟的值添加一个并返回它:

>>> side_effect = lambda value: value + 1
>>> mock = Mock(side_effect=side_effect)
>>> mock(3)
4
>>> mock(-8)
-7

side_effect 设置为 None 清除它:

>>> m = Mock(side_effect=KeyError, return_value=3)
>>> m()
Traceback (most recent call last):
 ...
KeyError
>>> m.side_effect = None
>>> m()
3
call_args

这要么是 None(如果尚未调用模拟),要么是上次调用模拟时使用的参数。 这将采用元组的形式:第一个成员是调用模拟的任何有序参数(或空元组),第二个成员是任何关键字参数(或空字典)。

>>> mock = Mock(return_value=None)
>>> print(mock.call_args)
None
>>> mock()
>>> mock.call_args
call()
>>> mock.call_args == ()
True
>>> mock(3, 4)
>>> mock.call_args
call(3, 4)
>>> mock.call_args == ((3, 4),)
True
>>> mock(3, 4, 5, key='fish', next='w00t!')
>>> mock.call_args
call(3, 4, 5, key='fish', next='w00t!')

call_args 以及列表 call_args_listmethod_callsmock_calls 的成员都是 call 对象。 这些是元组,因此可以将它们解包以获取单个参数并进行更复杂的断言。 将 调用视为元组

call_args_list

这是按顺序对模拟对象进行的所有调用的列表(因此列表的长度是它被调用的次数)。 在进行任何调用之前,它是一个空列表。 call 对象可用于方便地构建调用列表以与 call_args_list 进行比较。

>>> mock = Mock(return_value=None)
>>> mock()
>>> mock(3, 4)
>>> mock(key='fish', next='w00t!')
>>> mock.call_args_list
[call(), call(3, 4), call(key='fish', next='w00t!')]
>>> expected = [(), ((3, 4),), ({'key': 'fish', 'next': 'w00t!'},)]
>>> mock.call_args_list == expected
True

call_args_list 的成员是 call 对象。 这些可以解包为元组以获取单个参数。 将 调用视为元组

method_calls

除了跟踪对自身的调用,mock 还跟踪对方法和属性的调用,以及 他们的 方法和属性:

>>> mock = Mock()
>>> mock.method()
<Mock name='mock.method()' id='...'>
>>> mock.property.method.attribute()
<Mock name='mock.property.method.attribute()' id='...'>
>>> mock.method_calls
[call.method(), call.property.method.attribute()]

method_calls 的成员是 call 对象。 这些可以解包为元组以获取单个参数。 将 调用视为元组

mock_calls

mock_calls 记录 all 对模拟对象、其方法、魔术方法 返回值模拟的调用。

>>> mock = MagicMock()
>>> result = mock(1, 2, 3)
>>> mock.first(a=3)
<MagicMock name='mock.first()' id='...'>
>>> mock.second()
<MagicMock name='mock.second()' id='...'>
>>> int(mock)
1
>>> result(1)
<MagicMock name='mock()()' id='...'>
>>> expected = [call(1, 2, 3), call.first(a=3), call.second(),
... call.__int__(), call()(1)]
>>> mock.mock_calls == expected
True

mock_calls 的成员是 call 对象。 这些可以解包为元组以获取单个参数。 将 调用视为元组

笔记

记录 mock_calls 的方式意味着在进行嵌套调用时,不会记录祖先调用的参数,因此将始终比较相等:

>>> mock = MagicMock()
>>> mock.top(a=3).bottom()
<MagicMock name='mock.top().bottom()' id='...'>
>>> mock.mock_calls
[call.top(a=3), call.top().bottom()]
>>> mock.mock_calls[-1] == call.top(a=-1).bottom()
True
__class__

通常,对象的 __class__ 属性将返回其类型。 对于具有 spec 的模拟对象,__class__ 返回规范类。 这允许模拟对象通过 isinstance() 测试它们正在替换/伪装为的对象:

>>> mock = Mock(spec=3)
>>> isinstance(mock, int)
True

__class__ 可分配给,这允许模拟通过 isinstance() 检查,而无需强制您使用规范:

>>> mock = Mock()
>>> mock.__class__ = dict
>>> isinstance(mock, dict)
True
class unittest.mock.NonCallableMock(spec=None, wraps=None, name=None, spec_set=None, **kwargs)
Mock 的不可调用版本。 构造函数参数与 Mock 的含义相同,除了 return_valueside_effect 在不可调用的模拟中没有意义。

使用类或实例作为 specspec_set 的模拟对象能够通过 isinstance() 测试:

>>> mock = Mock(spec=SomeClass)
>>> isinstance(mock, SomeClass)
True
>>> mock = Mock(spec_set=SomeClass())
>>> isinstance(mock, SomeClass)
True

Mock 类支持模拟魔术方法。 有关完整的详细信息,请参阅 魔术方法

模拟类和 patch() 装饰器都采用任意关键字参数进行配置。 对于 patch() 装饰器,关键字被传递给正在创建的模拟的构造函数。 关键字参数用于配置模拟的属性:

>>> m = MagicMock(attribute=3, other='fish')
>>> m.attribute
3
>>> m.other
'fish'

子模拟的返回值和副作用可以用同样的方式设置,使用点符号。 由于您不能在通话中直接使用带点名称的名称,因此您必须创建一个字典并使用 ** 将其解压缩:

>>> attrs = {'method.return_value': 3, 'other.side_effect': KeyError}
>>> mock = Mock(some_attribute='eggs', **attrs)
>>> mock.some_attribute
'eggs'
>>> mock.method()
3
>>> mock.other()
Traceback (most recent call last):
  ...
KeyError

使用 spec(或 spec_set)创建的可调用模拟将在匹配对模拟的调用时内省规范对象的签名。 因此,它可以匹配实际调用的参数,无论它们是按位置传递还是按名称传递:

>>> def f(a, b, c): pass
...
>>> mock = Mock(spec=f)
>>> mock(1, 2, c=3)
<Mock name='mock()' id='140161580456576'>
>>> mock.assert_called_with(1, 2, 3)
>>> mock.assert_called_with(a=1, b=2, c=3)

这适用于 assert_called_with()assert_Called_once_with()assert_has_calls()assert_any_call()。 当 Autospeccing 时,它也将应用于模拟对象上的方法调用。

3.4 版更改: 添加了对指定和自动指定模拟对象的签名自省。


class unittest.mock.PropertyMock(*args, **kwargs)

旨在用作类的属性或其他描述符的模拟。 PropertyMock 提供了 __get__()__set__() 方法,因此您可以在获取时指定返回值。

从对象中获取 PropertyMock 实例调用模拟,没有参数。 设置它会调用具有设置值的模拟。

>>> class Foo:
...     @property
...     def foo(self):
...         return 'something'
...     @foo.setter
...     def foo(self, value):
...         pass
...
>>> with patch('__main__.Foo.foo', new_callable=PropertyMock) as mock_foo:
...     mock_foo.return_value = 'mockity-mock'
...     this_foo = Foo()
...     print(this_foo.foo)
...     this_foo.foo = 6
...
mockity-mock
>>> mock_foo.mock_calls
[call(), call(6)]

由于模拟属性的存储方式,您不能直接将 PropertyMock 附加到模拟对象。 相反,您可以将其附加到模拟类型对象:

>>> m = MagicMock()
>>> p = PropertyMock(return_value=3)
>>> type(m).foo = p
>>> m.foo
3
>>> p.assert_called_once_with()

26.5.2.1. 打电话

模拟对象是可调用的。 该调用将返回设置为 return_value 属性的值。 默认返回值是一个新的 Mock 对象; 它是在第一次访问返回值时创建的(显式或通过调用 Mock) - 但它被存储并且每次都返回相同的值。

对对象的调用将记录在 call_argscall_args_list 等属性中。

如果设置了 side_effect,那么它将在通话记录后调用,因此如果 side_effect 引发异常,通话仍会被记录。

使模拟在调用时引发异常的最简单方法是使 side_effect 成为异常类或实例:

>>> m = MagicMock(side_effect=IndexError)
>>> m(1, 2, 3)
Traceback (most recent call last):
  ...
IndexError
>>> m.mock_calls
[call(1, 2, 3)]
>>> m.side_effect = KeyError('Bang!')
>>> m('two', 'three', 'four')
Traceback (most recent call last):
  ...
KeyError: 'Bang!'
>>> m.mock_calls
[call(1, 2, 3), call('two', 'three', 'four')]

如果 side_effect 是一个函数,那么该函数返回的内容就是调用模拟返回的内容。 side_effect 函数使用与模拟相同的参数调用。 这允许您根据输入动态改变调用的返回值:

>>> def side_effect(value):
...     return value + 1
...
>>> m = MagicMock(side_effect=side_effect)
>>> m(1)
2
>>> m(2)
3
>>> m.mock_calls
[call(1), call(2)]

如果您希望模拟仍然返回默认返回值(新模拟)或任何设置的返回值,那么有两种方法可以做到这一点。 要么从 side_effect 内部返回 mock.return_value,要么返回 DEFAULT

>>> m = MagicMock()
>>> def side_effect(*args, **kwargs):
...     return m.return_value
...
>>> m.side_effect = side_effect
>>> m.return_value = 3
>>> m()
3
>>> def side_effect(*args, **kwargs):
...     return DEFAULT
...
>>> m.side_effect = side_effect
>>> m()
3

要移除 side_effect,并返回默认行为,请将 side_effect 设置为 None

>>> m = MagicMock(return_value=6)
>>> def side_effect(*args, **kwargs):
...     return 3
...
>>> m.side_effect = side_effect
>>> m()
3
>>> m.side_effect = None
>>> m()
6

side_effect 也可以是任何可迭代对象。 对模拟的重复调用将从可迭代对象返回值(直到可迭代对象耗尽并引发 StopIteration):

>>> m = MagicMock(side_effect=[1, 2, 3])
>>> m()
1
>>> m()
2
>>> m()
3
>>> m()
Traceback (most recent call last):
  ...
StopIteration

如果 iterable 的任何成员是异常,它们将被引发而不是返回:

>>> iterable = (33, ValueError, 66)
>>> m = MagicMock(side_effect=iterable)
>>> m()
33
>>> m()
Traceback (most recent call last):
 ...
ValueError
>>> m()
66

26.5.2.2. 删除属性

模拟对象按需创建属性。 这允许它们伪装成任何类型的对象。

您可能希望模拟对象将 False 返回到 hasattr() 调用,或者在获取属性时引发 AttributeError。 您可以通过为模拟提供一个对象作为 spec 来做到这一点,但这并不总是很方便。

您可以通过删除属性来“阻止”属性。 一旦删除,访问属性将引发 AttributeError

>>> mock = MagicMock()
>>> hasattr(mock, 'm')
True
>>> del mock.m
>>> hasattr(mock, 'm')
False
>>> del mock.f
>>> mock.f
Traceback (most recent call last):
    ...
AttributeError: f

26.5.2.3. 模拟名称和名称属性

由于“name”是 Mock 构造函数的一个参数,如果你想让你的模拟对象有一个“name”属性,你不能只在创建时传递它。 有两种选择。 一种选择是使用 configure_mock()

>>> mock = MagicMock()
>>> mock.configure_mock(name='my_name')
>>> mock.name
'my_name'

一个更简单的选择是在模拟创建后简单地设置“name”属性:

>>> mock = MagicMock()
>>> mock.name = "foo"

26.5.2.4. 附加模拟作为属性

当您附加一个模拟作为另一个模拟的属性(或作为返回值)时,它成为该模拟的“子项”。 对子进程的调用记录在父进程的 method_callsmock_calls 属性中。 这对于配置子模拟然后将它们附加到父模拟非常有用,或者用于将模拟附加到记录对子模拟的所有调用的父模拟并允许您对模拟之间的调用顺序进行断言:

>>> parent = MagicMock()
>>> child1 = MagicMock(return_value=None)
>>> child2 = MagicMock(return_value=None)
>>> parent.child1 = child1
>>> parent.child2 = child2
>>> child1(1)
>>> child2(2)
>>> parent.mock_calls
[call.child1(1), call.child2(2)]

例外情况是模拟有名称。 如果出于某种原因您不希望它发生,这允许您防止“养育”。

>>> mock = MagicMock()
>>> not_a_child = MagicMock(name='not-a-child')
>>> mock.attribute = not_a_child
>>> mock.attribute()
<MagicMock name='not-a-child()' id='...'>
>>> mock.mock_calls
[]

patch() 为您创建的模拟会自动命名。 要将具有名称的模拟附加到父级,您可以使用 attach_mock() 方法:

>>> thing1 = object()
>>> thing2 = object()
>>> parent = MagicMock()
>>> with patch('__main__.thing1', return_value=None) as child1:
...     with patch('__main__.thing2', return_value=None) as child2:
...         parent.attach_mock(child1, 'child1')
...         parent.attach_mock(child2, 'child2')
...         child1('one')
...         child2('two')
...
>>> parent.mock_calls
[call.child1('one'), call.child2('two')]
1
唯一的例外是魔术方法和属性(具有前导和尾随双下划线的那些)。 Mock 不会创建这些,而是引发 AttributeError。 这是因为解释器通常会隐式地请求这些方法,并且当它需要一个魔术方法时,会很困惑 非常 去获取一个新的 Mock 对象。 如果您需要魔术方法支持,请参阅 魔术方法


26.5.3. 修补程序

补丁装饰器仅用于在它们装饰的函数范围内修补对象。 即使引发异常,它们也会自动为您处理修补程序。 所有这些函数也可以用于 with 语句或作为类装饰器。

26.5.3.1. 修补

笔记

patch() 使用起来很简单。 关键是在正确的命名空间中进行修补。 请参阅 补丁 部分。


unittest.mock.patch(target, new=DEFAULT, spec=None, create=False, spec_set=None, autospec=None, new_callable=None, **kwargs)

patch() 充当函数装饰器、类装饰器或上下文管理器。 在函数体或 with 语句中,target 被一个 new 对象修补。 当函数/with 语句退出时,补丁被撤销。

如果省略 new,则目标将替换为 MagicMock。 如果 patch() 用作装饰器并且省略 new,则创建的模拟将作为额外参数传递给装饰函数。 如果 patch() 用作上下文管理器,则上下文管理器将返回创建的模拟。

target 应该是 'package.module.ClassName' 形式的字符串。 target 被导入,指定的对象被替换为 new 对象,所以 target 必须可以从你调用的环境中导入 patch() 来自。 目标是在执行装饰函数时导入的,而不是在装饰时导入。

specspec_set 关键字参数被传递给 MagicMock 如果补丁正在为你创建一个。

此外,您可以传递 spec=Truespec_set=True,这会导致 patch 传入被模拟为 spec/spec_set 对象的对象。

new_callable 允许您指定一个不同的类或可调用对象,它将被调用以创建 new 对象。 默认使用 MagicMock

spec 的一种更强大的形式是 autospec。 如果您设置 autospec=True,那么将使用被替换对象的规范创建模拟。 模拟的所有属性也将具有被替换对象的相应属性的规范。 被模拟的方法和函数将检查其参数,如果使用错误的签名调用它们,则会引发 TypeError。 对于替换类的模拟,它们的返回值(“实例”)将与类具有相同的规范。 请参阅 create_autospec() 函数和 Autospeccing

您可以通过 autospec=some_object 来使用任意对象作为规范而不是被替换的对象,而不是 autospec=True

默认情况下 patch() 将无法替换不存在的属性。 如果你传入create=True,而该属性不存在,patch会在调用patched函数时为你创建该属性,之后再删除。 这对于针对生产代码在运行时创建的属性编写测试非常有用。 默认情况下它是关闭的,因为它可能很危险。 打开它后,您可以针对实际上不存在的 API 编写通过测试!

笔记

3.5 版本更改: 如果您正在修补模块中的内置函数,则不需要通过 create=True,它将默认添加。

补丁可以用作 TestCase 类装饰器。 它通过装饰类中的每个测试方法来工作。 当您的测试方法共享一个通用补丁集时,这会减少样板代码。 patch() 通过查找以 patch.TEST_PREFIX 开头的方法名称来查找测试。 默认情况下,这是 'test',它与 unittest 查找测试的方式相匹配。 您可以通过设置 patch.TEST_PREFIX 来指定替代前缀。

Patch 可以用作上下文管理器,使用 with 语句。 此处修补适用于 with 语句之后的缩进块。 如果使用“as”,则修补对象将绑定到“as”之后的名称; 如果 patch() 正在为您创建模拟对象,则非常有用。

patch() 接受任意关键字参数。 这些将在构造时传递给 Mock(或 new_callable)。

patch.dict(...)patch.multiple(...)patch.object(...) 可用于替代用例。

patch() 作为函数装饰器,为您创建模拟并将其传递给装饰函数:

>>> @patch('__main__.SomeClass')
... def function(normal_argument, mock_class):
...     print(mock_class is SomeClass)
...
>>> function(None)
True

修补类会用 MagicMock 实例 替换该类。 如果该类在被测代码中实例化,则将使用模拟的 return_value

如果该类被多次实例化,您可以使用 side_effect 每次都返回一个新的模拟。 或者,您可以将 return_value 设置为您想要的任何值。

要在修补类上的 实例 的方法上配置返回值,您必须在 return_value 上执行此操作。 例如:

>>> class Class:
...     def method(self):
...         pass
...
>>> with patch('__main__.Class') as MockClass:
...     instance = MockClass.return_value
...     instance.method.return_value = 'foo'
...     assert Class() is instance
...     assert Class().method() == 'foo'
...

如果您使用 specspec_set 并且 patch() 正在替换 class,则创建的模拟的返回值将具有相同规格

>>> Original = Class
>>> patcher = patch('__main__.Class', spec=True)
>>> MockClass = patcher.start()
>>> instance = MockClass()
>>> assert isinstance(instance, Original)
>>> patcher.stop()

new_callable 参数在您想为创建的模拟使用默认 MagicMock 的替代类时很有用。 例如,如果您想要使用 NonCallableMock

>>> thing = object()
>>> with patch('__main__.thing', new_callable=NonCallableMock) as mock_thing:
...     assert thing is mock_thing
...     thing()
...
Traceback (most recent call last):
  ...
TypeError: 'NonCallableMock' object is not callable

另一个用例可能是用 io.StringIO 实例替换对象:

>>> from io import StringIO
>>> def foo():
...     print('Something')
...
>>> @patch('sys.stdout', new_callable=StringIO)
... def test(mock_stdout):
...     foo()
...     assert mock_stdout.getvalue() == 'Something\n'
...
>>> test()

patch() 为您创建模拟时,通常您需要做的第一件事是配置模拟。 其中一些配置可以在调用补丁中完成。 您传递给调用的任何任意关键字都将用于在创建的模拟上设置属性:

>>> patcher = patch('__main__.thing', first='one', second='two')
>>> mock_thing = patcher.start()
>>> mock_thing.first
'one'
>>> mock_thing.second
'two'

除了创建的模拟属性上的属性外,还可以配置子模拟的 return_valueside_effect。 这些在语法上不能直接作为关键字参数传入,但是以这些作为键的字典仍然可以使用 ** 扩展为 patch() 调用:

>>> config = {'method.return_value': 3, 'other.side_effect': KeyError}
>>> patcher = patch('__main__.thing', **config)
>>> mock_thing = patcher.start()
>>> mock_thing.method()
3
>>> mock_thing.other()
Traceback (most recent call last):
  ...
KeyError

26.5.3.2. 补丁对象

patch.object(target, attribute, new=DEFAULT, spec=None, create=False, spec_set=None, autospec=None, new_callable=None, **kwargs)

使用模拟对象修补对象 (target) 上的命名成员 (attribute)。

patch.object() 可以用作装饰器、类装饰器或上下文管理器。 参数 newspeccreatespec_setautospecnew_callablepatch() 的含义相同。 与 patch() 一样,patch.object() 使用任意关键字参数来配置它创建的模拟对象。

当用作类装饰器时 patch.object() 尊重 patch.TEST_PREFIX 选择要包装的方法。

您可以使用三个参数或两个参数调用 patch.object()。 三参数形式采用要修补的对象、属性名称和替换属性的对象。

当使用两个参数形式调用时,您将省略替换对象,并为您创建一个模拟并将其作为额外参数传递给装饰函数:

>>> @patch.object(SomeClass, 'class_method')
... def test(mock_method):
...     SomeClass.class_method(3)
...     mock_method.assert_called_with(3)
...
>>> test()

speccreatepatch.object() 的其他参数与 patch() 的含义相同。


26.5.3.3. 补丁文件

patch.dict(in_dict, values=(), clear=False, **kwargs)

修补字典或字典类对象,并在测试后将字典恢复到原始状态。

in_dict 可以是字典或映射,如容器。 如果它是一个映射,那么它至少必须支持获取、设置和删除项目以及对键进行迭代。

in_dict 也可以是指定字典名称的字符串,然后通过导入来获取。

values 可以是要在字典中设置的值的字典。 values 也可以是 (key, value) 对的迭代。

如果 clear 为真,则在设置新值之前将清除字典。

patch.dict() 也可以使用任意关键字参数调用来设置字典中的值。

patch.dict() 可以用作上下文管理器、装饰器或类装饰器。 当用作类装饰器时 patch.dict() 尊重 patch.TEST_PREFIX 选择要包装的方法。

patch.dict() 可用于向字典添加成员,或者简单地让测试更改字典,并确保在测试结束时恢复字典。

>>> foo = {}
>>> with patch.dict(foo, {'newkey': 'newvalue'}):
...     assert foo == {'newkey': 'newvalue'}
...
>>> assert foo == {}
>>> import os
>>> with patch.dict('os.environ', {'newkey': 'newvalue'}):
...     print(os.environ['newkey'])
...
newvalue
>>> assert 'newkey' not in os.environ

可以在 patch.dict() 调用中使用关键字来设置字典中的值:

>>> mymodule = MagicMock()
>>> mymodule.function.return_value = 'fish'
>>> with patch.dict('sys.modules', mymodule=mymodule):
...     import mymodule
...     mymodule.function('some', 'args')
...
'fish'

patch.dict() 可以与字典类对象一起使用,这些对象实际上不是字典。 他们至少必须支持项目获取、设置、删除以及迭代或成员资格测试。 这对应于魔术方法 __getitem__()__setitem__()__delitem__()__iter__()__contains__()

>>> class Container:
...     def __init__(self):
...         self.values = {}
...     def __getitem__(self, name):
...         return self.values[name]
...     def __setitem__(self, name, value):
...         self.values[name] = value
...     def __delitem__(self, name):
...         del self.values[name]
...     def __iter__(self):
...         return iter(self.values)
...
>>> thing = Container()
>>> thing['one'] = 1
>>> with patch.dict(thing, one=2, two=3):
...     assert thing['one'] == 2
...     assert thing['two'] == 3
...
>>> assert thing['one'] == 1
>>> assert list(thing) == ['one']

26.5.3.4. 补丁.multiple

patch.multiple(target, spec=None, create=False, spec_set=None, autospec=None, new_callable=None, **kwargs)

在一次调用中执行多个修补程序。 它需要要修补的对象(作为对象或字符串以通过导入获取对象)和修补程序的关键字参数:

with patch.multiple(settings, FIRST_PATCH='one', SECOND_PATCH='two'):
    ...

如果您希望 patch.multiple() 为您创建模拟,请使用 DEFAULT 作为值。 在这种情况下,创建的模拟通过关键字传递给装饰函数,并在使用 patch.multiple() 作为上下文管理器时返回字典。

patch.multiple() 可以用作装饰器、类装饰器或上下文管理器。 参数 specspec_setcreateautospecnew_callable的含义相同补丁()。 这些参数将应用于由 patch.multiple() 完成的 all 补丁。

当用作类装饰器时 patch.multiple() 尊重 patch.TEST_PREFIX 选择要包装的方法。

如果您希望 patch.multiple() 为您创建模拟,那么您可以使用 DEFAULT 作为值。 如果您使用 patch.multiple() 作为装饰器,则创建的模拟将通过关键字传递到装饰函数中。

>>> thing = object()
>>> other = object()
>>> @patch.multiple('__main__', thing=DEFAULT, other=DEFAULT)
... def test_function(thing, other):
...     assert isinstance(thing, MagicMock)
...     assert isinstance(other, MagicMock)
...
>>> test_function()

patch.multiple() 可以与其他 patch 装饰器嵌套,但将通过关键字 传递的参数放在patch() 创建的任何标准参数之后

>>> @patch('sys.exit')
... @patch.multiple('__main__', thing=DEFAULT, other=DEFAULT)
... def test_function(mock_exit, other, thing):
...     assert 'other' in repr(other)
...     assert 'thing' in repr(thing)
...     assert 'exit' in repr(mock_exit)
...
>>> test_function()

如果 patch.multiple() 用作上下文管理器,则上下文管理器返回的值是一个字典,其中创建的模拟按名称键:

>>> with patch.multiple('__main__', thing=DEFAULT, other=DEFAULT) as values:
...     assert 'other' in repr(values['other'])
...     assert 'thing' in repr(values['thing'])
...     assert values['thing'] is thing
...     assert values['other'] is other
...

26.5.3.5. 补丁方法:启动和停止

所有补丁程序都有 start()stop() 方法。 这些使得在 setUp 方法中打补丁或在不嵌套装饰器或 with 语句的情况下执行多个补丁变得更简单。

要使用它们,请正常调用 patch()patch.object()patch.dict() 并保留对返回的 [ X135X] 对象。 然后你可以调用 start() 来放置补丁,然后调用 stop() 来撤销它。

如果您使用 patch() 为您创建模拟,那么它将通过调用 patcher.start 返回。

>>> patcher = patch('package.module.ClassName')
>>> from package import module
>>> original = module.ClassName
>>> new_mock = patcher.start()
>>> assert module.ClassName is not original
>>> assert module.ClassName is new_mock
>>> patcher.stop()
>>> assert module.ClassName is original
>>> assert module.ClassName is not new_mock

一个典型的用例可能是在 TestCasesetUp 方法中做多个补丁:

>>> class MyTest(TestCase):
...     def setUp(self):
...         self.patcher1 = patch('package.module.Class1')
...         self.patcher2 = patch('package.module.Class2')
...         self.MockClass1 = self.patcher1.start()
...         self.MockClass2 = self.patcher2.start()
...
...     def tearDown(self):
...         self.patcher1.stop()
...         self.patcher2.stop()
...
...     def test_something(self):
...         assert package.module.Class1 is self.MockClass1
...         assert package.module.Class2 is self.MockClass2
...
>>> MyTest('test_something').run()

警告

如果您使用这种技术,您必须通过调用 stop 来确保补丁被“撤销”。 这可能比您想象的要复杂,因为如果在 setUp 中引发异常,则不会调用 tearDownunittest.TestCase.addCleanup() 使这更容易:

>>> class MyTest(TestCase):
...     def setUp(self):
...         patcher = patch('package.module.Class')
...         self.MockClass = patcher.start()
...         self.addCleanup(patcher.stop)
...
...     def test_something(self):
...         assert package.module.Class is self.MockClass
...

作为额外的奖励,您不再需要保留对 patcher 对象的引用。


也可以使用 patch.stopall() 停止所有已启动的补丁。

patch.stopall()
停止所有活动的补丁。 仅停止以 start 开头的补丁。


26.5.3.6. 补丁内置

您可以修补模块中的任何内置函数。 以下示例补丁内置于 ord()

>>> @patch('__main__.ord')
... def test(mock_ord):
...     mock_ord.return_value = 101
...     print(ord('c'))
...
>>> test()
101

26.5.3.7. TEST_PREFIX

所有修补程序都可以用作类装饰器。 当以这种方式使用时,它们包装了类上的每个测试方法。 修补程序会将以 'test' 开头的方法识别为测试方法。 这与默认情况下 unittest.TestLoader 查找测试方法的方式相同。

您可能希望为测试使用不同的前缀。 您可以通过设置 patch.TEST_PREFIX 来通知修补程序不同的前缀:

>>> patch.TEST_PREFIX = 'foo'
>>> value = 3
>>>
>>> @patch('__main__.value', 'not three')
... class Thing:
...     def foo_one(self):
...         print(value)
...     def foo_two(self):
...         print(value)
...
>>>
>>> Thing().foo_one()
not three
>>> Thing().foo_two()
not three
>>> value
3

26.5.3.8. 嵌套补丁装饰器

如果你想执行多个补丁,那么你可以简单地堆叠装饰器。

您可以使用此模式堆叠多个补丁装饰器:

>>> @patch.object(SomeClass, 'class_method')
... @patch.object(SomeClass, 'static_method')
... def test(mock1, mock2):
...     assert SomeClass.static_method is mock1
...     assert SomeClass.class_method is mock2
...     SomeClass.static_method('foo')
...     SomeClass.class_method('bar')
...     return mock1, mock2
...
>>> mock1, mock2 = test()
>>> mock1.assert_called_once_with('foo')
>>> mock2.assert_called_once_with('bar')

请注意,装饰器是从底部向上应用的。 这是 Python 应用装饰器的标准方式。 传递到您的测试函数中的创建模拟的顺序与此顺序匹配。


26.5.3.9. 在哪里打补丁

patch() 通过(临时)将 name 指向的对象更改为另一个对象。 可以有许多名称指向任何单个对象,因此要使修补工作,您必须确保修补被测系统使用的名称。

基本原理是在查找对象的位置打补丁,这不一定与定义的位置相同。 几个例子将有助于澄清这一点。

想象一下,我们有一个项目,我们要使用以下结构进行测试:

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 已经有一个对 real SomeClass 的引用,看起来我们的补丁没有效果。

关键是修补 SomeClass 使用它的地方(或查找它的地方)。 在这种情况下,some_function 实际上会在模块 b 中查找 SomeClass,我们已经将其导入到该模块中。 修补应如下所示:

@patch('b.SomeClass')

但是,请考虑替代方案,其中模块 b 代替 from a import SomeClass 使用 import a,而 some_function 使用 a.SomeClass。 这两种导入形式都很常见。 在这种情况下,我们要修补的类正在模块中查找,因此我们必须修补 a.SomeClass

@patch('a.SomeClass')

26.5.3.10。 修补描述符和代理对象

patchpatch.object 都正确地修补和恢复描述符:类方法、静态方法和属性。 您应该在 而不是实例上修补这些。 它们还与代理属性访问的 some 对象一起使用,例如 django 设置对象


26.5.4. MagicMock 和魔术方法支持

26.5.4.1. 模拟魔术方法

Mock 支持模拟 Python 协议方法,也称为“魔术方法”。 这允许模拟对象替换容器或其他实现 Python 协议的对象。

由于魔术方法与普通方法 2 的查找方式不同,因此专门实现了此支持。 这意味着仅支持特定的魔术方法。 支持的列表包括 几乎所有 。 如果您有任何遗漏,请告诉我们。

您可以通过将您感兴趣的方法设置为函数或模拟实例来模拟魔术方法。 如果您正在使用一个函数,那么它 必须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 关键字参数来创建模拟,则尝试设置不在规范中的魔术方法将引发 AttributeError


支持的魔法方法的完整列表是:

  • __hash____sizeof____repr____str__
  • __dir____format____subclasses__
  • __floor____trunc____ceil__
  • 比较:__lt____gt____le____ge____eq____ne__
  • 容器方式:__getitem____setitem____delitem____contains____len____iter____reversed__ ] 和 __missing__
  • 上下文管理器:__enter____exit__
  • 一元数值方法:__neg____pos____invert__
  • 数值方法(包括右手和就地变体):__add____sub____mul____matmul____div__、[ X121X]、__floordiv____mod____divmod____lshift____rshift____and__ ]、__or____pow__
  • 数值转换方法:__complex____int____float____index__
  • 描述符方法:__get____set____delete__
  • 酸洗:__reduce____reduce_ex____getinitargs____getnewargs____getstate____setstate__

以下方法存在但不支持 [X36X] 不支持 ,因为它们要么被模拟使用,要么不能动态设置,要么会导致问题:

  • __getattr____setattr____init____new__
  • __prepare____instancecheck____subclasscheck____del__


26.5.4.2. 魔术模拟

有两个 MagicMock 变体:MagicMockNonCallableMagicMock

class unittest.mock.MagicMock(*args, **kw)

MagicMockMock 的子类,具有大多数魔术方法的默认实现。 您可以使用 MagicMock 而无需自己配置魔术方法。

构造函数参数的含义与 Mock 相同。

如果您使用 specspec_set 参数,则将创建规范中存在的 only 魔术方法。

class unittest.mock.NonCallableMagicMock(*args, **kw)

MagicMock 的不可调用版本。

构造函数参数与 MagicMock 的含义相同,但 return_valueside_effect 对不可调用的模拟没有意义。

魔术方法是使用 MagicMock 对象设置的,因此您可以配置它们并以通常的方式使用它们:

>>> mock = MagicMock()
>>> mock[3] = 'fish'
>>> mock.__setitem__.assert_called_with(3, 'fish')
>>> mock.__getitem__.return_value = 'result'
>>> mock[2]
'result'

默认情况下,许多协议方法需要返回特定类型的对象。 这些方法预先配置了默认返回值,因此如果您对返回值不感兴趣,则无需执行任何操作即可使用它们。 如果您想更改默认值,您仍然可以手动设置返回值。

方法及其默认值:

  • __lt__:未实施
  • __gt__:未实施
  • __le__:未实施
  • __ge__:未实施
  • __int__:1
  • __contains__:假
  • __len__:0
  • __iter__: iter([])
  • __exit__:假
  • __complex__:1j
  • __float__:1.0
  • __bool__:真
  • __index__:1
  • __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____setformat__
2
魔术方法 应该 在类而不是实例上查找。 不同版本的 Python 在应用此规则方面不一致。 支持的协议方法应该适用于所有支持的 Python 版本。
3
该函数基本上连接到类,但每个 Mock 实例与其他实例保持隔离。


26.5.5. 帮手

26.5.5.1. 哨兵

unittest.mock.sentinel

sentinel 对象提供了一种为测试提供独特对象的便捷方式。

当您按名称访问属性时,会按需创建属性。 访问相同的属性将始终返回相同的对象。 返回的对象有一个合理的代表,以便测试失败消息是可读的。

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
>>> sentinel.some_object
sentinel.some_object

26.5.5.2. 默认

unittest.mock.DEFAULT
DEFAULT 对象是一个预先创建的哨兵(实际上是 sentinel.DEFAULT)。 side_effect 函数可以使用它来指示应该使用正常的返回值。


26.5.5.3. 称呼

unittest.mock.call(*args, **kwargs)

call() 是用于进行更简单断言的辅助对象,用于与 call_argscall_args_listmock_calls 和 method_calls 进行比较X170X]。 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_list() 返回所有中间调用以及最终调用的列表。

call_list 对于对“链式调用”进行断言特别有用。 链式调用是在一行代码上进行多次调用。 这会导致模拟上的 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_argsMock.call_args_listMock 中的 call 对象。可以内省 mock_calls 属性以获取它们包含的各个参数。

Mock.call_argsMock.call_args_list 中的 call 对象是(位置参数,关键字参数)的二元组,而 call 对象在Mock.mock_calls,连同你自己构造的,都是三元组(名称,位置参数,关键字参数)。

您可以使用它们的“元组性”来提取更复杂的内省和断言的单个参数。 位置参数是一个元组(如果没有位置参数,则为空元组),关键字参数是一个字典:

>>> m = MagicMock(return_value=None)
>>> m(1, 2, 3, arg='one', arg2='two')
>>> kall = m.call_args
>>> args, kwargs = kall
>>> args
(1, 2, 3)
>>> kwargs
{'arg2': 'two', 'arg': 'one'}
>>> args is kall[0]
True
>>> 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
{'arg2': 'three', 'arg': 'two'}
>>> name is m.mock_calls[0][0]
True

26.5.5.4. create_autospec

unittest.mock.create_autospec(spec, spec_set=False, instance=False, **kwargs)

使用另一个对象作为规范创建一个模拟对象。 模拟上的属性将使用 spec 对象上的相应属性作为其规范。

被模拟的函数或方法将检查它们的参数,以确保使用正确的签名调用它们。

如果 spec_setTrue,则尝试设置规范对象上不存在的属性将引发 AttributeError

如果将类用作规范,则模拟(类的实例)的返回值将具有相同的规范。 您可以通过传递 instance=True 来使用类作为实例对象的规范。 只有当模拟的实例可调用时,返回的模拟才可调用。

create_autospec() 还接受任意关键字参数,这些参数传递给创建的模拟的构造函数。

有关如何将自动规格与 create_autospec()patch()autospec 参数一起使用的示例,请参见 Autospeccing


26.5.5.5. 任何

unittest.mock.ANY

有时您可能需要在调用 mock 时对 some 的参数进行断言,但要么不关心某些参数,要么想将它们单独从 call_args 中提取出来并制作更复杂的断言。

要忽略某些参数,您可以传入比较等于 everything 的对象。 无论传入什么,对 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

26.5.5.6。 过滤器_目录

unittest.mock.FILTER_DIR

FILTER_DIR 是一个模块级变量,用于控制模拟对象响应 dir() 的方式(仅适用于 Python 2.6 或更新版本)。 默认值为 True,它使用下面描述的过滤,只显示有用的成员。 如果您不喜欢此过滤功能,或出于诊断目的需要将其关闭,请设置 mock.FILTER_DIR = False

启用过滤后,dir(some_mock) 仅显示有用的属性,并将包括通常不会显示的任何动态创建的属性。 如果模拟是使用 spec(当然或 autospec)创建的,那么会显示原始的所有属性,即使它们尚未被访问:

>>> dir(Mock())
['assert_any_call',
 'assert_called_once_with',
 'assert_called_with',
 'assert_has_calls',
 'attach_mock',
 ...
>>> from urllib import request
>>> dir(Mock(spec=request))
['AbstractBasicAuthHandler',
 'AbstractDigestAuthHandler',
 'AbstractHTTPHandler',
 'BaseHandler',
 ...

许多不太有用的(私有 Mock 而不是被嘲笑的东西)下划线和双下划线前缀属性已从调用 dir() 的结果中过滤掉模拟。 如果您不喜欢这种行为,可以通过设置模块级别开关 FILTER_DIR 将其关闭:

>>> from unittest import mock
>>> mock.FILTER_DIR = False
>>> dir(mock.Mock())
['_NonCallableMock__get_return_value',
 '_NonCallableMock__get_side_effect',
 '_NonCallableMock__return_value_doc',
 '_NonCallableMock__set_return_value',
 '_NonCallableMock__set_side_effect',
 '__call__',
 '__class__',
 ...

或者,您可以只使用 vars(my_mock)(实例成员)和 dir(type(my_mock))(类型成员)绕过过滤,而不管 mock.FILTER_DIR


26.5.5.7。 模拟打开

unittest.mock.mock_open(mock=None, read_data=None)

用于创建模拟以替换 open() 的使用的辅助函数。 它适用于直接调用或用作上下文管理器的 open()

mock 参数是要配置的模拟对象。 如果 None(默认值),则会为您创建一个 MagicMock,API 仅限于标准文件句柄上可用的方法或属性。

read_data 是文件句柄的 read()readline()readlines() 方法返回的字符串。 对这些方法的调用将从 read_data 获取数据,直到数据耗尽。 这些方法的模拟非常简单:每次调用 mock 时,read_data 都会倒回到开头。 如果您需要对提供给测试代码的数据进行更多控制,则需要为自己自定义此模拟。 当这还不够时,PyPI 上的一个内存文件系统包可以提供一个真实的文件系统进行测试。

3.4 版更改: 添加 readline()readlines() 支持。 read() 的模拟更改为使用 read_data 而不是在每次调用时返回它。

在 3.5 版更改:read_data 现在在每次调用 mock 时重置。

使用 open() 作为上下文管理器是确保文件句柄正确关闭并变得普遍的好方法:

with open('/some/path', 'w') as f:
    f.write('something')

问题是,即使您模拟对 open() 的调用,它也是 返回的对象 用作上下文管理器(并且具有 __enter__() 和 [ X159X] 调用)。

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'

26.5.5.8. 自动检测

Autospeccing 基于现有的 spec 模拟功能。 它将模拟的 api 限制为原始对象(规范)的 api,但它是递归的(延迟实现),因此模拟的属性仅与规范的属性具有相同的 api。 此外,模拟函数/方法与原始函数/方法具有相同的调用签名,因此如果它们被错误调用,它们会引发 TypeError

在我解释自动规范的工作原理之前,这就是为什么需要它。

Mock 是一个非常强大且灵活的对象,但是当用于从被测系统中模拟对象时,它有两个缺陷。 这些缺陷之一是特定于 Mock api,另一个是使用模拟对象的更普遍的问题。

首先是 Mock 特有的问题。 Mock 有两个非常方便的 assert 方法:assert_Called_with()assert_Called_once_with()

>>> mock = Mock(name='Thing', return_value=None)
>>> mock(1, 2, 3)
>>> mock.assert_called_once_with(1, 2, 3)
>>> mock(1, 2, 3)
>>> mock.assert_called_once_with(1, 2, 3)
Traceback (most recent call last):
 ...
AssertionError: Expected 'mock' to be called once. Called 2 times.

因为模拟按需自动创建属性,并允许您使用任意参数调用它们,如果您拼错了这些断言方法之一,那么您的断言就会消失:

>>> mock = Mock(name='Thing', return_value=None)
>>> mock(1, 2, 3)
>>> mock.assret_called_once_with(4, 5, 6)

由于打字错误,您的测试可能会无声且错误地通过。

第二个问题对于模拟来说更普遍。 如果你重构你的一些代码,重命名成员等等,任何仍然使用 old api 但使用模拟而不是真实对象的代码的测试仍然会通过。 这意味着即使您的代码已损坏,您的测试也可以全部通过。

请注意,这是您需要集成测试和单元测试的另一个原因。 孤立地测试一切都很好,但如果你不测试你的单元是如何“连接在一起”的,那么测试可能发现的错误仍有很大的空间。

mock 已经提供了一个功能来帮助解决这个问题,称为speccing。 如果您使用类或实例作为模拟的 spec,那么您只能访问真实类中存在的模拟上的属性:

>>> from urllib import request
>>> mock = Mock(spec=request.Request)
>>> mock.assret_called_with
Traceback (most recent call last):
 ...
AttributeError: Mock object has no attribute 'assret_called_with'

该规范仅适用于模拟本身,因此模拟上的任何方法仍然存在相同的问题:

>>> mock.has_data()
<mock.Mock object at 0x...>
>>> mock.has_data.assret_called_with()

Auto-speccing 解决了这个问题。 您可以将 autospec=True 传递给 patch() / patch.object() 或使用 create_autospec() 函数创建一个模拟规范 如果将 autospec=True 参数用于 patch(),则被替换的对象将用作规范对象。 因为规范是“惰性”完成的(规范是在访问模拟上的属性时创建的),您可以将其与非常复杂或深度嵌套的对象(如导入模块的模块)一起使用,而不会对性能造成很大的影响。

这是它的使用示例:

>>> 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 有一个规范。 request.Request 在构造函数中接受两个参数(其中一个是 self)。 如果我们尝试错误地调用它,会发生以下情况:

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

该规范也适用于实例化的类(即 指定模拟的返回值):

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

Request 对象不可调用,因此实例化我们模拟的 request.Request 的返回值是不可调用的模拟。 有了规范,我们断言中的任何拼写错误都会引发正确的错误:

>>> 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 更改而导致的错误。

除了使用 autospecpatch() 之外,还有一个 create_autospec() 用于直接创建 autospecced 模拟:

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

然而,这并非没有警告和限制,这就是为什么它不是默认行为的原因。 为了知道规范对象上有哪些属性可用,autospec 必须自省(访问属性)规范。 当您遍历模拟上的属性时,原始对象的相应遍历正在发生。 如果您指定的任何对象具有可以触发代码执行的属性或描述符,那么您可能无法使用 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'

有几种不同的方法可以解决这个问题。 最简单但不一定最不烦人的方法是在创建后简单地在模拟上设置所需的属性。 仅仅因为 autospec 不允许您获取规范中不存在的属性,它不会阻止您设置它们:

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

specautospec 有一个更激进的版本,does 阻止您设置不存在的属性。 如果你想确保你的代码也只有 sets 有效属性,这很有用,但显然它可以防止这种特殊情况:

>>> 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__() 中初始化的实例成员添加类属性作为默认值。 请注意,如果您只在 __init__() 中设置默认属性,那么通过类属性(当然在实例之间共享)提供它们也会更快。 例如

class Something:
    a = 33

这就引出了另一个问题。 为稍后将成为不同类型对象的成员提供默认值 None 是比较常见的。 None 作为规范是无用的,因为它不会让你访问 任何 属性或方法。 由于 Nonenever 将作为规范有用,并且可能表示通常属于其他类型的成员,因此 autospec 不会为设置为 [ X188X]。 这些只是普通的模拟(好吧 - MagicMocks):

>>> class Something:
...     member = None
...
>>> mock = create_autospec(Something)
>>> mock.member.foo.bar.baz()
<MagicMock name='mock.member.foo.bar.baz()' id='...'>

如果修改您的生产类以添加默认值不符合您的喜好,那么还有更多选择。 其中之一就是简单地使用实例作为规范而不是类。 另一种是创建生产类的子类,并在不影响生产类的情况下为子类添加默认值。 这两者都要求您使用替代对象作为规范。 幸好 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
这仅适用于类或已经实例化的对象。 调用一个模拟类来创建一个模拟实例 并不会 创建一个真正的实例。 仅完成属性查找 - 以及对 dir() 的调用。