unittest.mock — 模拟对象库

3.3 版新增。

**源代码:** Lib/unittest/mock.py


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

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

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

Mock 旨在与 unittest 一起使用,并且基于“操作 -> 断言”模式,而不是许多模拟框架使用的“记录 -> 回放”模式。

早期版本的 Python 有一个 unittest.mock 的反向移植,在 PyPI 上以 mock 的形式提供。

快速指南

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 允许您执行副作用,包括在调用模拟时引发异常

>>> from unittest.mock import Mock
>>> 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()

注意

当您嵌套 patch 装饰器时,模拟将按照它们应用的相同顺序(应用装饰器的正常 *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,您可以使用自动规范。自动规范可以通过 patch 的 *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__ 方法的签名。

Mock 类

Mock 是一个灵活的模拟对象,旨在替换整个代码中对桩模块和测试替身的的使用。模拟是可调用的,并在您访问它们时创建属性作为新的模拟 [1]。访问相同的属性将始终返回相同的模拟。模拟会记录您如何使用它们,从而允许您对代码对它们执行的操作进行断言。

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 的更严格变体。如果使用,则尝试在作为 spec_set 传入的对象上不存在的模拟对象上*设置*或获取属性将引发 AttributeError

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

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

    如果 side_effect 是一个可迭代对象,则每次调用模拟都将返回可迭代对象中的下一个值。

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

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

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

    在 3.5 版中添加。

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

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

  • name:如果模拟对象有名称,则该名称将用于模拟对象的 repr 中。这对于调试很有用。该名称将传播到子模拟对象。

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

assert_called()

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

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

在 3.6 版中添加。

assert_called_once()

断言模拟被调用了恰好一次。

>>> 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)

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

如果模拟*曾经*被调用过,则断言通过,这与 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* 为 true,则调用可以是任何顺序,但它们必须全部出现在 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() 不会 清除 return_valueside_effect 或您使用默认赋值设置的任何子属性。如果您想重置 return_valueside_effect,请将相应的参数传递为 True。子模拟对象和返回值模拟对象(如果有)也会被重置。

注意

return_valueside_effect 是仅限关键字的参数。

mock_add_spec(spec, spec_set=False)

向模拟对象添加规范。*spec* 可以是对象或字符串列表。只有 *spec* 上的属性才能作为模拟对象的属性获取。

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

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。下面是一个示例,它将调用模拟对象时使用的值加 1 并返回它。

>>> 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 最后一次被调用的参数。这将采用元组的形式:第一个成员(也可以通过 args 属性访问)是调用 mock 时使用的任何有序参数(或空元组),第二个成员(也可以通过 kwargs 属性访问)是任何关键字参数(或空字典)。

>>> 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.call_args.args
(3, 4)
>>> mock.call_args.kwargs
{}
>>> mock(3, 4, 5, key='fish', next='w00t!')
>>> mock.call_args
call(3, 4, 5, key='fish', next='w00t!')
>>> mock.call_args.args
(3, 4, 5)
>>> mock.call_args.kwargs
{'key': 'fish', 'next': 'w00t!'}

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

在 3.8 版更改: 添加了 argskwargs 属性。

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.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 记录对模拟对象、其方法、魔术方法*和*返回值模拟的*所有*调用。

>>> 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_value* 和 *side_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()。当进行自动指定时,它也将应用于模拟对象上的方法调用。

版本 3.4 中的变化: 添加了对指定和自动指定模拟对象的签名自省。

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

一个打算用作类上的 property 或其他 描述符 的模拟。 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()
class unittest.mock.AsyncMock(spec=None, side_effect=None, return_value=DEFAULT, wraps=None, name=None, spec_set=None, unsafe=False, **kwargs)

MagicMock 的异步版本。 AsyncMock 对象的行为方式将使该对象被识别为异步函数,并且调用的结果是可等待的。

>>> mock = AsyncMock()
>>> asyncio.iscoroutinefunction(mock)
True
>>> inspect.isawaitable(mock())  
True

mock() 的结果是一个异步函数,它在被等待后将具有 side_effectreturn_value 的结果

  • 如果 side_effect 是一个函数,异步函数将返回该函数的结果,

  • 如果 side_effect 是一个异常,异步函数将引发该异常,

  • 如果 side_effect 是一个可迭代对象,异步函数将返回可迭代对象的下一个值,但是,如果结果序列已耗尽,则会立即引发 StopAsyncIteration

  • 如果未定义 side_effect,异步函数将返回由 return_value 定义的值,因此,默认情况下,异步函数返回一个新的 AsyncMock 对象。

MockMagicMock 的 *spec* 设置为异步函数将导致在调用后返回协程对象。

>>> async def async_func(): pass
...
>>> mock = MagicMock(async_func)
>>> mock
<MagicMock spec='function' id='...'>
>>> mock()  
<coroutine object AsyncMockMixin._mock_call at ...>

MockMagicMockAsyncMock 的 *spec* 设置为具有异步和同步函数的类将自动检测同步函数,并将它们设置为 MagicMock(如果父模拟是 AsyncMockMagicMock)或 Mock(如果父模拟是 Mock)。所有异步函数都将是 AsyncMock

>>> class ExampleClass:
...     def sync_foo():
...         pass
...     async def async_foo():
...         pass
...
>>> a_mock = AsyncMock(ExampleClass)
>>> a_mock.sync_foo
<MagicMock name='mock.sync_foo' id='...'>
>>> a_mock.async_foo
<AsyncMock name='mock.async_foo' id='...'>
>>> mock = Mock(ExampleClass)
>>> mock.sync_foo
<Mock name='mock.sync_foo' id='...'>
>>> mock.async_foo
<AsyncMock name='mock.async_foo' id='...'>

3.8 版新增。

assert_awaited()

断言模拟至少被等待过一次。请注意,这与对象已被调用的情况是分开的,必须使用 await 关键字

>>> mock = AsyncMock()
>>> async def main(coroutine_mock):
...     await coroutine_mock
...
>>> coroutine_mock = mock()
>>> mock.called
True
>>> mock.assert_awaited()
Traceback (most recent call last):
...
AssertionError: Expected mock to have been awaited.
>>> asyncio.run(main(coroutine_mock))
>>> mock.assert_awaited()
assert_awaited_once()

断言模拟被等待过一次。

>>> mock = AsyncMock()
>>> async def main():
...     await mock()
...
>>> asyncio.run(main())
>>> mock.assert_awaited_once()
>>> asyncio.run(main())
>>> mock.method.assert_awaited_once()
Traceback (most recent call last):
...
AssertionError: Expected mock to have been awaited once. Awaited 2 times.
assert_awaited_with(*args, **kwargs)

断言最后一次等待使用的是指定的参数。

>>> mock = AsyncMock()
>>> async def main(*args, **kwargs):
...     await mock(*args, **kwargs)
...
>>> asyncio.run(main('foo', bar='bar'))
>>> mock.assert_awaited_with('foo', bar='bar')
>>> mock.assert_awaited_with('other')
Traceback (most recent call last):
...
AssertionError: expected call not found.
Expected: mock('other')
Actual: mock('foo', bar='bar')
assert_awaited_once_with(*args, **kwargs)

断言模拟被等待过一次,并且使用的是指定的参数。

>>> mock = AsyncMock()
>>> async def main(*args, **kwargs):
...     await mock(*args, **kwargs)
...
>>> asyncio.run(main('foo', bar='bar'))
>>> mock.assert_awaited_once_with('foo', bar='bar')
>>> asyncio.run(main('foo', bar='bar'))
>>> mock.assert_awaited_once_with('foo', bar='bar')
Traceback (most recent call last):
...
AssertionError: Expected mock to have been awaited once. Awaited 2 times.
assert_any_await(*args, **kwargs)

断言使用指定参数等待过模拟对象。

>>> mock = AsyncMock()
>>> async def main(*args, **kwargs):
...     await mock(*args, **kwargs)
...
>>> asyncio.run(main('foo', bar='bar'))
>>> asyncio.run(main('hello'))
>>> mock.assert_any_await('foo', bar='bar')
>>> mock.assert_any_await('other')
Traceback (most recent call last):
...
AssertionError: mock('other') await not found
assert_has_awaits(calls, any_order=False)

断言已使用指定的调用等待过模拟对象。将检查 await_args_list 列表中是否存在等待。

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

如果 any_order 为 true,则等待可以是任何顺序,但它们必须全部出现在 await_args_list 中。

>>> mock = AsyncMock()
>>> async def main(*args, **kwargs):
...     await mock(*args, **kwargs)
...
>>> calls = [call("foo"), call("bar")]
>>> mock.assert_has_awaits(calls)
Traceback (most recent call last):
...
AssertionError: Awaits not found.
Expected: [call('foo'), call('bar')]
Actual: []
>>> asyncio.run(main('foo'))
>>> asyncio.run(main('bar'))
>>> mock.assert_has_awaits(calls)
assert_not_awaited()

断言从未等待过模拟对象。

>>> mock = AsyncMock()
>>> mock.assert_not_awaited()
reset_mock(*args, **kwargs)

参见 Mock.reset_mock()。同时将 await_count 设置为 0,将 await_args 设置为 None,并清除 await_args_list

await_count

一个整数,用于跟踪模拟对象被等待的次数。

>>> mock = AsyncMock()
>>> async def main():
...     await mock()
...
>>> asyncio.run(main())
>>> mock.await_count
1
>>> asyncio.run(main())
>>> mock.await_count
2
await_args

它要么是 None(如果模拟对象尚未被等待),要么是上次等待模拟对象时使用的参数。其功能与 Mock.call_args 相同。

>>> mock = AsyncMock()
>>> async def main(*args):
...     await mock(*args)
...
>>> mock.await_args
>>> asyncio.run(main('foo'))
>>> mock.await_args
call('foo')
>>> asyncio.run(main('bar'))
>>> mock.await_args
call('bar')
await_args_list

这是一个列表,包含按顺序对模拟对象进行的所有等待(因此列表的长度是它被等待的次数)。在进行任何等待之前,它是一个空列表。

>>> mock = AsyncMock()
>>> async def main(*args):
...     await mock(*args)
...
>>> mock.await_args_list
[]
>>> asyncio.run(main('foo'))
>>> mock.await_args_list
[call('foo')]
>>> asyncio.run(main('bar'))
>>> mock.await_args_list
[call('foo'), call('bar')]

调用

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

对对象进行的调用将记录在属性中,例如 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 = (33, ValueError, 66)
>>> m = MagicMock(side_effect=iterable)
>>> m()
33
>>> m()
Traceback (most recent call last):
 ...
ValueError
>>> m()
66

删除属性

模拟对象按需创建属性。这使得它们可以假装成任何类型的对象。

您可能希望模拟对象对 hasattr() 调用返回 False,或者在获取属性时引发 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

模拟名称和 name 属性

由于“name”是 Mock 构造函数的参数,因此如果您希望模拟对象具有“name”属性,则不能仅在创建时将其传入。有两种选择。一种选择是使用 configure_mock()

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

更简单的选择是在创建模拟对象后简单地设置“name”属性。

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

将模拟对象作为属性附加

当您将一个模拟对象作为另一个模拟对象的属性(或作为返回值)附加时,它将成为该模拟对象的“子级”。对子级的调用记录在父级的 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')]

修补器

补丁装饰器用于在它们装饰的函数范围内修补对象。它们会自动处理取消修补操作,即使出现异常也是如此。所有这些函数也可以在 with 语句中使用,或作为类装饰器使用。

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,则如果被修补的对象是异步函数,则 target 将被替换为 AsyncMock,否则将被替换为 MagicMock。如果 patch() 用作装饰器并且省略了 new,则创建的模拟对象将作为额外参数传递给装饰的函数。如果 patch() 用作上下文管理器,则创建的模拟对象将由上下文管理器返回。

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

如果 patch 正在为您创建一个 MagicMock,则 specspec_set 关键字参数将传递给它。

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

new_callable 允许您指定一个不同的类或可调用对象,它将被调用以创建 new 对象。默认情况下,AsyncMock 用于异步函数,MagicMock 用于其他情况。

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

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

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

注意

版本 3.5 中的变化: 如果要修补模块中的内置函数,则不需要传递 create=True,它将默认添加。

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

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

patch() 接受任意关键字参数。如果修补的对象是异步的,这些参数将传递给 AsyncMock,否则传递给 MagicMock,或者如果指定了 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'
...

如果您使用 *spec* 或 *spec_set* 并且 patch() 正在替换一个*类*,则创建的模拟对象的返回值将具有相同的规范。

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

当您想为创建的模拟对象使用默认 MagicMock 以外的替代类时,*new_callable* 参数很有用。例如,如果您想使用 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() 为您创建模拟对象时,您通常需要做的第一件事是配置该模拟对象。其中一些配置可以在调用 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

默认情况下,尝试修补模块中不存在的函数(或类中的方法或属性)将失败并引发 AttributeError

>>> @patch('sys.non_existing_attribute', 42)
... def test():
...     assert sys.non_existing_attribute == 42
...
>>> test()
Traceback (most recent call last):
  ...
AttributeError: <module 'sys' (built-in)> does not have the attribute 'non_existing_attribute'

但在调用 patch() 时添加 create=True 将使前面的示例按预期工作

>>> @patch('sys.non_existing_attribute', 42, create=True)
... def test(mock_stdout):
...     assert sys.non_existing_attribute == 42
...
>>> test()

在 3.8 版更改: 如果目标是异步函数,则 patch() 现在返回一个 AsyncMock

patch.object

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

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

patch.object() 可以用作装饰器、类装饰器或上下文管理器。参数 *new*、*spec*、*create*、*spec_set*、*autospec* 和 *new_callable* 与 patch() 的含义相同。与 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()

patch.object() 的 *spec*、*create* 和其他参数的含义与它们在 patch() 中的含义相同。

patch.dict

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

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

in_dict 可以是字典或类似映射的容器。如果是映射,则它必须至少支持获取、设置和删除项目以及迭代键。

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

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

如果 clear 为 true,则在设置新值之前,字典将被清空。

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

在 3.8 版更改: 当用作上下文管理器时,patch.dict() 现在返回已修补的字典。

patch.dict() 可以用作上下文管理器、装饰器或类装饰器。

>>> foo = {}
>>> @patch.dict(foo, {'newkey': 'newvalue'})
... def test():
...     assert foo == {'newkey': 'newvalue'}
...
>>> test()
>>> assert foo == {}

当用作类装饰器时,patch.dict() 会遵循 patch.TEST_PREFIX(默认为 'test')来选择要包装的方法。

>>> import os
>>> import unittest
>>> from unittest.mock import patch
>>> @patch.dict('os.environ', {'newkey': 'newvalue'})
... class TestSample(unittest.TestCase):
...     def test_sample(self):
...         self.assertEqual(os.environ['newkey'], 'newvalue')

如果要对测试使用不同的前缀,可以通过设置 patch.TEST_PREFIX 来通知修补程序不同的前缀。有关如何更改值的更多详细信息,请参阅 TEST_PREFIX

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

>>> foo = {}
>>> with patch.dict(foo, {'newkey': 'newvalue'}) as patched_foo:
...     assert foo == {'newkey': 'newvalue'}
...     assert patched_foo == {'newkey': 'newvalue'}
...     # You can add, update or delete keys of foo (or patched_foo, it's the same dict)
...     patched_foo['spam'] = 'eggs'
...
>>> assert foo == {}
>>> assert patched_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']

patch.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_callablepatch() 具有相同的含义。这些参数将应用于 patch.multiple() 完成的*所有*修补。

当用作类装饰器时,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
...

修补方法:start 和 stop

所有修补程序都有 start()stop() 方法。这些方法可以更轻松地在 setUp 方法中或在要执行多个修补而无需嵌套装饰器或 with 语句的情况下进行修补。

要使用它们,请照常调用 patch()patch.object()patch.dict(),并保留对返回的 patcher 对象的引用。然后,可以调用 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(unittest.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(unittest.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 启动的补丁。

补丁内置函数

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

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

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

嵌套补丁装饰器

如果要执行多个补丁,则只需堆叠装饰器即可。

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

>>> @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 应用装饰器的标准方式。传递给测试函数的已创建模拟的顺序与此顺序匹配。

在哪里打补丁

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')

但是,请考虑另一种情况,模块 b 不是执行 from a import SomeClass 而是执行 import a,并且 some_function 使用 a.SomeClass。这两种导入形式都很常见。在这种情况下,我们要修补的类正在模块中查找,因此我们必须改为修补 a.SomeClass

@patch('a.SomeClass')

修补描述符和代理对象

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

MagicMock 和魔术方法支持

模拟魔术方法

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__

  • __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____delete__

  • 序列化: __reduce____reduce_ex____getinitargs____getnewargs____getstate____setstate__

  • 文件系统路径表示: __fspath__

  • 异步迭代方法: __aiter____anext__

版本 3.8 中的变化: 添加了对 os.PathLike.__fspath__() 的支持。

版本 3.8 中的变化: 添加了对 __aenter____aexit____aiter____anext__ 的支持。

以下方法存在,但*不支持*,因为它们要么正在被 mock 使用,要么无法动态设置,要么会导致问题

  • __getattr____setattr____init____new__

  • __prepare____instancecheck____subclasscheck____del__

魔术模拟对象

有两种 MagicMock 变体: MagicMockNonCallableMagicMock

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

MagicMockMock 的子类,它为大多数 魔术方法 提供了默认实现。你可以使用 MagicMock,而无需自己配置魔术方法。

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

如果使用 specspec_set 参数,则*只*会创建规范中存在的魔术方法。

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__: 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__: 模拟对象的默认哈希值

  • __str__: 模拟对象的默认字符串表示形式

  • __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__

辅助函数

sentinel

unittest.mock.sentinel

sentinel 对象提供了一种为测试提供唯一对象的便捷方法。

属性是在您按名称访问它们时按需创建的。访问相同的属性将始终返回相同的对象。返回的对象具有合理的 repr,以便测试失败消息可读。

在 3.7 版更改: 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.DEFAULT)。side_effect 函数可以使用它来指示应该使用正常的返回值。

call

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

call() 是一个辅助对象,用于进行更简单的断言,用于与 call_argscall_args_listmock_callsmethod_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_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.mock_calls 属性中的 call 对象可以进行自省以获取它们包含的各个参数。

Mock.call_argsMock.call_args_list 中的 call 对象是 (位置参数、关键字参数) 的二元组,而 Mock.mock_calls 中的 call 对象以及您自己构造的 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_set* 为 True,则尝试设置规范对象上不存在的属性将引发 AttributeError

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

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

有关如何将自动规范与 create_autospec() 以及 patch() 的 *autospec* 参数一起使用的示例,请参阅 自动规范

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

启用过滤后,dir(some_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())
['_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 如何。

mock_open

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 版更改: 现在在每次调用 mock 时都会重置 read_data

3.8 版更改: 在实现中添加了 __iter__(),以便迭代(例如在 for 循环中)正确消耗 read_data

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

with open('/some/path', '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'

自动指定

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

在解释自动指定的工作原理之前,我先解释一下为什么需要它。

Mock 是一个非常强大和灵活的对象,但它存在一个模拟普遍存在的缺陷。如果您重构了一些代码、重命名了成员等,那么对于仍在使用*旧 api*但使用模拟而不是真实对象的代码的任何测试都将继续通过。这意味着即使您的代码已损坏,您的测试也可能全部通过。

3.5 版更改: 在 3.5 之前,当断言一词中存在拼写错误的测试应该引发错误时,它们会静默地通过。您仍然可以通过将 unsafe=True 传递给 Mock 来实现此行为。

请注意,这是您还需要集成测试和单元测试的另一个原因。单独测试所有内容都很好,但是如果您不测试您的单元是如何“连接在一起”的,那么仍然有很多测试可能已经发现的错误的空间。

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

>>> 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'

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

>>> mock.has_data()
<mock.Mock object at 0x...>
>>> mock.has_data.assret_called_with()  # Intentional typo!

自动指定解决了这个问题。您可以将 autospec=True 传递给 patch() / patch.object(),也可以使用 create_autospec() 函数创建具有规范的模拟。如果您对 patch() 使用 autospec=True 参数,则将使用被替换的对象作为规范对象。因为规范是“延迟”完成的(规范是在访问模拟上的属性时创建的),所以您可以将它与非常复杂或深度嵌套的对象(例如导入导入模块的模块)一起使用,而不会造成很大的性能损失。

以下是一个使用示例

>>> 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  # Intentional typo!
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() 用于直接创建自动指定的模拟

>>> 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 都有一个更激进的版本,它*确实*阻止您设置不存在的属性。如果您还想确保您的代码只*设置*有效的属性,这将非常有用,但显然它会阻止这种情况发生

>>> 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 作为规范将毫无用处,因为它不允许您访问其上的 *任何* 属性或方法。由于 None *永远* 不会用作规范,并且可能表示一个通常是其他类型的成员,因此 autospec 不会为设置为 None 的成员使用规范。这些将只是普通的模拟对象(嗯 - 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='...'>

密封模拟对象

unittest.mock.seal(mock)

当访问被密封的模拟对象的属性或其任何已经是模拟对象的属性时,seal 将禁用自动创建模拟对象,递归进行。

如果将具有名称或规范的模拟实例分配给某个属性,则在密封链中不会考虑该属性。这允许防止 seal 固定模拟对象的一部分。

>>> mock = Mock()
>>> mock.submock.attribute1 = 2
>>> mock.not_submock = mock.Mock(name="sample_name")
>>> seal(mock)
>>> mock.new_attribute  # This will raise AttributeError.
>>> mock.submock.attribute2  # This will raise AttributeError.
>>> mock.not_submock.attribute2  # This won't raise.

在 3.7 版中添加。

side_effectreturn_value 和 *wraps* 的优先级顺序

它们的优先级顺序是

  1. side_effect

  2. return_value

  3. 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'

由于 Noneside_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