unittest.mock
— 模拟对象库¶
在 3.3 版本中添加。
源代码: Lib/unittest/mock.py
unittest.mock
是一个用于 Python 中测试的库。它允许您使用模拟对象替换被测试系统的部分,并断言它们的使用方式。
unittest.mock
提供了一个核心的 Mock
类,无需在整个测试套件中创建大量存根。执行操作后,您可以断言使用了哪些方法/属性以及调用它们的参数。您还可以按正常方式指定返回值并设置所需的属性。
此外,mock 提供了一个 patch()
装饰器,用于处理测试范围内的模块和类级别属性的 patch,以及 sentinel
用于创建唯一对象。请参阅快速入门,了解如何使用 Mock
、MagicMock
和 patch()
的一些示例。
Mock 旨在与 unittest
一起使用,并且基于“操作 -> 断言”模式,而不是许多模拟框架使用的“记录 -> 回放”模式。
有一个适用于早期 Python 版本的 unittest.mock
的反向移植版本,在 PyPI 上作为 mock 提供。
快速入门¶
Mock
和 MagicMock
对象会在您访问它们时创建所有属性和方法,并存储它们的使用方式的详细信息。您可以配置它们,指定返回值或限制可用属性,然后断言它们的使用方式
>>> 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 对象。这通常很简单,但要快速了解,请阅读在哪里进行 patch。
除了作为装饰器外,patch()
还可以用作 with 语句中的上下文管理器
>>> with patch.object(ProductionClass, 'method', return_value=None) as mock_method:
... thing = ProductionClass()
... thing.method(1, 2, 3)
...
>>> mock_method.assert_called_once_with(1, 2, 3)
还有一个 patch.dict()
用于在范围期间设置字典中的值,并在测试结束时将字典恢复到其原始状态
>>> foo = {'key': 'value'}
>>> original = foo.copy()
>>> with patch.dict(foo, {'newkey': 'newvalue'}, clear=True):
... assert foo == {'newkey': 'newvalue'}
...
>>> assert foo == original
Mock 支持模拟 Python 魔术方法。使用魔术方法最简单的方法是使用 MagicMock
类。它允许您执行诸如
>>> mock = MagicMock()
>>> mock.__str__.return_value = 'foobarbaz'
>>> str(mock)
'foobarbaz'
>>> mock.__str__.assert_called_with()
Mock 允许您将函数(或其他 Mock 实例)分配给魔术方法,并且它们将被适当调用。MagicMock
类只是一个 Mock 变体,它为您预先创建了所有魔术方法(好吧,所有有用的方法)。
以下是使用普通 Mock 类使用魔术方法的示例
>>> mock = Mock()
>>> mock.__str__ = Mock(return_value='wheeeeee')
>>> str(mock)
'wheeeeee'
为了确保测试中的模拟对象与它们替换的对象具有相同的 API,您可以使用 自动规范化 (auto-speccing)。自动规范化可以通过 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
是一个灵活的模拟对象,旨在替代代码中使用的桩和测试替身。Mock 是可调用的,并在你访问它们时创建属性作为新的 mock [1]。访问相同的属性将始终返回相同的 mock。Mock 记录你如何使用它们,允许你断言你的代码对它们做了什么。
MagicMock
是 Mock
的子类,所有魔术方法都已预先创建并可以使用。还有一些不可调用的变体,当你模拟不可调用的对象时很有用:NonCallableMock
和 NonCallableMagicMock
。
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 对象的类。这允许 mock 通过isinstance()
测试。spec_set: spec 的更严格的变体。如果使用,尝试在 mock 上设置或获取一个不是作为 spec_set 传递的对象上的属性,将会引发
AttributeError
。side_effect: 一个在调用 Mock 时调用的函数。请参阅
side_effect
属性。用于引发异常或动态更改返回值。该函数使用与 mock 相同的参数调用,除非它返回DEFAULT
,否则此函数的返回值将用作返回值。或者,side_effect 可以是异常类或实例。在这种情况下,当调用 mock 时将引发异常。
如果 side_effect 是一个可迭代对象,那么每次调用 mock 将返回来自可迭代对象的下一个值。
可以通过将其设置为
None
来清除 side_effect。return_value: 调用 mock 时返回的值。默认情况下,这是一个新的 Mock(在首次访问时创建)。请参阅
return_value
属性。unsafe: 默认情况下,访问名称以 assert、assret、asert、aseert 或 assrt 开头的任何属性都将引发
AttributeError
。传递unsafe=True
将允许访问这些属性。在 3.5 版本中添加。
wraps: 要被 mock 对象包装的项目。如果 wraps 不是
None
,则调用 Mock 会将调用传递给被包装的对象(返回真实结果)。对 mock 的属性访问将返回一个 Mock 对象,该对象包装被包装对象的相应属性(因此尝试访问不存在的属性将引发AttributeError
)。如果 mock 设置了显式的 return_value,则调用不会传递给被包装的对象,而是返回 return_value。
name: 如果 mock 有名称,则它将用于 mock 的 repr。这对于调试很有用。该名称会传播到子 mock。
mock 也可以使用任意关键字参数进行调用。这些参数将在创建 mock 后用于设置 mock 的属性。有关详细信息,请参阅
configure_mock()
方法。- assert_called()¶
断言 mock 至少被调用一次。
>>> mock = Mock() >>> mock.method() <Mock name='mock.method()' id='...'> >>> mock.method.assert_called()
在 3.6 版本中添加。
- assert_called_once()¶
断言 mock 只被调用一次。
>>> 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 = 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)¶
断言 mock 已使用指定的参数调用。
如果 mock 曾经 被调用过,则断言通过,这与
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 已使用指定的调用进行调用。检查
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()¶
断言 mock 从未被调用。
>>> 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 = Mock(return_value=None) >>> mock('hello') >>> mock.called True >>> mock.reset_mock() >>> mock.called False
在 3.6 版本中更改: 为 reset_mock 函数添加了两个仅关键字参数。
当你想进行一系列重用同一对象的断言时,这会很有用。请注意,
reset_mock()
默认情况下不会清除return_value
、side_effect
或你使用普通赋值设置的任何子属性。如果你想重置return_value
或side_effect
,则将相应的参数传递为True
。子 mock 和返回值 mock(如果有)也会被重置。注意
return_value 和 side_effect 是仅关键字参数。
- mock_add_spec(spec, spec_set=False)¶
向 mock 添加一个 spec。 spec 可以是对象或字符串列表。只有 spec 上的属性才能作为 mock 的属性获取。
如果 spec_set 为 true,则只能设置 spec 上的属性。
- attach_mock(mock, attribute)¶
将一个 mock 作为此 mock 的属性附加,替换其名称和父级。对附加 mock 的调用将记录在此 mock 的
method_calls
和mock_calls
属性中。
- configure_mock(**kwargs)¶
通过关键字参数设置 mock 上的属性。
可以使用标准点表示法和在方法调用中解包字典来设置子 mock 上的属性以及返回值和副作用
>>> 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
在 mock 的构造函数调用中也可以实现相同的事情
>>> 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()
的存在是为了更容易在创建 mock 后进行配置。
- __dir__()¶
Mock
对象将dir(some_mock)
的结果限制为有用的结果。 对于具有 spec 的 mock,这包括 mock 的所有允许属性。请参阅
FILTER_DIR
了解此过滤的功能以及如何关闭它。
- _get_child_mock(**kw)¶
为属性和返回值创建子 mock。 默认情况下,子 mock 将与父级类型相同。 Mock 的子类可能希望覆盖此方法以自定义创建子 mock 的方式。
对于不可调用的 mock,将使用可调用变体(而不是任何自定义子类)。
- called¶
一个布尔值,表示 mock 对象是否已被调用
>>> mock = Mock(return_value=None) >>> mock.called False >>> mock() >>> mock.called True
- call_count¶
一个整数,告诉您 mock 对象已被调用的次数
>>> mock = Mock(return_value=None) >>> mock.call_count 0 >>> mock() >>> mock() >>> mock.call_count 2
- return_value¶
设置此项以配置调用 mock 时返回的值
>>> mock = Mock() >>> mock.return_value = 'fish' >>> mock() 'fish'
默认返回值是一个 mock 对象,您可以按照正常方式配置它
>>> 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¶
这可以是一个在调用 mock 时调用的函数、一个可迭代对象或要引发的异常(类或实例)。
如果您传入一个函数,它将在调用 mock 时使用相同的参数进行调用,除非该函数返回
DEFAULT
单例,否则对 mock 的调用将返回该函数返回的任何值。 如果该函数返回DEFAULT
,则 mock 将返回其正常值(来自return_value
)。如果您传入一个可迭代对象,它将用于检索一个迭代器,该迭代器必须在每次调用时产生一个值。 该值可以是引发的异常实例,也可以是从对 mock 的调用返回的值(
DEFAULT
处理与函数情况相同)。一个引发异常的 mock 示例(用于测试 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
可以在构造函数中设置。 这是一个将 mock 调用值加一并返回的示例>>> 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_list
、method_calls
和mock_calls
的成员是call
对象。 这些是元组,因此可以解包以获取单个参数并进行更复杂的断言。 请参阅 调用作为元组。在 3.8 版本中更改: 添加了
args
和kwargs
属性。
- call_args_list¶
这是对 mock 对象进行的所有调用的顺序列表(因此列表的长度是它被调用的次数)。在进行任何调用之前,它是一个空列表。
call
对象可用于方便地构造调用列表以与call_args_list
进行比较。>>> mock = Mock(return_value=None) >>> mock() >>> mock(3, 4) >>> mock(key='fish', next='w00t!') >>> mock.call_args_list [call(), call(3, 4), call(key='fish', next='w00t!')] >>> expected = [(), ((3, 4),), ({'key': 'fish', 'next': 'w00t!'},)] >>> mock.call_args_list == expected True
call_args_list
的成员是call
对象。 这些可以解包为元组以获取单个参数。 请参阅 调用作为元组。
- method_calls¶
除了跟踪对自身的调用,mock 还会跟踪对方法和属性的调用,以及它们的方法和属性。
>>> mock = Mock() >>> mock.method() <Mock name='mock.method()' id='...'> >>> mock.property.method.attribute() <Mock name='mock.property.method.attribute()' id='...'> >>> mock.method_calls [call.method(), call.property.method.attribute()]
method_calls
的成员是call
对象。这些对象可以解包为元组以获取各个参数。请参阅 将调用作为元组。
- mock_calls¶
mock_calls
记录对 mock 对象、其方法、魔法方法以及返回值 mock 的所有调用。>>> 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
的 mock 对象,__class__
返回 spec 类。这允许 mock 对象通过它们正在替换/伪装的对象的isinstance()
测试。>>> mock = Mock(spec=3) >>> isinstance(mock, int) True
__class__
是可赋值的,这允许 mock 通过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_value 和 side_effect 除外,它们在不可调用的 mock 上没有意义。
使用类或实例作为 spec
或 spec_set
的 Mock 对象能够通过 isinstance()
测试。
>>> mock = Mock(spec=SomeClass)
>>> isinstance(mock, SomeClass)
True
>>> mock = Mock(spec_set=SomeClass())
>>> isinstance(mock, SomeClass)
True
Mock
类支持模拟魔法方法。有关完整详细信息,请参阅 魔法方法。
mock 类和 patch()
装饰器都接受任意关键字参数进行配置。对于 patch()
装饰器,关键字将传递给正在创建的 mock 的构造函数。关键字参数用于配置 mock 的属性。
>>> m = MagicMock(attribute=3, other='fish')
>>> m.attribute
3
>>> m.other
'fish'
可以使用点号表示法以相同的方式设置子 mock 的返回值和副作用。由于您不能直接在调用中使用点号名称,因此必须创建一个字典并使用 **
解包它。
>>> 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)创建的可调用 mock 将在匹配对 mock 的调用时检查规范对象的签名。因此,无论实际调用的参数是按位置传递还是按名称传递,它都可以匹配实际调用的参数。
>>> 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()
。当 自动规范化 时,它也将应用于 mock 对象的方法调用。
在 3.4 版本中更改: 在规范化和自动规范化的 mock 对象上添加了签名检查。
- class unittest.mock.PropertyMock(*args, **kwargs)¶
一个旨在用作类上的
property
或其他 描述符 的 mock。PropertyMock
提供了__get__()
和__set__()
方法,因此您可以在获取它时指定返回值。从对象获取
PropertyMock
实例会调用 mock,且不带任何参数。设置它会使用正在设置的值调用 mock。>>> 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)]
由于 mock 属性的存储方式,您不能直接将 PropertyMock
附加到 mock 对象。相反,您可以将其附加到 mock 类型对象。
>>> m = MagicMock()
>>> p = PropertyMock(return_value=3)
>>> type(m).foo = p
>>> m.foo
3
>>> p.assert_called_once_with()
注意
如果 PropertyMock
引发 AttributeError
,它将被解释为缺少描述符,并且将在父 mock 上调用 __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() >>> asyncio.iscoroutinefunction(mock) True >>> inspect.isawaitable(mock()) True
mock()
的结果是一个异步函数,该函数在等待后将具有side_effect
或return_value
的结果。如果
side_effect
是一个函数,则异步函数将返回该函数的结果。如果
side_effect
是一个异常,则异步函数将引发该异常。如果
side_effect
是一个可迭代对象,则异步函数将返回该可迭代对象的下一个值,但是,如果结果序列已用完,则会立即引发StopAsyncIteration
。如果未定义
side_effect
,则异步函数将返回由return_value
定义的值,因此,默认情况下,异步函数会返回一个新的AsyncMock
对象。
将
Mock
或MagicMock
的 spec 设置为异步函数将在调用后返回协程对象。>>> async def async_func(): pass ... >>> mock = MagicMock(async_func) >>> mock <MagicMock spec='function' id='...'> >>> mock() <coroutine object AsyncMockMixin._mock_call at ...>
将
Mock
,MagicMock
或AsyncMock
的 spec 设置为一个包含异步和同步函数的类时,会自动检测同步函数,并将其设置为MagicMock
(如果父 mock 是AsyncMock
或MagicMock
) 或Mock
(如果父 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()¶
断言 mock 至少被 await 过一次。请注意,这与对象是否被调用是分开的,必须使用
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 恰好被 await 过一次。
>>> 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)¶
断言最后一次 await 是使用指定的参数。
>>> 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)¶
断言 mock 恰好被 await 过一次且使用了指定的参数。
>>> 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 曾经使用指定的参数被 await 过。
>>> 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)¶
断言 mock 已被 await 且使用了指定的调用。
await_args_list
列表会检查 await 调用。如果 *any_order* 为 false,则 await 调用必须是顺序的。在指定的 await 调用之前或之后可以有额外的调用。
如果 *any_order* 为 true,则 await 调用可以以任何顺序出现,但它们都必须出现在
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 从未被 await 过。
>>> 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 对象被 await 的次数。
>>> mock = AsyncMock() >>> async def main(): ... await mock() ... >>> asyncio.run(main()) >>> mock.await_count 1 >>> asyncio.run(main()) >>> mock.await_count 2
- await_args¶
如果 mock 没有被 await 过,则为
None
,否则为 mock 最后一次被 await 时的参数。功能与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 对象进行的所有 await 调用的列表(因此列表的长度是它被 await 的次数)。在进行任何 await 调用之前,它是一个空列表。
>>> 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)¶
等待直到 mock 被调用。
如果在创建 mock 时传递了超时时间,或者如果将超时参数传递给此函数,则如果未及时执行调用,该函数将引发
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)¶
等待直到 mock 使用指定的参数被调用。
如果在创建模拟对象时传递了超时时间,如果调用未及时执行,该函数将引发
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
属性的值。默认返回值是一个新的模拟对象;它在首次访问返回值时创建(显式或通过调用模拟对象),但它会被存储,并且每次都返回相同的对象。
对对象进行的调用将记录在诸如 call_args
和 call_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 属性¶
由于 “name” 是 Mock
构造函数的参数,如果你希望你的模拟对象具有 “name” 属性,你不能只在创建时将其传入。有两种替代方法。一种方法是使用 configure_mock()
。
>>> mock = MagicMock()
>>> mock.configure_mock(name='my_name')
>>> mock.name
'my_name'
一个更简单的选择是在创建模拟对象后简单地设置 “name” 属性。
>>> mock = MagicMock()
>>> mock.name = "foo"
将模拟对象附加为属性¶
当你将一个模拟对象附加为另一个模拟对象的属性(或作为返回值)时,它会成为该模拟对象的 “子级”。对子级的调用会记录在父级的 method_calls
和 mock_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,则如果修补的对象是异步函数,则目标将替换为
AsyncMock
,否则替换为MagicMock
。如果patch()
用作装饰器并且省略了 new,则创建的模拟对象将作为额外的参数传递给被装饰的函数。如果patch()
用作上下文管理器,则创建的模拟对象将由上下文管理器返回。target 应该是一个字符串,格式为
'package.module.ClassName'
。 target 会被导入,并且指定的对象会被 new 对象替换,因此 target 必须可以从你调用patch()
的环境中导入。目标是在执行被装饰的函数时导入的,而不是在装饰时导入的。如果 patch 为你创建
MagicMock
,则 spec 和 spec_set 关键字参数将传递给它。此外,你可以传递
spec=True
或spec_set=True
,这会导致 patch 将被模拟的对象作为 spec/spec_set 对象传入。new_callable 允许你指定一个不同的类或可调用对象,该对象将被调用以创建新的对象。默认情况下,异步函数使用
AsyncMock
,其余情况使用MagicMock
。spec 的更强大形式是 autospec。 如果你设置
autospec=True
,则将使用被替换对象的 spec 创建 mock。 mock 的所有属性也将具有被替换对象相应属性的 spec。 被 mock 的方法和函数将检查其参数,如果使用错误的签名调用它们,则会引发TypeError
。对于替换类的 mock,它们的返回值(“实例”)将具有与类相同的 spec。 请参阅create_autospec()
函数和 自动 spec。除了使用
autospec=True
,你还可以传递autospec=some_object
来使用任意对象作为 spec,而不是被替换的对象。默认情况下,
patch()
将无法替换不存在的属性。如果你传入create=True
,并且该属性不存在,则 patch 将在调用被 patch 的函数时为你创建该属性,并在被 patch 的函数退出后再次删除它。 这对于针对你的生产代码在运行时创建的属性编写测试非常有用。默认情况下它是关闭的,因为它可能很危险。 启用它后,你可以针对实际上不存在的 API 编写通过的测试!注意
在 3.5 版本中变更: 如果你正在 patch 模块中的内置函数,则不需要传递
create=True
,它将默认添加。Patch 可以用作
TestCase
类装饰器。 它通过装饰类中的每个测试方法来工作。 当你的测试方法共享一组常见的 patching 时,这可以减少样板代码。patch()
通过查找以patch.TEST_PREFIX
开头的方法名称来查找测试。默认情况下,这是'test'
,这与unittest
查找测试的方式相匹配。 你可以通过设置patch.TEST_PREFIX
来指定替代前缀。Patch 可以用作上下文管理器,使用 with 语句。 在这里,patching 应用于 with 语句之后的缩进块。 如果你使用 “as”,则被 patch 的对象将绑定到 “as” 之后的名称;如果
patch()
为你创建一个 mock 对象,则非常有用。patch()
接受任意关键字参数。 如果被 patch 的对象是异步的,则这些参数将传递给AsyncMock
;否则传递给MagicMock
,或者在指定的情况下传递给 new_callable。patch.dict(...)
、patch.multiple(...)
和patch.object(...)
可用于替代用例。
patch()
作为函数装饰器,为你创建 mock 并将其传递到被装饰的函数中
>>> @patch('__main__.SomeClass')
... def function(normal_argument, mock_class):
... print(mock_class is SomeClass)
...
>>> function(None)
True
Patch 类会将该类替换为 MagicMock
实例。如果在测试中的代码中实例化了该类,则将使用 mock 的 return_value
。
如果该类被多次实例化,你可以使用 side_effect
来每次返回一个新的 mock。 或者,你可以将 return_value 设置为任何你想要的值。
要在被 patch 的类的实例的方法上配置返回值,你必须在 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()
正在替换一个类,那么创建的 mock 的返回值将具有相同的 spec。
>>> Original = Class
>>> patcher = patch('__main__.Class', spec=True)
>>> MockClass = patcher.start()
>>> instance = MockClass()
>>> assert isinstance(instance, Original)
>>> patcher.stop()
当你想使用替代类来代替默认的 MagicMock
来创建 mock 时,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()
为你创建 mock 时,通常你需要做的第一件事就是配置 mock。 一些配置可以在调用 patch 时完成。 你传递给调用的任何任意关键字都将用于设置创建的 mock 上的属性
>>> patcher = patch('__main__.thing', first='one', second='two')
>>> mock_thing = patcher.start()
>>> mock_thing.first
'one'
>>> mock_thing.second
'two'
除了创建的 mock 上的属性外,还可以配置子 mock 的属性,例如 return_value
和 side_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
默认情况下,尝试 patch 模块中的函数(或类中的方法或属性),如果它不存在,则会失败并显示 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()
patch.object¶
- patch.object(target, attribute, new=DEFAULT, spec=None, create=False, spec_set=None, autospec=None, new_callable=None, **kwargs)¶
使用 mock 对象 patch 对象 (target) 上指定的成员 (attribute)。
patch.object()
可以用作装饰器、类装饰器或上下文管理器。参数 new、 spec、 create、 spec_set、 autospec 和 new_callable 的含义与patch()
相同。与patch()
一样,patch.object()
接受任意关键字参数来配置它创建的 mock 对象。当用作类装饰器时,
patch.object()
会遵循patch.TEST_PREFIX
来选择要包装的方法。
你可以使用三个参数或两个参数调用 patch.object()
。三个参数的形式接受要被修补的对象、属性名和用于替换该属性的对象。
当使用两个参数的形式调用时,你会省略替换对象,并为你创建一个 mock 对象,并将其作为额外的参数传递给被装饰的函数。
>>> @patch.object(SomeClass, 'class_method')
... def test(mock_method):
... SomeClass.class_method(3)
... mock_method.assert_called_with(3)
...
>>> test()
spec、create 和 patch.object()
的其他参数的含义与它们在 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()
为你创建 mock,请使用DEFAULT
作为值。在这种情况下,创建的 mock 通过关键字传递给被装饰的函数,并且当patch.multiple()
用作上下文管理器时,会返回一个字典。patch.multiple()
可以用作装饰器、类装饰器或上下文管理器。参数 spec、spec_set、create、autospec 和 new_callable 的含义与patch()
相同。这些参数将应用于patch.multiple()
完成的所有修补。当用作类装饰器时,
patch.multiple()
会遵循patch.TEST_PREFIX
来选择要包装的方法。
如果你希望 patch.multiple()
为你创建 mock,那么你可以使用 DEFAULT
作为值。如果你将 patch.multiple()
用作装饰器,那么创建的 mock 将通过关键字传递给被装饰的函数。
>>> 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()
用作上下文管理器,则上下文管理器返回的值是一个字典,其中创建的 mock 按名称键控。
>>> 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
...
patch 方法:start 和 stop¶
所有的补丁器都有 start()
和 stop()
方法。这使得在 setUp
方法中或在您想执行多个补丁而无需嵌套装饰器或 with 语句时进行补丁操作更加简单。
要使用它们,请像往常一样调用 patch()
,patch.object()
或 patch.dict()
,并保留对返回的 patcher
对象的引用。然后,您可以调用 start()
来应用补丁,并调用 stop()
来撤销它。
如果您使用 patch()
来为您创建一个 mock 对象,那么它将由 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
这种方法的典型用例可能是在 TestCase
的 setUp
方法中执行多个补丁。
>>> 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
中引发异常,则不会调用 tearDown
。unittest.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 应用装饰器的标准方式。传递到您的测试函数中的创建的 mock 的顺序与此顺序匹配。
在哪里修补¶
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')
修补描述符和代理对象¶
patch 和 patch.object 都可以正确地修补和恢复描述符:类方法,静态方法和属性。您应该在类而不是实例上修补它们。它们还可以与某些代理属性访问的对象一起使用,例如 django 设置对象。
MagicMock 和魔术方法支持¶
模拟魔术方法¶
Mock
支持模拟 Python 协议方法,也称为 “魔术方法”。这使得 mock 对象可以替换容器或其他实现 Python 协议的对象。
由于魔术方法与普通方法的查找方式不同 [2],因此已专门实现了此支持。这意味着仅支持特定的魔术方法。支持的列表包括几乎所有魔术方法。如果您需要任何缺少的方法,请告知我们。
您可以通过将您感兴趣的方法设置为函数或 mock 实例来模拟魔术方法。如果您使用函数,则它必须将 self
作为第一个参数 [3]。
>>> def __str__(self):
... return 'fooble'
...
>>> mock = Mock()
>>> mock.__str__ = __str__
>>> str(mock)
'fooble'
>>> mock = Mock()
>>> mock.__str__ = Mock()
>>> mock.__str__.return_value = 'fooble'
>>> str(mock)
'fooble'
>>> mock = Mock()
>>> mock.__iter__ = Mock(return_value=iter([]))
>>> list(mock)
[]
此用例之一是模拟在 with
语句中用作上下文管理器的对象。
>>> mock = Mock()
>>> mock.__enter__ = Mock(return_value='foo')
>>> mock.__exit__ = Mock(return_value=False)
>>> with mock as m:
... assert m == 'foo'
...
>>> mock.__enter__.assert_called_with()
>>> mock.__exit__.assert_called_with(None, None, None)
对魔术方法的调用不会出现在 method_calls
中,但它们会记录在 mock_calls
中。
注意
如果您使用 spec 关键字参数创建 mock,则尝试设置 spec 中不存在的魔术方法将引发 AttributeError
。
支持的魔术方法的完整列表是
__hash__
,__sizeof__
,__repr__
和__str__
__dir__
,__format__
和__subclasses__
__round__
,__floor__
,__trunc__
和__ceil__
比较:
__lt__
,__gt__
,__le__
,__ge__
,__eq__
和__ne__
容器方法:
__getitem__
,__setitem__
,__delitem__
,__contains__
,__len__
,__iter__
,__reversed__
和__missing__
上下文管理器:
__enter__
,__exit__
,__aenter__
和__aexit__
一元数字方法:
__neg__
,__pos__
和__invert__
数值方法(包括右侧和原地变体):
__add__
、__sub__
、__mul__
、__matmul__
、__truediv__
、__floordiv__
、__mod__
、__divmod__
、__lshift__
、__rshift__
、__and__
、__xor__
、__or__
和__pow__
数值转换方法:
__complex__
、__int__
、__float__
和__index__
描述符方法:
__get__
、__set__
和__delete__
Pickling:
__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__
Magic Mock¶
有两种 MagicMock
变体:MagicMock
和 NonCallableMagicMock
。
- class unittest.mock.MagicMock(*args, **kw)¶
MagicMock
是Mock
的子类,默认实现了大多数魔法方法。 你可以使用MagicMock
,而无需自己配置魔法方法。构造函数参数的含义与
Mock
相同。如果使用 spec 或 spec_set 参数,则只会创建 spec 中存在的魔法方法。
- class unittest.mock.NonCallableMagicMock(*args, **kw)¶
MagicMock
的不可调用版本。构造函数参数的含义与
MagicMock
相同,但 return_value 和 side_effect 除外,它们在不可调用 mock 上没有意义。
魔法方法使用 MagicMock
对象进行设置,因此您可以像往常一样配置和使用它们
>>> mock = MagicMock()
>>> mock[3] = 'fish'
>>> mock.__setitem__.assert_called_with(3, 'fish')
>>> mock.__getitem__.return_value = 'result'
>>> mock[2]
'result'
默认情况下,许多协议方法都需要返回特定类型的对象。这些方法都预先配置了默认返回值,因此如果您对返回值不感兴趣,则可以在无需执行任何操作的情况下使用它们。如果您想更改默认值,仍然可以手动设置返回值。
方法及其默认值
__lt__
:NotImplemented
__gt__
:NotImplemented
__le__
:NotImplemented
__ge__
:NotImplemented
__int__
:1
__contains__
:False
__len__
:0
__iter__
:iter([])
__exit__
:False
__aexit__
:False
__complex__
:1j
__float__
:1.0
__bool__
:True
__index__
:1
__hash__
: mock 的默认哈希值__str__
: mock 的默认字符串__sizeof__
: mock 的默认大小
例如
>>> 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__
魔法方法应该在类而不是实例上查找。 不同版本的 Python 在应用此规则时存在不一致之处。 受支持的协议方法应适用于所有受支持的 Python 版本。
该函数基本上被连接到类,但是每个 Mock
实例彼此隔离。
助手¶
sentinel¶
- unittest.mock.sentinel¶
sentinel
对象提供了一种方便的方式来为您的测试提供唯一的对象。当您通过名称访问属性时,会按需创建属性。访问同一个属性将始终返回同一个对象。返回的对象具有合理的 repr,以便测试失败消息可读。
有时在测试时,您需要测试是否将特定对象作为参数传递给另一个方法,或者是否被返回。创建命名的标记对象来测试这种情况是很常见的。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_args
、call_args_list
、mock_calls
和method_calls
进行比较。call()
也可以与assert_has_calls()
一起使用。>>> m = MagicMock(return_value=None) >>> m(1, 2, a='foo', b='bar') >>> m() >>> m.call_args_list == [call(1, 2, a='foo', b='bar'), call()] True
- call.call_list()¶
对于表示多次调用的调用对象,
call_list()
返回所有中间调用以及最终调用的列表。
call_list
特别适用于对“链式调用”进行断言。链式调用是同一行代码上的多次调用。这会在 mock 上的 mock_calls
中产生多个条目。手动构建调用序列可能很繁琐。
call_list()
可以从相同的链式调用中构建调用序列
>>> m = MagicMock()
>>> m(1).method(arg='foo').other('bar')(2.0)
<MagicMock name='mock().method().other()()' id='...'>
>>> kall = call(1).method(arg='foo').other('bar')(2.0)
>>> kall.call_list()
[call(1),
call().method(arg='foo'),
call().method().other('bar'),
call().method().other()(2.0)]
>>> m.mock_calls == kall.call_list()
True
call
对象是 (位置参数, 关键字参数) 或 (名称, 位置参数, 关键字参数) 的元组,具体取决于其构造方式。当您自己构造它们时,这并不特别有趣,但是 call
对象位于 Mock.call_args
、 Mock.call_args_list
和 Mock.mock_calls
属性中,可以进行内省以获取它们包含的各个参数。
Mock.call_args
和 Mock.call_args_list
中的 call
对象是 (位置参数, 关键字参数) 的二元组,而 Mock.mock_calls
中的 call
对象以及您自己构造的对象是 (名称, 位置参数, 关键字参数) 的三元组。
您可以使用它们的“元组性”来提取各个参数,以进行更复杂的内省和断言。位置参数是一个元组(如果没有位置参数,则为空元组),关键字参数是一个字典
>>> m = MagicMock(return_value=None)
>>> m(1, 2, 3, arg='one', arg2='two')
>>> kall = m.call_args
>>> kall.args
(1, 2, 3)
>>> kall.kwargs
{'arg': 'one', 'arg2': 'two'}
>>> kall.args is kall[0]
True
>>> kall.kwargs is kall[1]
True
>>> m = MagicMock()
>>> m.foo(4, 5, 6, arg='two', arg2='three')
<MagicMock name='mock.foo()' id='...'>
>>> kall = m.mock_calls[0]
>>> name, args, kwargs = kall
>>> name
'foo'
>>> args
(4, 5, 6)
>>> kwargs
{'arg': 'two', 'arg2': 'three'}
>>> name is m.mock_calls[0][0]
True
create_autospec¶
- unittest.mock.create_autospec(spec, spec_set=False, instance=False, **kwargs)¶
使用另一个对象作为规范来创建 mock 对象。mock 上的属性将使用 spec 对象上的相应属性作为其规范。
将被 mock 的函数或方法将检查它们的参数,以确保使用正确的签名调用它们。
如果 spec_set 为
True
,则尝试设置规范对象上不存在的属性将引发AttributeError
。如果使用类作为规范,则 mock 的返回值(该类的实例)将具有相同的规范。您可以通过传递
instance=True
来使用类作为实例对象的规范。仅当 mock 的实例可调用时,返回的 mock 才是可调用的。create_autospec()
也接受传递给所创建 mock 的构造函数的任意关键字参数。
请参阅 自动规范,了解如何将自动规范与 create_autospec()
和 patch()
的 autospec 参数一起使用。
在 3.8 版本中变更: 如果目标是异步函数,create_autospec()
现在返回 AsyncMock
。
ANY¶
- unittest.mock.ANY¶
有时,您可能需要对 mock 调用的某些参数进行断言,但是要么不关心某些参数,要么想将它们单独从 call_args
中提取出来,并对它们进行更复杂的断言。
要忽略某些参数,您可以传入与任何内容都相等比较的对象。assert_called_with()
和 assert_called_once_with()
的调用无论传入什么都会成功。
>>> mock = Mock(return_value=None)
>>> mock('foo', bar=object())
>>> mock.assert_called_once_with('foo', bar=ANY)
ANY
也可用于与调用列表(如 mock_calls
)进行比较
>>> m = MagicMock(return_value=None)
>>> m(1)
>>> m(1, 2)
>>> m(object())
>>> m.mock_calls == [call(1), call(1, 2), ANY]
True
ANY
不限于与调用对象的比较,因此也可以在测试断言中使用
class TestStringMethods(unittest.TestCase):
def test_split(self):
s = 'hello world'
self.assertEqual(s.split(), ['hello', ANY])
FILTER_DIR¶
- unittest.mock.FILTER_DIR¶
FILTER_DIR
是一个模块级变量,用于控制 mock 对象对 dir()
的响应方式。默认值为 True
,它使用下面描述的过滤,仅显示有用的成员。如果您不喜欢此过滤,或者需要为了诊断目的而将其关闭,请设置 mock.FILTER_DIR = False
。
启用过滤后,dir(some_mock)
仅显示有用的属性,并且会包含任何动态创建的、通常不会显示的属性。如果 mock 是使用 spec (当然也可以是 autospec) 创建的,那么会显示原始对象的所有属性,即使它们尚未被访问。
>>> dir(Mock())
['assert_any_call',
'assert_called',
'assert_called_once',
'assert_called_once_with',
'assert_called_with',
'assert_has_calls',
'assert_not_called',
'attach_mock',
...
>>> from urllib import request
>>> dir(Mock(spec=request))
['AbstractBasicAuthHandler',
'AbstractDigestAuthHandler',
'AbstractHTTPHandler',
'BaseHandler',
...
许多不太有用的(Mock
私有的,而不是被 mock 的对象的私有属性)以下划线和双下划线开头的属性已从在 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)¶
一个辅助函数,用于创建一个 mock 来替换
open()
的使用。它适用于直接调用open()
或用作上下文管理器的情况。mock 参数是要配置的 mock 对象。如果为
None
(默认值),则会为你创建一个MagicMock
,其 API 限制为标准文件句柄上可用的方法或属性。read_data 是一个字符串,用于文件句柄的
read()
、readline()
和readlines()
方法的返回值。对这些方法的调用将从 read_data 中获取数据,直到数据耗尽。这些方法的 mock 非常简单:每次调用 mock 时,read_data 都会倒回到开头。如果你需要更多地控制你提供给被测试代码的数据,你需要自己自定义这个 mock。当这还不够时,PyPI 上的一些内存文件系统包可以为测试提供一个真实的文件系统。在 3.4 版本中更改: 添加了
readline()
和readlines()
的支持。read()
的 mock 更改为消耗 read_data 而不是在每次调用时返回它。在 3.5 版本中更改: 现在,每次调用 mock 时,都会重置 read_data。
在 3.8 版本中更改: 为实现添加了
__iter__()
,以便迭代(例如在 for 循环中)可以正确地消耗 read_data。
使用 open()
作为上下文管理器是确保文件句柄正确关闭的好方法,并且正变得越来越普遍。
with open('/some/path', 'w') as f:
f.write('something')
问题在于,即使你 mock 掉了对 open()
的调用,作为上下文管理器使用的也是返回的对象(并且调用了 __enter__()
和 __exit__()
)。
使用 MagicMock
mock 上下文管理器很常见,而且很麻烦,因此辅助函数很有用。
>>> 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'
自动指定 (Autospeccing)¶
自动指定基于 mock 的现有 spec
功能。它将 mock 的 API 限制为原始对象(spec)的 API,但它是递归的(延迟实现),以便 mock 的属性只具有与 spec 的属性相同的 API。此外,mock 的函数/方法具有与原始函数/方法相同的调用签名,因此如果调用不正确,它们会引发 TypeError
。
在解释自动指定如何工作之前,这里说明为什么需要它。
Mock
是一个非常强大且灵活的对象,但它存在一个 mock 通有的缺陷。如果你重构了一些代码,重命名了成员等等,任何仍在使用旧 API 但使用 mock 而不是真实对象的代码的测试仍然会通过。这意味着即使你的代码已损坏,你的测试仍然可以通过。
在 3.5 版本中更改: 在 3.5 之前,当测试应该引发错误时,如果单词 assert 中存在拼写错误,测试会静默通过。你仍然可以通过将 unsafe=True
传递给 Mock 来实现此行为。
请注意,这是你需要集成测试以及单元测试的另一个原因。单独测试所有内容都很好,但是如果你不测试你的单元是如何“连接在一起的”,仍然有很大的空间可能存在测试可能捕获到的错误。
unittest.mock
已经提供了一个功能来帮助解决这个问题,称为指定 (speccing)。如果你使用类或实例作为 mock 的 spec
,那么你只能访问 mock 上存在于真实类上的属性。
>>> from urllib import request
>>> mock = Mock(spec=request.Request)
>>> mock.assret_called_with # Intentional typo!
Traceback (most recent call last):
...
AttributeError: Mock object has no attribute 'assret_called_with'
spec 仅适用于 mock 本身,因此对于 mock 上的任何方法,我们仍然存在相同的问题。
>>> mock.has_data()
<mock.Mock object at 0x...>
>>> mock.has_data.assret_called_with() # Intentional typo!
自动指定解决了这个问题。你可以将 autospec=True
传递给 patch()
/ patch.object()
或使用 create_autospec()
函数创建具有 spec 的 mock。如果你使用 patch()
的 autospec=True
参数,那么被替换的对象将用作 spec 对象。由于指定是“延迟”完成的(当访问 mock 上的属性时才创建 spec),因此你可以将其与非常复杂或深度嵌套的对象(例如导入模块的模块导入的模块)一起使用,而不会产生很大的性能损失。
这是一个使用示例
>>> from urllib import request
>>> patcher = patch('__main__.request', autospec=True)
>>> mock_request = patcher.start()
>>> request is mock_request
True
>>> mock_request.Request
<MagicMock name='request.Request' spec='Request' id='...'>
你可以看到 request.Request
有一个 spec。request.Request
在构造函数中接受两个参数(其中一个是self)。如果我们尝试不正确地调用它,会发生什么:
>>> req = request.Request()
Traceback (most recent call last):
...
TypeError: <lambda>() takes at least 2 arguments (1 given)
spec 也适用于实例化的类(即,指定的 mock 的返回值)
>>> req = request.Request('foo')
>>> req
<NonCallableMagicMock name='request.Request()' spec='Request' id='...'>
Request
对象不可调用,因此实例化我们 mock 掉的 request.Request
的返回值是不可调用的 mock。有了 spec,我们断言中的任何拼写错误都会引发正确的错误
>>> 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()
用于直接创建自动指定的 mock。
>>> from urllib import request
>>> mock_request = create_autospec(request)
>>> mock_request.Request('foo', 'bar')
<NonCallableMagicMock name='mock.Request()' spec='Request' id='...'>
然而,这并非没有警告和限制,这就是为什么它不是默认行为的原因。为了知道 spec 对象上可用的属性,autospec 必须自省(访问属性)spec。当你遍历 mock 上的属性时,原始对象的相应遍历也在后台发生。如果你的任何指定对象具有可以触发代码执行的属性或描述符,那么你可能无法使用 autospec。另一方面,最好设计你的对象,使自省是安全的 [4]。
一个更严重的问题是,实例属性通常在 __init__()
方法中创建,并且根本不存在于类中。autospec 无法知道任何动态创建的属性,并将 API 限制为可见属性。
>>> class Something:
... def __init__(self):
... self.a = 33
...
>>> with patch('__main__.Something', autospec=True):
... thing = Something()
... thing.a
...
Traceback (most recent call last):
...
AttributeError: Mock object has no attribute 'a'
有几种不同的方法可以解决这个问题。最简单但并非最不麻烦的方法是在创建后简单地在 mock 上设置所需的属性。仅仅因为 autospec 不允许你获取 spec 上不存在的属性,但这并不会阻止你设置它们
>>> with patch('__main__.Something', autospec=True):
... thing = Something()
... thing.a = 33
...
spec 和 autospec 都有一个更激进的版本,它可以阻止你设置不存在的属性。如果你想确保你的代码也只设置有效的属性,这很有用,但显然它会阻止这种特定的情况。
>>> with patch('__main__.Something', autospec=True, spec_set=True):
... thing = Something()
... thing.a = 33
...
Traceback (most recent call last):
...
AttributeError: Mock object has no attribute 'a'
解决这个问题的最好方法可能是为在 __init__()
中初始化的实例成员添加类属性作为默认值。请注意,如果你只是在 __init__()
中设置默认属性,那么通过类属性(当然,在实例之间共享)提供它们也会更快。例如:
class Something:
a = 33
这又引出了另一个问题。为稍后将成为不同类型对象的成员提供 None
的默认值是比较常见的。None
作为 spec 将毫无用处,因为它不允许你访问它的任何属性或方法。由于 None
作为 spec 永远不会有用,并且可能表示通常为其他类型的成员,因此 autospec 不会对设置为 None
的成员使用 spec。这些将只是普通的 mock(嗯 - MagicMocks)。
>>> class Something:
... member = None
...
>>> mock = create_autospec(Something)
>>> mock.member.foo.bar.baz()
<MagicMock name='mock.member.foo.bar.baz()' id='...'>
如果修改你的生产类以添加默认值不合你意,那么还有更多选择。其中一种方法是简单地使用一个实例作为 spec,而不是类。另一种方法是创建生产类的子类,并将默认值添加到子类中,而不影响生产类。这两种方法都需要你使用一个替代对象作为 spec。幸运的是,patch()
支持这一点 - 你可以简单地将替代对象作为 autospec 参数传递。
>>> class Something:
... def __init__(self):
... self.a = 33
...
>>> class SomethingForTest(Something):
... a = 33
...
>>> p = patch('__main__.Something', autospec=SomethingForTest)
>>> mock = p.start()
>>> mock.a
<NonCallableMagicMock name='Something.a' spec='int' id='...'>
这仅适用于类或已实例化的对象。调用一个被 mock 的类来创建一个 mock 实例并不会创建真正的实例。只有属性查找 - 以及对 dir()
的调用 - 才会执行。
密封 mock¶
- unittest.mock.seal(mock)¶
Seal 将禁用在访问被密封的 mock 的属性或其已递归的任何属性时自动创建 mock。
如果一个带有名称或 spec 的 mock 实例被分配给一个属性,它将不会被视为密封链的一部分。这允许你阻止 seal 固定 mock 对象的一部分。
>>> 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_effect
、return_value
和 wraps 的优先级顺序¶
它们的优先级顺序是
wraps
如果全部三个都被设置,mock 将从 side_effect
返回值,忽略 return_value
和被包装的对象。如果设置了任意两个,则优先级较高的那个将返回值。无论哪个先设置,优先级顺序都保持不变。
>>> from unittest.mock import Mock
>>> class Order:
... @staticmethod
... def get_value():
... return "third"
...
>>> order_mock = Mock(spec=Order, wraps=Order)
>>> order_mock.get_value.side_effect = ["first"]
>>> order_mock.get_value.return_value = "second"
>>> order_mock.get_value()
'first'
由于 None
是 side_effect
的默认值,如果你将其值重新分配回 None
,则将在 return_value
和被包装的对象之间检查优先级,忽略 side_effect
。
>>> order_mock.get_value.side_effect = None
>>> order_mock.get_value()
'second'
如果 side_effect
返回的值是 DEFAULT
,它将被忽略,并且优先级顺序将移动到后继者以获取要返回的值。
>>> from unittest.mock import DEFAULT
>>> order_mock.get_value.side_effect = [DEFAULT]
>>> order_mock.get_value()
'second'
当 Mock
包装一个对象时,return_value
的默认值将是 DEFAULT
。
>>> order_mock = Mock(spec=Order, wraps=Order)
>>> order_mock.return_value
sentinel.DEFAULT
>>> order_mock.get_value.return_value
sentinel.DEFAULT
优先级顺序将忽略此值,并且将移动到最后一个后继者,即被包装的对象。
由于实际调用正在被传递到被包装的对象,创建此 mock 的实例将返回该类的真实实例。必须传递被包装的对象所需的任何位置参数(如果有)。
>>> 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
即使你在初始化 mock 时一次设置所有三个,优先级顺序也保持不变。
>>> 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