python / 3.7.2rc1 / all / library-unittest.mock.html

unittest.mock —模拟对象库

版本 3.3 中的新Function。

源代码: Lib/unittest/mock.py


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

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

此外,mock 提供了一个patch()装饰器,该装饰器在测试范围内处理修补模块和类级别的属性,并提供sentinel用于创建唯Pair象。有关如何使用MockMagicMockpatch()的一些示例,请参见quick guide

Mock 非常易于使用,并且设计用于unittest。模拟基于“动作->assert”模式,而不是许多模拟框架所使用的“记录->重放”。

对于较早版本的 Python,有一个unittest.mock的反向端口,可作为在 PyPI 上模拟使用。

Quick Guide

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

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

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

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

模拟还有许多其他方式可以配置和控制其行为。例如,* spec *参数将模拟配置为从另一个对象获取其规格。try访问规范中不存在的模拟属性或方法将失败,并返回AttributeError

patch()装饰器/上下文 Management 器使在被测模块中模拟类或对象变得容易。在测试期间,您指定的对象将被模拟(或其他对象)替换,并在测试结束时恢复:

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

Note

当您嵌套补丁装饰器时,模拟将以它们应用的 Sequences(装饰器的正常* Python *Sequences)传递给装饰的函数。这意味着从下至上,因此在上面的示例中,首先传入了module.ClassName1的模拟。

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

装饰器patch()可以在 with 语句中用作上下文 Management 器:

>>> 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 magic methods的模拟。使用魔术方法的最简单方法是使用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。可以pass patch 的* autospec *参数或create_autospec()函数来实现自动指定。自动指定会创建模拟对象,这些模拟对象具有与要替换的对象相同的属性和方法,并且任何函数和方法(包括构造函数)都具有与真实对象相同的调用签名。

这可以确保如果使用不当,模拟将以与生产代码相同的方式失败:

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

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

模拟类

Mock是一个灵活的模拟对象,旨在替换存根的使用并在整个代码中进行两次测试。Mock 是可调用的,并在您访问[1]时将其创建为新的嘲讽属性。访问相同的属性将始终返回相同的模拟。模仿记录了您如何使用它们,使您可以 assert 代码对它们做了什么。

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 *:这可以是字符串列表,也可以是用作模拟对象规范的现有对象(类或实例)。如果传入一个对象,则pass在该对象上调用 dir 来形成字符串列表(不支持的魔术属性和方法除外)。访问不在此列表中的任何属性将引发AttributeError

如果* spec *是对象(而不是字符串列表),则class返回 spec 对象的类。这使模拟程序可以passisinstance()测试。

    • spec_set spec 的更严格的变体。如果使用了该属性,则try对未作为 spec_set *传递的对象进行模拟或对其进行“设置”或获取属性时,将引发AttributeError
    • side_effect *:每当调用 Mock 时要调用的函数。请参阅side_effect属性。对于引发异常或动态更改返回值很有用。该函数使用与模拟函数相同的参数调用,除非返回DEFAULT,否则将此函数的返回值用作返回值。

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

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

可以pass将* side_effect *设置为None来清除它。

    • return_value *:调用模拟时返回的值。默认情况下,这是一个新的 Mock(在首次访问时创建)。请参阅return_value属性。
  • 不安全:默认情况下,如果任何以* assert assret *开头的属性将引发AttributeError。传递unsafe=True将允许访问这些属性。

3.5 版中的新Function。

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

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

    • name *:如果模拟具有名称,则它将在模拟的代表中使用。这对于调试很有用。该名称将传播到子模拟。

还可以使用任意关键字参数来调用模仿。创建模拟后,将使用它们来设置属性。有关详细信息,请参见configure_mock()方法。

  • assert_called ( )
    • assert 该模拟至少被调用过一次。
>>> mock = Mock()
>>> mock.method()
<Mock name='mock.method()' id='...'>
>>> mock.method.assert_called()

3.6 版的新Function。

  • assert_called_once ( )
    • assert 该模拟仅被调用一次。
>>> mock = Mock()
>>> mock.method()
<Mock name='mock.method()' id='...'>
>>> mock.method.assert_called_once()
>>> mock.method()
<Mock name='mock.method()' id='...'>
>>> mock.method.assert_called_once()
Traceback (most recent call last):
...
AssertionError: Expected 'method' to have been called once. Called 2 times.

3.6 版的新Function。

  • assert_called_with((args ** kwargs *)
    • 此方法是 assert 上次调用已以特定方式进行的便捷方式:
>>> 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 *)
    • assert 该模拟仅被调用一次,并且该调用是使用指定的参数进行的。
>>> mock = Mock(return_value=None)
>>> mock('foo', bar='baz')
>>> mock.assert_called_once_with('foo', bar='baz')
>>> mock('other', bar='values')
>>> mock.assert_called_once_with('other', bar='values')
Traceback (most recent call last):
  ...
AssertionError: Expected 'mock' to be called once. Called 2 times.
  • assert_any_call((args ** kwargs *)
    • assert 已使用指定的参数调用了该模拟。

如果曾经调用过模拟,则 assert 会pass,而不同于assert_called_with()assert_called_once_with(),只有在调用是最近的调用时才pass,而在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 *)
    • assert 已使用指定的调用调用了该模拟。检查mock_calls列表中的呼叫。

如果* any_order *为 false,则调用必须是连续的。在指定呼叫之前或之后可能会有额外的呼叫。

如果* any_order *为 true,则调用可以按任何 Sequences 进行,但它们必须全部出现在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 ( )
    • assert 该模拟从未被调用。
>>> m = Mock()
>>> m.hello.assert_not_called()
>>> obj = m.hello()
>>> m.hello.assert_not_called()
Traceback (most recent call last):
  ...
AssertionError: Expected 'hello' to not have been called. Called 1 times.

3.5 版中的新Function。

  • reset_mock(** return_value = False side_effect = False *)
    • reset_mock 方法重置模拟对象上的所有调用属性:
>>> mock = Mock(return_value=None)
>>> mock('hello')
>>> mock.called
True
>>> mock.reset_mock()
>>> mock.called
False

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

在您要进行一系列重用同Pair象的 assert 时,这很有用。请注意,默认情况下reset_mock() 不会清除返回值,side_effect或您使用常规分配设置的任何子属性。如果您想重置* return_value *或side_effect,则将相应的参数传递为True。子模拟和返回值模拟(如果有)也将重置。

Note

  • mock_add_spec(* spec spec_set = False *)
    • 向模拟添加规格。 * spec 可以是一个对象或字符串列表。只能从模拟中获取 spec *上的属性作为属性。

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

  • attach_mock(模拟属性)

    • 附加一个模拟作为此模拟的属性,替换其名称和父代。对此随附的模拟程序的呼叫将记录在该模拟项的method_callsmock_calls属性中。
  • configure_mock((*** kwargs *)

    • pass关键字参数在模拟上设置属性。

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

>>> 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 *的模拟,这包括该模拟的所有允许的属性。

有关此过滤的Function以及如何将其关闭的信息,请参见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
    • 设置此项以配置pass调用模拟返回的值:
>>> 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)。

如果传入一个 Iterable,它将用于检索一个迭代器,该迭代器必须在每次调用时产生一个值。该值可以是要引发的异常实例,也可以是从对模拟的调用返回的值(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(如果尚未调用该模拟),也可以是最后一次使用该模拟的参数。这将以 Tuples 的形式出现:第一个成员(也可以passargs属性访问)是使用该模拟调用的任何有序参数(或空的 Tuples),第二个成员也可以pass以下方式访问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个对象。这些是 Tuples,因此可以解压缩它们以获取各个参数并进行更复杂的语句。参见称为 Tuples

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

  • call_args_list
    • 这是按 Sequences 对模拟对象进行的所有调用的列表(因此列表的长度是其被调用的次数)。在进行任何呼叫之前,它是一个空列表。 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个对象。这些可以作为 Tuples 解包,以获取各个参数。参见称为 Tuples

  • 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个对象。这些可以作为 Tuples 解包,以获取各个参数。参见称为 Tuples

  • 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个对象。这些可以作为 Tuples 解包,以获取各个参数。参见称为 Tuples

Note

记录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 类。这允许模拟对象passisinstance()测试,以代替/伪装成以下对象:
>>> mock = Mock(spec=3)
>>> isinstance(mock, int)
True

class可分配给它,这允许模拟passisinstance()检查,而不会强制您使用规范:

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

将类或实例用作specspec_set的模拟对象能够passisinstance()测试:

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

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

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

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

子模拟的返回值和副作用可以使用点分表示法以相同的方式设置。由于您不能在通话中直接使用点名,因此必须创建字典并使用**解压缩它:

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

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

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

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

Note

在版本 3.4 中进行了更改:在指定的和自动指定的模拟对象上添加了签名自省。

    • class * unittest.mock. PropertyMock( *args * kwargs *)
    • 拟用作类的属性或其他 Descriptors 的模拟。 PropertyMock提供get()set()方法,因此您可以在获取返回值时指定其返回值。

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

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

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

>>> m = MagicMock()
>>> p = PropertyMock(return_value=3)
>>> type(m).foo = p
>>> m.foo
3
>>> p.assert_called_once_with()
    • class * unittest.mock. AsyncMock(* spec = None side_effect = None return_value = DEFAULT wraps = None name = None spec_set = None unsafe = False * * kwargs *)
    • Mock的异步版本。 AsyncMock对象的行为将使该对象被识别为异步函数,并且调用结果是可以 await 的。
>>> mock = AsyncMock()
>>> asyncio.iscoroutinefunction(mock)
True
>>> inspect.isawaitable(mock())  
True

mock()的结果是一个异步函数,await 它后将具有side_effectreturn_value的结果:

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

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

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

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

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

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

MockMagicMockAsyncMock的* spec *设置为具有异步和同步Function的类将自动检测同步Function并将其设置为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 版的新Function。

  • assert_awaited ( )
    • assert 该模拟至少 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 ( )
    • assert 该模拟仅 await 了一次。
>>> mock = AsyncMock()
>>> async def main():
...     await mock()
...
>>> asyncio.run(main())
>>> mock.assert_awaited_once()
>>> asyncio.run(main())
>>> mock.method.assert_awaited_once()
Traceback (most recent call last):
...
AssertionError: Expected mock to have been awaited once. Awaited 2 times.
  • assert_awaited_with((args ** kwargs *)
    • assert 最后一次 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 call not found.
Expected: mock('other')
Actual: mock('foo', bar='bar')
  • assert_awaited_once_with((args ** kwargs *)
    • assert 该模拟已经精确地 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 *)
    • assert 使用指定参数一直在 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 *)
    • 语句已pass指定的调用 await 模拟。在await_args_list列表中检查 await。

如果* any_order *为 false,则 await 必须是连续的。在指定 await 之前或之后可能会有额外的呼叫。

如果* any_order *为 true,则 await 可以以任何 Sequences 进行,但是它们必须全部出现在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 ( )
    • assert 从未 await 过模拟。
>>> mock = AsyncMock()
>>> mock.assert_not_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(如果尚未 await 模拟),或者是最后 await 模拟的参数。Function与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
    • 这是按 Sequences 对模拟对象进行的所有 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')]

Calling

模拟对象是可调用的。该调用将返回设置为return_value属性的值。默认的返回值是一个新的 Mock 对象。它是在首次访问返回值(显式或pass调用 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函数。这使您可以根据 Importing 动态地更改调用的返回值:

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

如果您希望模拟仍返回默认返回值(新模拟)或任何设置的返回值,则有两种方法可以执行此操作。从side_effect内部返回mock.return_value,或者返回DEFAULT

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

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

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

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

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

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

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

Deleting Attributes

模拟对象根据需要创建属性。这使他们可以 Feign 为任何类型的对象。

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

您可以pass删除属性来“阻止”属性。删除后,访问属性将引发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

模拟名称和名称属性

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

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

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

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

将模拟物作为属性

当您将一个模拟作为另一个模拟的属性(或作为返回值)附加时,它将成为该模拟的“子代”。对孩子的呼叫记录在 parent 的method_callsmock_calls属性中。这对于配置子模拟然后将它们附加到父代,或将模拟附加到父代很有用,它记录了对子代的所有调用,并允许您语句模拟之间的调用 Sequences:

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

exceptions 是,模拟对象有名称。如果出于某种原因您不希望它发生,这可以防止发生“育儿”。

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

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

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

The patchers

修补程序修饰符仅在修饰的Function范围内用于修补对象。即使出现异常,它们也会为您自动处理补丁。所有这些Function也可以与语句一起使用或用作类装饰器。

patch

Note

patch()易于使用。关键是在正确的名称空间中进行修补。请参阅在哪里打补丁部分。

  • unittest.mock. patch(* target new = DEFAULT spec = None create = False spec_set = None autospec = None new_callable = None ** kwargs *)
    • patch()充当函数装饰器,类装饰器或上下文 Management 器。在函数主体或 with 语句内部,* target new *对象修补。当 function/with 语句退出时,撤消补丁。

如果Ellipsis* new ,则如果修补对象是异步函数,则将目标替换为AsyncMock,否则将目标替换为MagicMock。如果将patch()用作装饰器,并且Ellipsis new *,则将创建的模拟作为额外的参数传递给装饰的函数。如果patch()用作上下文 Management 器,则上下文 Management 器返回创建的模拟。

  • target 应该是格式为'package.module.ClassName'的字符串。将导入 target 并将指定的对象替换为 new 对象,因此 target *必须可从您从其调用patch()的环境中导入。在执行修饰Function时(而不是在修饰时)导入目标。

如果补丁程序正在为您创建一个参数,则将* spec spec_set *关键字参数传递给MagicMock

另外,您可以传递spec=Truespec_set=True,这会使 patch 将要模拟的对象作为 spec/spec_set 对象传递。

  • new_callable 允许您指定一个不同的类或可调用对象,将调用它们来创建 new *对象。默认情况下,AsyncMock用于异步Function,而MagicMock用于其余Function。

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

除了autospec=True之外,您还可以传递autospec=some_object以使用任意对象作为规范,而不是替换该对象。

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

Note

在版本 3.5 中进行了更改:如果要在模块中修补内置插件,则无需传递create=True,它将默认添加。

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

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

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

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

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

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

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

如果该类被多次实例化,则可以每次使用side_effect返回一个新的模拟。另外,您可以将* return_value *设置为所需的任何值。

要在修补类的* instances *方法上配置返回值,您必须在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()替换了 class *,则创建的模拟的返回值将具有相同的规范。

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

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

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

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

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

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

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

默认情况下,try修补不存在的模块中的函数(或类中的方法或属性)将失败,并返回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'

但是在对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 *)
    • 使用模拟对象将命名成员(* attribute )修补到对象( target *)上。

patch.object()可用作装饰器,类装饰器或上下文 Management 器。参数* new spec create spec_set autospec new_callable *具有与patch()相同的含义。与patch()一样,patch.object()可以使用任意关键字参数来配置它创建的模拟对象。

当用作类装饰器时,patch.object()会奖励patch.TEST_PREFIX来选择要包装的方法。

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

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

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

patch.dict

  • patch. dict(* in_dict values =() clear = False ** kwargs *)
    • 修补字典或类似对象的字典,并在测试后将字典还原为其原始状态。
  • in_dict *可以是字典或类似容器的 Map。如果是 Map,则它至少必须支持获取,设置和删除项目以及遍历键。

  • in_dict *也可以是指定字典名称的字符串,然后pass导入字典将其提取。

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

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

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

在版本 3.8 中更改:patch.dict()现在用作上下文 Management 器时返回修补的字典。

patch.dict()可用作上下文 Management 器,装饰器或类装饰器:

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

如果要为测试使用其他前缀,则可以pass设置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 *)
    • 在一个呼叫中执行多个补丁。它需要修补的对象(作为对象或pass导入获取对象的字符串)和修补程序的关键字参数:
with patch.multiple(settings, FIRST_PATCH='one', SECOND_PATCH='two'):
    ...

如果要patch.multiple()为您创建模拟,请使用DEFAULT作为值。在这种情况下,创建的模拟pass关键字传递到修饰的函数中,并且当patch.multiple()用作上下文 Management 器时,将返回字典。

patch.multiple()可用作装饰器,类装饰器或上下文 Management 器。参数* spec spec_set create autospec new_callable 具有与patch()相同的含义。这些参数将应用于patch.multiple()完成的所有*补丁。

当用作类装饰器时,patch.multiple()会奖励patch.TEST_PREFIX来选择要包装的方法。

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

>>> 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()用作上下文 Management 器,则上下文 Management 器返回的值是字典,其中创建的模拟由名称进行键控:

>>> 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()方法。这些使在setUp方法中进行修补或在不嵌 Sets 饰器或语句的情况下要进行多个修补的地方变得更容易。

要使用它们,请正常调用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()

Caution

如果您使用此技术,则必须pass调用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开头的补丁。

patch builtins

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

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

TEST_PREFIX

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

您可能想为测试使用其他前缀。您可以pass设置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 应用装饰器的标准方法。传递到您的测试函数中的已创建模拟的 Sequences 与此 Sequences 匹配。

修补位置

patch()的工作方式是(暂时)将* name *指向的对象更改为另一个对象。可以有许多名称指向任何单个对象,因此,要使修补程序起作用,必须确保对被测系统使用的名称进行修补。

基本原理是,您修补对象查找的位置,该对象不一定与定义对象的位置相同。几个例子将有助于阐明这一点。

假设我们有一个要测试的项目,其结构如下:

a.py
    -> Defines SomeClass

b.py
    -> from a import SomeClass
    -> some_function instantiates SomeClass

现在我们要测试some_function,但是我们想使用patch()模拟SomeClass。问题在于,当我们导入模块 b 时,我们将不得不从模块 a 导入SomeClass。如果我们使用patch()模拟a.SomeClass,那么它将对我们的测试没有影响;模块 b 已经具有对* real * SomeClass的引用,并且看来我们的修补没有任何效果。

关键是修补SomeClass的使用位置(或查找位置)。在这种情况下,some_function实际上将在模块 b 中(已将其导入)中查找SomeClass。修补程序应如下所示:

@patch('b.SomeClass')

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

@patch('a.SomeClass')

修补 Descriptors 和代理对象

patchpatch.object都正确修补和还原了 Descriptors:类方法,静态方法和属性。您应该在* class 而不是实例上打补丁。它们还与代理属性访问的 some *对象一起使用,例如django 设置对象

MagicMock 和魔术方法支持

模拟魔术方法

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

由于查找魔术方法的方法与正常方法[2]的查找方法不同,因此已特别实现了此支持。这意味着仅支持特定的魔术方法。支持的列表几乎全部包含了。如果您有任何遗漏,请告诉我们。

您可以pass将感兴趣的方法设置为函数或模拟实例来模拟魔术方法。如果您使用的是函数,则它必须*将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语句中模拟用作上下文 Management 器的对象:

>>> 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中。

Note

如果使用* spec *关键字参数创建模拟,则try设置规范中未包含的魔术方法将引发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__

  • 上下文 Management 器:__enter____exit____aenter____aexit__

  • 一元数值方法:__neg____pos____invert__

  • 数值方法(包括右手和就位变体):__add____sub____mul____matmul____div____truediv____floordiv____mod____divmod____lshift____rshift____and____xor____or____pow__

  • 数值转换方法:__complex____int____float____index__

  • Descriptors 方法:__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__

Magic Mock

有两种MagicMock变体:MagicMockNonCallableMagicMock

  • 类别 unittest.mock. MagicMock( *args * kw *)
    • MagicMockMock的子类,其中大多数魔术方法的默认实现。您可以使用MagicMock,而不必自己配置魔术方法。

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

如果使用* spec spec_set 参数,那么将创建规范中存在的 only *魔术方法。

  • 类别 unittest.mock. NonCallableMagicMock( *args * kw *)

构造函数参数的含义与MagicMock相同,但* return_value side_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__:模拟的默认 str

  • __sizeof__:模拟的默认 sizeof

For example:

>>> mock = MagicMock()
>>> int(mock)
1
>>> len(mock)
0
>>> list(mock)
[]
>>> object() in mock
False

eq()ne()这两个相等方法很特殊。它们使用side_effect属性对身份进行默认的相等比较,除非您更改其返回值以返回其他内容:

>>> MagicMock() == 3
False
>>> MagicMock() != 3
True
>>> mock = MagicMock()
>>> mock.__eq__.return_value = True
>>> mock == 3
True

MagicMock.__iter__()的返回值可以是任何可迭代的对象,并且不需要是迭代器:

>>> mock = MagicMock()
>>> mock.__iter__.return_value = ['a', 'b', 'c']
>>> list(mock)
['a', 'b', 'c']
>>> list(mock)
['a', 'b', 'c']

如果返回值是一个迭代器,则对其进行一次迭代将消耗它,随后的迭代将导致一个空列表:

>>> mock.__iter__.return_value = iter(['a', 'b', 'c'])
>>> list(mock)
['a', 'b', 'c']
>>> list(mock)
[]

MagicMock已配置了所有受支持的魔术方法,除了一些晦涩和过时的魔术方法。如果需要,您仍然可以进行设置。

MagicMock中受支持但默认情况下未设置的魔术方法是:

  • __subclasses__

  • __dir__

  • __format__

  • __get____set____delete__

  • __reversed____missing__

  • __reduce____reduce_ex____getinitargs____getnewargs____getstate____setstate__

  • __getformat____setformat__

  • [2]

    • 应该在类而不是实例上查找魔术方法。不同版本的 Python 在应用此规则方面不一致。受支持的协议方法应与所有受支持的 Python 版本一起使用。
  • [3]

    • 该函数基本上连接到该类,但是每个Mock实例与其他实例保持隔离。

Helpers

sentinel

  • unittest.mock. sentinel
    • sentinel对象提供了一种方便的方法来为您的测试提供唯一的对象。

pass名称访问属性时,将根据需要创建属性。访问相同的属性将始终返回相同的对象。返回的对象具有合理的代表,因此可以读取测试失败消息。

在版本 3.7 中进行了更改:sentinel属性现在保留为copiedpickled时的身份。

有时,在测试时,您需要测试特定对象是否作为参数传递给另一个方法或已返回。创建命名的哨兵对象来对此进行测试是很常见的。 sentinel提供了一种方便的方法来创建和测试此类对象的身份。

在此示例中,我们猴子补丁method返回sentinel.some_object

>>> real = ProductionClass()
>>> real.method = Mock(name="method")
>>> real.method.return_value = sentinel.some_object
>>> result = real.method()
>>> assert result is sentinel.some_object
>>> sentinel.some_object
sentinel.some_object

DEFAULT

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

call

>>> 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()返回所有中间呼叫以及finally呼叫的列表。

call_list对于对“链接的呼叫”进行 assert 特别有用。链式调用是在一行代码上的多个调用。这样会在一个模拟中的mock_calls中产生多个条目。手动构造调用 Sequences 可能很繁琐。

call_list()可以从同一链接的呼叫构造呼叫 Sequences:

>>> 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对象是(位置 args,关键字 args)或(名称,位置 args,关键字 args)的 Tuples,具体取决于其构造方式。当您自己构造它们时,这并不是特别有趣,但是可以自省Mock.call_argsMock.call_args_listMock.mock_calls属性中的call对象以获取它们包含的各个参数。

Mock.call_argsMock.call_args_list中的call对象是(位置 args,关键字 args)的二 Tuples,而Mock.mock_calls中的call对象以及您自己构造的对象是(名称,位置 args,关键字 args)的三 Tuples。

您可以使用它们的“Tuples”来提取各个参数,以进行更复杂的自省和 assert。位置参数是一个 Tuples(如果没有位置参数,则为空的 Tuples),关键字参数是一个字典:

>>> 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_set *为True,则try设置 spec 对象上不存在的属性将引发AttributeError

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

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

有关如何对create_autospec()使用自动指定以及patch()的* autospec *参数的示例,请参见Autospeccing

在 3.8 版中进行了更改:如果目标是异步函数,则create_autospec()现在返回AsyncMock

ANY

  • unittest.mock. ANY

有时您可能需要在对模拟的调用中对某些参数进行 assert,但要么不在乎某些参数,要么想将它们分别从call_args中拉出来,并对它们进行更复杂的 assert。

要忽略某些参数,您可以传入比较* everything *的对象。无论传入了什么,对assert_called_with()assert_called_once_with()的调用都将成功。

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

ANY也可以与mock_calls之类的通话 Lists 进行比较:

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

FILTER_DIR

  • unittest.mock. FILTER_DIR

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

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

>>> dir(Mock())
['assert_any_call',
 'assert_called',
 '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上调用dir()的结果中过滤掉了许多不是很有用的(专用于Mock而不是被 Mock 的下划线)和双下划线前缀的属性。如果您不喜欢这种行为,可以pass设置模块级别开关FILTER_DIR将其关闭:

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

另外,您可以只使用vars(my_mock)(实例成员)和dir(type(my_mock))(类型成员)来绕过过滤,而不必考虑mock.FILTER_DIR

mock_open

  • unittest.mock. mock_open(* mock = None read_data = None *)
    • 一个帮助函数,用于创建模拟来代替open()的使用。它适用于直接调用或用作上下文 Management 器的open()
  • mock *参数是要配置的模拟对象。如果是None(默认设置),则会为您创建一个MagicMock,API 限于标准文件句柄上可用的方法或属性。

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

在版本 3.4 中进行了更改:添加了readline()readlines()支持。 read()的模拟更改为使用* read_data *,而不是在每次调用时都将其返回。

在版本 3.5 中进行了更改:现在,每次调用* mock 都会重置 read_data *。

在版本 3.8 中进行了更改:在实现中添加了iter(),以便迭代(例如 for 循环)正确地使用* read_data *。

使用open()作为上下文 Management 器是一种确保正确关闭文件句柄并使其变得常见的好方法:

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

问题是,即使您模拟了对open()的调用,也将返回对象用作上下文 Management 器(并调用了enter()exit())。

MagicMock模拟上下文 Management 器很常见,也很笨拙,以至于助手Function很有用。

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

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

在解释自动指定的工作原理之前,这就是为什么需要它的原因。

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

首先是针对Mock的问题。 Mock有两个非常方便的 assert 方法:assert_called_with()assert_called_once_with()

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

因为模拟会根据需要自动创建属性,并允许您使用任意参数来调用它们,所以如果您拼写了这些 assert 方法之一,那么您的 assert 就会消失:

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

由于 Importing 错误,您的测试可能会悄无声息地pass。

第二个问题对嘲讽更为笼统。如果您重构某些代码,重命名成员等,则仍在使用旧 API 但使用模拟而不是真实对象的任何代码测试仍将pass。这意味着即使代码已损坏,您的测试也可以全部pass。

请注意,这是您需要集成测试和单元测试的另一个原因。孤立地测试所有内容都是好事,但如果您不测试如何将单元“连接在一起”,那么仍然存在很多可能会捕获测试错误的空间。

mock已经提供了一种有助于解决此问题的Function,称为“ Specing”。如果将类或实例用作模拟的spec,则只能访问真实类中存在的模拟属性:

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

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

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

自动指定解决了此问题。您可以将autospec=True传递给patch()/patch.object(),也可以使用create_autospec()函数来创建具有规范的模拟。如果对patch()使用autospec=True参数,则将替换的对象用作 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有规格。 request.Request在构造函数中接受两个参数(其中一个是* self *)。如果我们try错误地调用它,将会发生以下情况:

>>> 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的返回值是不可调用的模拟。有了规范,我们的 assert 中的任何错别字都会引发正确的错误:

>>> req.add_header('spam', 'eggs')
<MagicMock name='request.Request().add_header()' id='...'>
>>> req.add_header.assret_called_with
Traceback (most recent call last):
 ...
AttributeError: Mock object has no attribute 'assret_called_with'
>>> req.add_header.assert_called_with('spam', 'eggs')

在许多情况下,您仅可以将autospec=True添加到现有的patch()调用中,然后就可以防止由于 Importing 错误和 api 更改而导致的错误。

除了passpatch()使用* autospec *之外,还有create_autospec()用于直接创建自动指定的模拟:

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

但是,这并不是没有警告和限制的,这就是为什么它不是默认行为的原因。为了知道 spec 对象上有哪些可用属性,autospec 必须自检(访问属性)spec。当您遍历模拟对象的属性时,原始对象的对应遍历也在后台进行。如果任何指定对象具有可触发代码执行的属性或 Descriptors,则您可能无法使用自动指定。另一方面,设计对象要好得多,以使自省是安全的[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
...
  • 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()中设置默认属性,则pass类属性(当然是在实例之间共享)提供它们也更快。例如

class Something:
    a = 33

这带来了另一个问题。通常为成员提供默认值None,这些成员以后将成为不同类型的对象。 None作为规范将毫无用处,因为它不允许您访问* any *属性或方法。由于None永远不会用作规范,并且可能表示通常将使用其他类型的成员,因此 autospec 不会对设置为None的成员使用规范。这些只是普通的模拟游戏(嗯-MagicMocks):

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

如果您不喜欢修改生产类以添加默认值,那么还有更多选择。其中之一就是简单地将实例用作规范而非类。另一种方法是创建生产类的子类,并将默认值添加到该子类中,而不会影响生产类。这两个都要求您使用替代对象作为规范。幸运的是patch()支持这一点-您只需将替代对象作为* autospec *参数传递即可:

>>> class Something:
...   def __init__(self):
...     self.a = 33
...
>>> class SomethingForTest(Something):
...   a = 33
...
>>> p = patch('__main__.Something', autospec=SomethingForTest)
>>> mock = p.start()
>>> mock.a
<NonCallableMagicMock name='Something.a' spec='int' id='...'>
  • [4]
    • 这仅适用于类或已实例化的对象。调用模拟类来创建模拟实例不会创建真实实例。仅完成属性查找以及对dir()的调用。

Sealing mocks

  • unittest.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 版中的新Function。