Python mock

官方链接:https://docs.python.org/zh-cn/3/library/unittest.mock-examples.html

Mock

备注:常用的有两个 mock 类:MockMagicMock ,在多数示例中,MockMagicMock 两个类可以相互替换。MagicMockMock 的子类,它实现了大部分常用的 魔法方法,通常情况下,使用 MagicMock 就可以了。

Mock 类

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

Mock 类用来创建一个新的 Mock 对象。参数:

  • spec: 可以是字符串列表,也可以是充当模拟对象规范的现有对象(类或实例)。如果传入一个对象,则在该对象上调用 dir 来生成字符串列表(不支持的魔法属性和方法除外),针对创建成功的 mock 对象,访问不在此列表中的属性都将触发 AttributeError 。如果 spec 是一个对象(而不是字符串列表),则 __class__ 返回 spec 对象的类。 这允许模拟程序能使用 isinstance()

  • spec_set :spec 的更严格的变体。如果使用了该属性,尝试 set get 的属性不在 spec_set 所包含的对象中时,会抛出 AttributeError 。

  • side_effect :每当调用 Mock 时都会调用的函数。它可以用来引发异常或动态更改返回值。 该函数使用与 mock 函数相同的参数调用,并且除非返回 DEFAULT ,否则该函数的返回值将用作 mock 的返回值。另外, side_effect 可以是异常类或实例,这种情况下调用模拟程序时将引发异常。如果 side_effect 是可迭代对象,则每次调用 mock 都将返回可迭代对象的下一个值。 side_effect 为 None 即可清空。(简单来说,mock 函数的参数会原样传递给 side_effect,如果 side_effect 返回值不是 DEFAULT,则将 side_effect 的返回值作为 mock 的返回值。因此我们可以用它来抛出异常,或者迭代数据)

  • return_value :调用 mock 的返回值。 默认情况下,返回值是一个新的Mock。

  • unsafe: 默认情况下,访问以这些名字开头的属性: assert, assret, asert, aseert or assrt 都会引发 AttributeError。传递 unsafe=True 将会允许我们使用这些名字开头的属性

  • wraps: mock 对象要去包装的对象。 如果 wraps 不是 None ,那么调用 Mock 会将调用传递给 wraps 指定的对象(返回wraps 对象的结果)。 对 mock 的属性访问将返回一个 Mock 对象,该对象包装了 wraps 对象的相应属性(因此,尝试访问不存在的属性将引发 AttributeError )。如果明确指定 return_value ,则调用时不会返回包装对象,而是返回 return_value。(简而言之,指定了 wraps 后,mock 就相当于是 wraps 的代理,对 mock 的访问实际上是访问 wraps)

  • name :mock 的名称。 在调试时很有用。 名称会传递到子 mock 。

使用 mock

mock 对象的方法

现有如下待模拟的方法:

from unittest.mock import Mock, MagicMock


class ProductionClass:
	def method(self):  # 一个待模拟的方法
		pass

p = ProductionClass()

模拟对象方法:

p.method = Mock(name="t.method")

Mock 的 name 参数可以用来设置 Mock 的名字,打印 mock 对象时会显示这个名字

调用 mock 方法:

p.method(1, key=2)

可以随意传参,因为它是 mock 对象

判断 mock 被调用了:

print(p.method.called)  # called 可以判断 mock 是否被调用

# 下面是两个判断语句,判断 mock 是否用哪些参数被调用,它们没有返回值,如果判断失败会抛出异常。
p.method.assert_called_with(1, key=2)
p.method.assert_called_once_with(1, key=2)

mock 作为参数的函数

譬如:

from unittest.mock import Mock, MagicMock


class ProductionClass:
    def closer(self, something):  # closer 接收一个函数作为参数
        something.close()

我们可以把 mock 作为参数传入 closer 方法中:

real = ProductionClass()
mock = Mock()
real.closer(mock)
mock.close.assert_called_with()  # 判断 mock 被调用

mock 类

创建两个文件:

production.py:

class ProductionClass:
	def method(self):
		return 100

test_mock.py:

from unittest.mock import patch
import production  # 导入模块


def some_function():
	instance = production.ProductionClass()  # 使用模块中的类
	return instance.method()


with patch("production.ProductionClass") as mock:  # 模拟该类
	instance = mock.return_value  # mock 的 return_value 默认也是一个 mock 对象!
	instance.method.return_value = 1  # 设置 mock 的 method 方法返回值 == 1
	result = some_function()  # some_function 中会使用真实的该类
	assert result == 1  # 结果 == 1,说明我们 mock 的类替换了该类

patch 的参数:

target, new=DEFAULT, spec=None, create=False,
spec_set=None, autospec=None, new_callable=None, *, unsafe=False, **kwargs

target 是要 mock 的对象,格式是:'package.module.ClassName' ,它必须是一个能从当前位置导入的对象

new 通常忽略不写,它会自动根据你的 target 来选定生成哪种 mock 对象: AsyncMock 或 MagicMock 对象。如果当前 patch 函数是作为装饰器使用的、并且 new 参数忽略没写:那么生成的 mock 对象会作为额外的参数传递给被装饰的函数。如果 patch 是作为上下文管理器使用的,那么上下文管理器的返回值就是新创建的 mock 对象(本例就是作为上下文管理器使用的)

spec , spec_set 参数用来指定类或实例。这两个参数会被传递给 new 选定的 mock 对象,来生成 mock 。生成的 mock 对象只能使用通过 spec、spec_set 参数指定的类中的属性和方法。你也可以设置 spec=Truespec_set=True 来默认让target 作为 spec 对象

create 参数:默认情况下,给一个不存在的类、方法、函数等打补丁会报错(即 target 不存在)。但是当 create=True,patch 会自动创建你要打补丁的对象

autospec=True 可以生成一个 spec = 'target' 的 mock 对象。也可以指定 autospec=some_object

new_callable 允许你指定一个类,或者可调用对象,这个类或可调用对象会被调用来创建 new 对象,By default AsyncMock is used for async functions and MagicMock for the rest.

patch() 可以作为函数装饰器、类装饰器或上下文管理器。 在函数或 with 语句(上下文管理器)的内部,target 会打上一个 new 对象补丁。 当函数/with 语句退出时补丁将被撤销。简而言之:patch 可以创建一个 mock 对象(即参数 new),用这个 mock 对象来给 target 打补丁(替换 target)

备注:如果想要给模块自身中的类打补丁,可以写成:patch("__main__.ClassName")

追踪调用

mock 对象所有的调用过程,都可以通过 mock_calls 属性来查看。

mock = MagicMock()

mock.method()
mock.attribute.method(10, x=53)

print(mock.mock_calls)  # [call.method(), call.attribute.method(10, x=53)]

断言 mock_calls 调用过程,需要使用 call 对象来构造列表以便与 mock_calls 进行比较:

from unittest.mock import call

expected = [call.method(), call.attribute.method(10, x=53)]
print(mock.mock_calls == expected)

设置返回值和属性

在 mock 对象上设置返回值非常容易:

mock = Mock()
mock.return_value = 3
print(mock())  # 3

也可以在构造 mock 对象时设置返回值:

mock = Mock(return_value=3)

对 mock 对象的方法设置返回值:

mock.method.return_value = 3

print(mock.method())  # 3

对 mock 对象设置属性:

mock = Mock()
mock.x = 3

有时你会想要模拟更复杂的情况,例如 mock.connection.cursor().execute("SELECT 1")。 如果我们希望这个调用返回一个列表,实现方法如下:

mock = Mock()
cursor = mock.connection.cursor.return_value  # 之前说过,默认情况下 return_value 返回值也是一个 mock
cursor.execute.return_value = ['foo']  # 给 cursor 这个 子mock 对象设置 execute 方法以及该方法的返回值
mock.connection.cursor().execute("SELECT 1")  # ['foo']

# 断言 mock 的调用
expected = call.connection.cursor().execute("SELECT 1").call_list()  # [call.connection.cursor(), call.connection.cursor().execute('SELECT 1')]
print(mock.mock_calls == expected)

.call_list() 的调用会将我们的调用对象转成一个链式的调用列表。

通过 mock 引发异常

一个很有用的属性是 side_effect。 如果你将该属性设为一个异常类或者实例,那么当 mock 被调用时该异常将会被引发。

mock = Mock(side_effect=Exception('Boom!'))
mock()  # 将会引发异常

side_effect 也可以被设为一个函数或可迭代对象。 side_effect 作为可迭代对象的应用场景适用于你的 mock 将要被多次调用,并且你希望每次调用都返回不同的值的情况。 当你将 side_effect 设为一个可迭代对象时,每次对 mock 的调用将返回可迭代对象的下一个值。

mock = MagicMock(side_effect=[4, 5, 6])
print(mock())  # 4
print(mock())  # 5
print(mock())  # 6

side_effect 设置成函数。所有传递给 mock 的参数都会被原样传递给 side_effect 函数,如果 side_effect 函数的返回值不是 unittest.mock.DEFAULT ,那么就用 side_effect 的返回值作为 mock 的返回值:

def side_effect(*args):
    return args


mock = MagicMock(side_effect=side_effect)
print(mock(1,2,3,4))  # (1, 2, 3, 4)

模拟异步迭代器

从 Python 3.8 起,AsyncMockMagicMock 支持通过 __aiter__ 来模拟 异步迭代器__aiter__return_value 属性可以被用来设置要用于迭代的返回值。

from unittest.mock import Mock, MagicMock
import asyncio

mock = MagicMock()  # AsyncMock 也能用在这里
mock.__aiter__.return_value = [1, 2, 3]

async def main():
    return [i async for i in mock]

result = asyncio.run(main())
print(result)  # [1, 2, 3]

模拟异步上下文管理器

从 Python 3.8 起,AsyncMockMagicMock 支持通过 __aenter____aexit__ 来模拟 异步上下文管理器。 在默认情况下,__aenter____aexit__ 都是 AsyncMock 实例,它们返回异步函数。

import asyncio
from unittest.mock import MagicMock


class AsyncContextManager:
	async def __aenter__(self):
		return self

	async def __aexit__(self, exc_type, exc, tb):
		pass


mock_instance = MagicMock(AsyncContextManager())  # AsyncMock also works here


async def main():
	async with mock_instance as result:
		pass


asyncio.run(main())
mock_instance.__aenter__.assert_awaited_once()
mock_instance.__aexit__.assert_awaited_once()

用已存在的对象创建 mock

假设你要 mock 的对象有个 method1 方法,所以你给创建的 mock 对象也添加了 method1 方法。但是如果你重构了要 mock 的对象,把 method1 改成了 method2 ,那么你创建的 mock 对象依然使用 method1 方法来测试,这显然不对了。

Mock 允许你指定一个对象来作为生成的 mock 对象的 “规范”,使用 spec 关键字参数来提供一个对象作为 mock 的规格说明。在 mock 上访问不存在于你的规格说明对象中的方法 / 属性将立即引发一个属性错误。(即 spec 对象拥有哪些 api,生成的 mock 就拥有哪些 api,mock 访问不存在的 api 会报错)

譬如:

>>> mock = Mock(spec=SomeClass)
>>> mock.old_method()
Traceback (most recent call last):
   ...
AttributeError: object has no attribute 'old_method'

使用规格说明还可以启用对 mock 的调用的更聪明的匹配操作: 无论是否有将某些形参作为位置或关键字参数传入:

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

如果你想要让这些更聪明的匹配操作也适用于 mock 上的方法调用,你可以使用 auto-speccing

如果你想要更强形式的规格说明以防止设置任意属性并获取它们那么你可以使用 spec_set 来代替 spec

补丁装饰器

测试中的一个常见需求是为类或模块打补丁(即临时性的替换掉它们),例如修补内置对象或修补某个模块中的类来测试其是否被实例化。 模块和类都可算是全局对象,因此对它们打补丁的操作必须在测试完成之后被还原,否则补丁将持续影响其他测试并导致难以诊断的问题。patch() 通过(临时性地)修改某一个对象的 名称 指向另一个对象来发挥作用。

我们上面有用 patch 来 mock 类:见上文《mock 类》小节

补丁的位置

补丁装饰器,重要的是找到要在哪里打补丁。

示例1:

a.py

class A:
	def method(self):
		pass

b.py

import a  # 导入 a 模块,想用 A 只能使用:a.A()

def some_func():
	aa = a.A()
	return aa.method()


from unittest.mock import patch
with patch("a.A") as mock:  # 模拟该类
	instance = mock.return_value
	instance.method.return_value = 1
	result = some_func()
	assert result == 1

上面的例子中,我们在 b.pyimport a,我们想要打补丁的类 A 来自 a,因此我们使用 patch("a.A") 来打补丁。

示例2:

假如我们修改 b.py 如下:

b.py

from a import A  # 直接将 A 导入 b 中, 想用 A 就能直接使用: A()

如果此时有个 c.py 想要给 b.py 中的 A 打补丁,需要:

c.py

import b

with patch("b.A") as mock:
    ...

看明白了吗?我们不在乎要打补丁的类 A 是在哪里定义的,只在乎类 A 是在哪个模块中查找到的,示例1 中 A 是在 a.py 中查找到的,因此补丁要写成 patch("a.A")。示例2 中类 A 是在 b.py 中查找到的,因此补丁写成 patch("b.A")

三种补丁

mock 提供了三个便捷的装饰器: patch(), patch.object()patch.dict()patch 接受单个字符串,其形式 package.module.Class.attribute 指明你要修补的属性。 它还可选择接受一个值用来替换指定的属性(或者类对象等等)。 'patch.object' 接受一个对象和你想要修补的属性名称,并可选择接受要用作补丁的值。

patch

在 《mock 类》 这一小节我们简单介绍了 patch 的参数,还用 patch 以上下文管理器的形式模拟了类。这里我们就说说以装饰器的形式,来给函数打补丁。

当 patch 作为装饰器,它会为你创建 mock 并将其传入被装饰的函数:

from unittest.mock import Mock, patch, MagicMock


class A:
	def method(self):
		pass


@patch("__main__.A")
def function(normal_argument, mock):  # 第二个参数用来接收传入的 mock 对象
	print(normal_argument, mock)

function(1)

new_callable 参数适用于当你想要使用其他类来替代所创建的 mock 默认的 MagicMock 的场合。 例如使用 NonCallableMock:

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

另一个场景:用 io.StringIO 实例来替换某个对象:

>>> from io import StringIO
>>> def foo():
...     print('Something')  # 打印到 sys.stdout
...
>>> @patch('sys.stdout', new_callable=StringIO)  # 使用 stringIO 来给 sys.stdout 打补丁
... def test(mock_stdout):
...     foo()
...     assert mock_stdout.getvalue() == 'Something\n'
...
>>> test()

你在调用 patch 时传入的任何关键字参数都将被用来在所创建的 mock 上设置属性:

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

使用 ** 将以属性为键的字典扩展至一个 patch() 调用中(因为语法问题,我们不能写成:patch("__main__.thing", method.return_value=3, other.side_effect=KeyError),所以我们只能写成字典形式,然后解包):

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

在默认情况下,尝试给某个模块中并不存在的函数(或者某个类中的方法或属性)打补丁将会失败并引发 AttributeError:

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

但在对 patch() 的调用中添加 create=True 将使之前示例的效果符合预期:

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

patch.object

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

用一个 mock 对象为对象 (target) 中指定名称的成员 (attribute) 打补丁。patch.object() 可以被用作装饰器、类装饰器或上下文管理器。 new, spec, create, spec_set, autospecnew_callable 等参数的含义与 patch() 的相同。 与 patch() 类似,patch.object() 接受任意关键字参数用于配置它所创建的 mock 对象。

使用三个参数:要打补丁的对象、属性的名称以及将要替换该属性的对象,作为装饰器使用时,不会给被装饰函数传递新创建的 mock

使用两个参数:将附带两个参数的形式调用时你将省略替换对象,还会为你创建一个 mock 并作为附加参数传入被装饰的函数

譬如:

from unittest.mock import Mock, patch

class A:
	def method(self):
		pass

def method2():
	pass


# 三个参数
@patch.object(A, "method", method2)
def function(normal_argument):
	print(normal_argument)

function(1)

# 两个参数时,被装饰函数必须要接受新创建的 mock 对象作为附加参数
@patch.object(A, "method")
def function(normal_argument, mock):
	print(normal_argument, mock)

function(1)

patch.dict

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

为一个字典或字典类对象打补丁,并在测试之后将该目录恢复到其初始状态。

in_dict 可以是一个字典或映射类容器。 如果它是一个映射则它必须至少支持获取、设置和删除条目以及对键执行迭代。in_dict 也可以是一个指定字典名称的字符串,然后将通过导入操作来获取该字典。

values 可以是一个要在字典中设置的值的字典。 values 也可以是一个包含 (key, value) 对的可迭代对象。如果 clear 为真值则该字典将在设置新值之前先被清空。patch.dict() 也可以附带任意关键字参数调用以设置字典中的值。

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

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

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() 被用作上下文管理器时则将返回一个字典。

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 可以被用作 TestCase 类装饰器(上面提到的四种 patch 都可以)。 其作用是装饰类中的每个测试方法。 patch() 会通过查找以 patch.TEST_PREFIX ( 默认值是 'test')打头的名称来找到测试用例,这与 unittest 找到测试的方式一致。 你可以通过设置 patch.TEST_PREFIX 来指定其他的前缀。举例:

import os
import unittest
from unittest.mock import patch

@patch.dict('os.environ', {'newkey': 'newvalue'})
class TestSample(unittest.TestCase):
    def test_sample(self):  # 自动给 test_sample 打补丁
        self.assertEqual(os.environ['newkey'], 'newvalue')

补丁方法: start 和 stop

简单来说,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

此操作的一个典型应用场景是在一个 TestCasesetUp 方法中执行多重补丁:

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

小心:如果你要使用这个技巧则你必须通过调用 stop 来确保补丁被“恢复”。 这可能要比你想像的更麻烦,因为如果在 setUp 中引发了异常那么 tearDown 将不会被调用。 unittest.TestCase.addCleanup() 可以简化此操作:

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

一项额外的好处是你不再需要保留指向 patcher 对象的引用。

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

为内置函数打补丁

你可以为一个模块中的任何内置函数打补丁。 以以示例是为内置函数 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 的顺序也将匹配这个顺序。

autospec

讲到 patch 的时候,我们曾经说过几个参数:spec, spec_set, autospec,mock 会以 spec 等指定的类或对象来创建(拥有和它们同样的方法和属性,使用不存在的属性或方法会报错)

示例1:spec

from unittest.mock import Mock, patch

class A:
	def method(self):
		pass


mock = Mock(spec=A)
mock.method()
# mock.method2()  # 会报错,因为 spec 指定的类 A 中没有 method2
mock.method.method2()  # 但是这个不会报错,因为 spec 仅会应用于 mock 本身。对于 子mock 不起作用

但是如果我们用 autospec=True:

from unittest.mock import Mock, patch

class A:
	def method(self):
		pass


mock = patch("__main__.A", autospec=True)
mock_ins = mock.start()
mock_ins.method.method2()  # 会报错,autospec=True 和 spec 相比,也限定了 子mock,不允许使用类 A 不存在的方法或属性

posted @ 2023-02-08 23:21  wztshine  阅读(322)  评论(0编辑  收藏  举报