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: missing a required argument: 'b'

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

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

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

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

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

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

  • unsafe: 默认情况下,访问任何名称以 *assert*、*assret*、*asert*、*aseert* 或 *assrt* 开头的属性将引发 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.
Calls: [call(), call()].

在 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.
Calls: [call('foo', bar='baz'), call('other', bar='values')].
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* 为假,则调用必须是顺序的。在指定调用之前或之后可以有额外的调用。

如果 *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.
Calls: [call()].

在 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

当你想要进行一系列重用同一对象的断言时,这会很有用。

return_value 参数设置为 True 时,它会重置 return_value

>>> mock = Mock(return_value=5)
>>> mock('hello')
5
>>> mock.reset_mock(return_value=True)
>>> mock('hello')
<Mock name='mock()' id='...'>

side_effect 参数设置为 True 时,它会重置 side_effect

>>> mock = Mock(side_effect=ValueError)
>>> mock('hello')
Traceback (most recent call last):
  ...
ValueError
>>> mock.reset_mock(side_effect=True)
>>> mock('hello')
<Mock name='mock()' id='...'>

请注意,reset_mock() 默认情况下 不会 清除 return_valueside_effect 或你使用正常赋值设置的任何子属性。

子模拟也会被重置。

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

mock_add_spec(spec, spec_set=False)

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

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

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(如果模拟对象尚未被调用),要么是模拟对象上次被调用时使用的参数。这将以元组的形式出现:第一个成员(也可以通过 args 属性访问)是模拟对象被调用时使用的任何位置参数(或空元组),第二个成员(也可以通过 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__ 会返回 spec 类。这使得模拟对象能够通过它们替换/伪装的对象进行 isinstance() 测试。

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

__class__ 是可赋值的,这允许模拟通过 isinstance() 检查而无需强制你使用 spec。

>>> 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()。当 自动规范化 时,它也将适用于模拟对象上的方法调用。

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

注意

如果 PropertyMock 引发 AttributeError,它将被解释为缺少描述符,并且将对父模拟对象调用 __getattr__()

>>> m = MagicMock()
>>> no_attribute = PropertyMock(side_effect=AttributeError)
>>> type(m).my_property = no_attribute
>>> m.my_property
<MagicMock name='mock.my_property' id='140165240345424'>

有关详细信息,请参阅 __getattr__()

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()
>>> inspect.iscoroutinefunction(mock)
True
>>> inspect.isawaitable(mock())
True

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

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

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

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

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

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

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

MockMagicMockAsyncMockspec 设置为一个包含异步和同步函数的类,将自动检测同步函数并将其设置为 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()

断言模拟对象至少被 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()

断言模拟只被 awaited 一次。

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

断言上次 awaited 是使用指定参数完成的。

>>> 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 await not found.
Expected: mock('other')
Actual: mock('foo', bar='bar')
assert_awaited_once_with(*args, **kwargs)

断言模拟对象只被 awaited 一次,并且使用了指定的参数。

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

断言模拟曾被使用指定的参数 awaited。

>>> 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 调用。将检查 await_args_list 列表以查找 awaits。

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

如果 *any_order* 为 true,则 awaits 可以是任何顺序,但它们都必须出现在 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()

断言模拟对象从未被 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

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

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

这要么是 None(如果模拟对象尚未被 awaited),要么是模拟对象上次被 awaited 时使用的参数。功能与 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

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

>>> 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')]
class unittest.mock.ThreadingMock(spec=None, side_effect=None, return_value=DEFAULT, wraps=None, name=None, spec_set=None, unsafe=False, *, timeout=UNSET, **kwargs)

MagicMock 的一个版本,用于多线程测试。ThreadingMock 对象提供了额外的方法来等待调用被调用,而不是立即断言。

默认超时由 timeout 参数指定,如果未设置,则由 ThreadingMock.DEFAULT_TIMEOUT 属性指定,默认为阻塞 (None)。

您可以通过设置 ThreadingMock.DEFAULT_TIMEOUT 来配置全局默认超时。

wait_until_called(*, timeout=UNSET)

等待直到模拟对象被调用。

如果在创建模拟时传递了超时时间,或者如果将超时参数传递给此函数,则如果调用未及时执行,则该函数会引发 AssertionError

>>> mock = ThreadingMock()
>>> thread = threading.Thread(target=mock)
>>> thread.start()
>>> mock.wait_until_called(timeout=1)
>>> thread.join()
wait_until_any_call_with(*args, **kwargs)

等待直到使用指定参数调用模拟对象。

如果在创建模拟时传递了超时时间,则如果调用未及时执行,函数会引发 AssertionError

>>> mock = ThreadingMock()
>>> thread = threading.Thread(target=mock, args=("arg1", "arg2",), kwargs={"arg": "thing"})
>>> thread.start()
>>> mock.wait_until_any_call_with("arg1", "arg2", arg="thing")
>>> thread.join()
DEFAULT_TIMEOUT

创建 ThreadingMock 实例的全局默认超时时间(秒)。

在 3.13 版本加入。

调用

模拟对象是可调用的。调用将返回设置为 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 内部返回 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”是 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 语句一起使用或作为类装饰器使用。

补丁

备注

关键是在正确的命名空间中进行修补。请参阅 何处修补 部分。

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

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

如果补丁为您创建了一个 MagicMock,则 specspec_set 关键字参数会传递给它。

此外,您可以传递 spec=Truespec_set=True,这会导致补丁将要被模拟的对象作为 spec/spec_set 对象传递。

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

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

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

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

备注

版本 3.5 中更改: 如果您在模块中修补内置函数,则无需传递 create=True,它将默认添加。

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

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

patch() 接受任意关键字参数。如果修补的对象是异步的,这些参数将传递给 AsyncMock,否则传递给 MagicMock,如果指定了 new_callable,则传递给 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() 正在替换一个 ,那么创建的模拟对象的返回值将具有相同的 spec。

>>> 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() 正在为您创建一个模拟对象时,通常您需要做的第一件事是配置该模拟对象。一些配置可以在对 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() 可以用作装饰器、类装饰器或上下文管理器。参数 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() 的含义相同。

patch.dict

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

修补一个字典或类似字典的对象,并在测试后将字典恢复到其原始状态,其中恢复的字典是测试前字典的副本。

in_dict 可以是一个字典或一个类似映射的容器。如果它是一个映射,那么它至少必须支持获取、设置和删除项以及遍历键。

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

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

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

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 settings 对象

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 关键字参数来创建模拟,那么尝试设置一个不在 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__

  • Pickling:__reduce__, __reduce_ex__, __getinitargs__, __getnewargs__, __getstate____setstate__

  • 文件系统路径表示:__fspath__

  • 异步迭代方法:__aiter____anext__

版本 3.8 中更改: 增加了对 os.PathLike.__fspath__() 的支持。

版本 3.8 中更改: 增加了对 __aenter__, __aexit__, __aiter____anext__ 的支持。

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

  • __getattr__, __setattr__, __init____new__

  • __prepare__, __instancecheck__, __subclasscheck__, __del__

魔术模拟

有两种 MagicMock 变体:MagicMockNonCallableMagicMock

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

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

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

如果您使用 specspec_set 参数,那么只有存在于 spec 中的魔术方法才会被创建。

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 函数使用,以指示应使用正常返回值。

调用

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 对象以及您自己构造的对象是 (名称, 位置参数, 关键字参数) 的三元组。

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

>>> 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 对象上的相应属性作为其规范。

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

如果 spec_setTrue,那么尝试设置在规范对象上不存在的属性将引发 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)) (类型成员) 来绕过过滤,而无论 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 版中已更改: read_data 现在在每次调用 mock 时都会重置。

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 之前,带有“assert”单词拼写错误的测试会静默通过,而实际上应该引发错误。您仍然可以通过向 Mock 传递 unsafe=True 来实现此行为。

请注意,这也是您需要集成测试和单元测试的另一个原因。独立测试一切都很好,但如果您不测试您的单元如何“连接在一起”,仍然存在许多测试可能已经捕获的错误空间。

unittest.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.header_items()
<mock.Mock object at 0x...>
>>> mock.header_items.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')

在许多情况下,您只需在现有的 patch() 调用中添加 autospec=True,即可防止由于拼写错误和 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='...'>

然而,这并非没有注意事项和限制,这也是它不是默认行为的原因。为了了解规范对象上有哪些属性可用,自动规范必须内省(访问属性)规范。当您遍历模拟对象上的属性时,原始对象会在底层进行相应的遍历。如果您的任何规范对象具有可能触发代码执行的属性或描述符,那么您可能无法使用自动规范。另一方面,更好地设计您的对象以使内省安全会好得多 [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

这引出了另一个问题。通常情况下,对于稍后将是不同类型对象的成员,会提供默认值 NoneNone 作为规范将毫无用处,因为它不允许您访问其上 任何 属性或方法。由于 None 永远 不会作为规范有用,并且可能表示通常是其他类型的成员,因此自动规范不会为设置为 None 的成员使用规范。这些将只是普通的模拟对象(好吧 - MagicMock)。

>>> 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_valuewraps 的优先级顺序

它们的优先级顺序是

  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