☰
CPython unittest.mock 实战指南:从 Mock 基础到 Patch 高级用法的官方示例全解
2026/11/3 1:54:29 网站建设 项目流程

CPython unittest.mock 实战指南:从 Mock 基础到 Patch 高级用法的官方示例全解

【免费下载链接】cpythonThe Python programming language项目地址: https://gitcode.com/GitHub_Trending/cp/cpython

本文以 CPython 仓库中随标准库发布的官方入门文档Doc/library/unittest.mock-examples.rst为主体,完整梳理unittest.mock的三类核心能力——Mock对象的配置与断言、patch系列装饰器/上下文管理器的使用模式,以及链式调用、可变参数、导入替换等进阶场景的官方示例。文中所有代码示例均可直接复制运行,并结合Lib/unittest/mock.py的实现源码(约 3200 行)印证了关键行为背后的机制,读完可以独立编写可维护、可验证的 Python 单元测试。

背景与 mock 的类体系

unittest.mock自 Python 3.3 起纳入标准库(见文档头部的versionadded标记),其实现位于 Lib/unittest/mock.py,对应的完整 API 参考文档是 Doc/library/unittest.mock.rst,本文聚焦的“getting started”示例文档则是 Doc/library/unittest.mock-examples.rst。

从源码结构看,mock 的类继承体系如下(以 Lib/unittest/mock.py 等处的类定义为准):

类定义位置作用
NonCallableMockmock.py#L433不可调用 mock 的基类,提供调用记录、spec检查等基础设施
Mockmock.py#L1263可调用 mock,Mock属性是Mock
MagicMockmock.py#L2223额外支持__len__、__iter__、__getitem__等魔术方法的 mock,属性也是MagicMock
AsyncMockmock.py#L24743.8 起用于 mock 协程,支持await与异步上下文管理

官方示例文档给出了一条重要提示:在大多数示例中Mock与MagicMock可以互换使用,由于MagicMock能力更强,默认选用它是更稳妥的选择。

使用 Mock:替换方法与记录调用

Mock对象的两大常见用途是:替换(patch)方法与记录方法调用。

替换对象上的方法

当你想替换对象上的某个方法,以检查它是否被系统的另一部分以正确参数调用时,可以这样做:

real = SomeClass() real.method = MagicMock(name='method') real.method(3, 4, 5, key='value')

Mock 一旦被使用,就会拥有用于断言其使用方式的方法和属性。调用后mock.called属性会被置为True;更重要的是,可以用assert_called_with或assert_called_once_with检查它是否以正确参数被调用。下面的例子测试调用ProductionClass().method会导致对something方法的调用:

class ProductionClass: def method(self): self.something(1, 2, 3) def something(self, a, b, c): pass real = ProductionClass() real.something = MagicMock() real.method() real.something.assert_called_once_with(1, 2, 3)

Mock 对象上的方法调用

另一种常见用法是:把一个对象传入被测方法,然后检查它是否被正确使用。

class ProductionClass: def closer(self, something): something.close() real = ProductionClass() mock = Mock() real.closer(mock) mock.close.assert_called_with()

这里不需要为 mock 提供close方法——访问close属性本身就会创建它。但如果close从未被真正调用过,在测试中访问它虽然会创建子 mock,随后assert_called_with仍会抛出失败异常,这正是 mock“惰性创建 + 调用记账”机制带来的严格性。

Mock 类:通过 return_value 配置实例

被 mock 掉的类会被替换为一个 mock,而“实例”是通过调用类创建的,因此要访问 mock 实例,需要查看被 mock 类的返回值:

def some_function(): instance = module.Foo() return instance.method() with patch('module.Foo') as mock: instance = mock.return_value instance.method.return_value = 'the result' result = some_function() assert result == 'the result'

给 mock 命名

给 mock 命名会让它的repr在测试失败信息中更可读,并且名称会传播到子属性/子方法上:

mock = MagicMock(name='foo') # <MagicMock name='foo' id='...'> mock.method # <MagicMock name='foo.method' id='...'>

跟踪所有调用:mock_calls 与 call 对象

mock.mock_calls记录对 mock 所有子属性(及其子级)的调用。用call对象构造期望列表再比较:

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

对mock_calls做断言的价值在于:它不仅断言期望的调用发生过,还隐式验证了调用顺序正确且没有多余调用。但要注意一个陷阱:对“返回 mock 的调用”的参数不会被记录,因此无法跟踪嵌套调用中创建祖先所用的参数:

m = Mock() m.factory(important=True).deliver() m.mock_calls[-1] == call.factory(important=False).deliver() # True,参数丢失

设置返回值与属性

设置返回值极其简单,且构造函数参数、属性直接赋值同样有效:

mock = Mock() mock.return_value = 3 mock() # 3 mock.method.return_value = 3 mock.method() # 3 Mock(return_value=3)() # 3 mock.x = 3 # 任意属性直接赋值

对更复杂的场景,例如mock.connection.cursor().execute("SELECT 1"),需要配置嵌套调用的结果;用call.call_list()可以把链式调用对象拆成调用列表以便断言:

mock = Mock() cursor = mock.connection.cursor.return_value cursor.execute.return_value = ['foo'] mock.connection.cursor().execute("SELECT 1") # ['foo'] expected = call.connection.cursor().execute("SELECT 1").call_list() mock.mock_calls == expected # True

side_effect:抛异常、可迭代与函数

side_effect是Mock最有用的属性之一,支持三种取值:

  1. 异常类或实例——mock 被调用时抛出该异常:

    mock = Mock(side_effect=Exception('Boom!')) mock() # 抛出 Exception: Boom!
  2. 可迭代对象——每次调用返回下一个值,适合 mock 会被调用多次且每次返回不同值的场景:

    mock = MagicMock(side_effect=[4, 5, 6]) mock() # 4 mock() # 5 mock() # 6
  3. 函数——函数以与 mock 完全相同的参数被调用,其返回值即为 mock 的返回值,可用于根据参数动态决定返回什么:

    vals = {(1, 2): 1, (2, 3): 2} def side_effect(*args): return vals[args] mock = MagicMock(side_effect=side_effect) mock(1, 2) # 1 mock(2, 3) # 2

Mock 异步迭代器与异步上下文管理器

自 Python 3.8 起,AsyncMock与MagicMock支持通过__aiter__mock 异步迭代器,通过__aenter__/__aexit__mock 异步上下文管理器。__aiter__的return_value用于设置迭代产生的值:

mock = MagicMock() # AsyncMock 同样适用 mock.__aiter__.return_value = [1, 2, 3] async def main(): return [i async for i in mock] asyncio.run(main()) # [1, 2, 3]

异步上下文管理器的__aenter__和__aexit__默认是AsyncMock实例:

class AsyncContextManager: async def __aenter__(self): return self async def __aexit__(self, exc_type, exc, tb): pass mock_instance = MagicMock(AsyncContextManager()) # AsyncMock 同样适用 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.py 中的_get_child_mock实现:当父 mock 是MagicMock且子 mock 名称落在_async_method_magics中时,子 mock 会被创建为AsyncMock;反之AsyncMock上的同步方法会被创建为MagicMock。

从已有对象创建 mock:spec 与 spec_set

过度使用 mock 的隐患是测试与 mock 的实现耦合,而非与真实代码耦合——重构掉某个方法后,测试依然会绿。spec关键字参数解决了这个问题:以对象(或类)作为 mock 的规范,访问规范对象上不存在的属性会立即抛出AttributeError:

mock = Mock(spec=SomeClass) mock.old_method() # AttributeError: Mock object has no attribute 'old_method'

使用 spec 还带来“更聪明的调用匹配”:无论参数是按位置还是按关键字传入都能正确比对:

def f(a, b, c): pass mock = Mock(spec=f) mock(1, 2, 3) mock.assert_called_with(a=1, b=2, c=3) # 成功

若想让方法调用也享受这种匹配,可以使用自动 spec(auto-speccing)。spec_set是更严格的形式:既禁止读取不存在的方法,也禁止设置任意属性。

用 mock_open 按文件返回不同内容

mock_open用于 patchopen;结合side_effect函数,可以为每次调用返回一个“全新”的 file mock,从而实现按文件名返回不同内容:

from unittest.mock import mock_open, patch DEFAULT = "default" data_dict = {"file1": "data1", "file2": "data2"} def open_side_effect(name): return mock_open(read_data=data_dict.get(name, DEFAULT))() with patch("builtins.open", side_effect=open_side_effect): with open("file1") as file1: assert file1.read() == "data1" with open("file2") as file2: assert file2.read() == "data2" with open("file3") as file2: assert file2.read() == "default"

mock_open的实现位于 mock.py#L2962,其返回的 file mock 本身仍是完整 mock,因此read、迭代等调用都会被记录、可断言。

Patch 装饰器:patch、patch.object、patch.dict

使用patch时必须注意:要在对象被查找的命名空间中替换它(详见官方文档“where to patch”一节)。模块和类在效果上是全局的,因此对它们的 patch 必须在测试结束后撤销,否则会泄漏到其他测试。mock 为此提供三个便捷装饰器:

  • patch('package.module.Class.attribute', new):以点分字符串定位属性,可指定替换值;
  • patch.object(obj, 'name', new):直接传对象与属性名;
  • patch.dict(dict, values, clear=False):在一个作用域内修改字典(包括模块字典、sys.modules),结束后还原。
original = SomeClass.attribute @patch.object(SomeClass, 'attribute', sentinel.attribute) def test(): assert SomeClass.attribute == sentinel.attribute test() assert SomeClass.attribute == original # 已还原

patch 一个模块(包括builtins)时用patch而非patch.object:

mock = MagicMock(return_value=sentinel.file_handle) with patch('builtins.open', mock): handle = open('filename', 'r') mock.assert_called_with('filename', 'r') assert handle == sentinel.file_handle

模块名可以是点分形式(package.module、package.module.ClassName.attribute)。装饰测试方法本身是推荐模式:

class MyTest(unittest.TestCase): @patch.object(SomeClass, 'attribute', sentinel.attribute) def test_something(self): self.assertEqual(SomeClass.attribute, sentinel.attribute)

当只提供 patch 目标(patch一个参数 /patch.object两个参数)时,mock 会被自动创建并作为额外参数传入测试函数/方法:

class MyTest(unittest.TestCase): @patch.object(SomeClass, 'static_method') def test_something(self, mock_method): SomeClass.static_method() mock_method.assert_called_with()

多个 patch 装饰器可以叠加。注意传参顺序:mock 按装饰器从下往上应用(Python 装饰器的常规顺序),因此下面例子中ClassName2的 mock 先传入:

class MyTest(unittest.TestCase): @patch('package.module.ClassName1') @patch('package.module.ClassName2') def test_something(self, MockClass2, MockClass1): self.assertIs(package.module.ClassName1, MockClass1) self.assertIs(package.module.ClassName2, MockClass2)

patch.dict用于字典作用域修改,支持clear=True先清空原内容:

foo = {'key': 'value'} with patch.dict(foo, {'newkey': 'newvalue'}, clear=True): assert foo == {'newkey': 'newvalue'} assert foo == {'key': 'value'} # 自动还原

patch、patch.object、patch.dict三者都可以作为上下文管理器使用,其中自动创建 mock 的场景用with ... as mock取到引用:

with patch.object(ProductionClass, 'method') as mock_method: real = ProductionClass() real.method(1, 2, 3) mock_method.assert_called_with(1, 2, 3)

它们同样可以作为类装饰器使用,等价于把装饰器分别应用到每个以test开头的方法上。从源码看,这一行为在_patch.decorate_class中实现:遍历类属性,凡以patch.TEST_PREFIX(即"test")开头的可调用属性,都会绑定该 patcher 的一份拷贝(patcher = self.copy()),从而保证每个测试方法各自独立 start/stop,互不串扰。

进阶示例

Mock 链式调用

一旦理解return_value,链式调用 mock 就很直接:mock 首次被调用(或提前取return_value)时会新建一个子Mock,因此可以通过审问return_value这个 mock 来查看“返回值”被如何使用:

mock = Mock() mock().foo(a=2, b=3) mock.return_value.foo.assert_called_with(a=2, b=3)

以真实代码为例,Something.method()中有一长串链式调用:

class Something: def __init__(self): self.backend = BackendProvider() def method(self): response = self.backend.get_endpoint('foobar').create_call('spam', 'eggs').start_call() # more code

假设只关心最终start_call的返回对象,可以用configure_mock以“点分路径”一步配置多层return_value,避免冗长的链式赋值:

something = Something() mock_response = Mock(spec=open) # 以 open 为 spec 的 file-like 对象 mock_backend = Mock() config = {'get_endpoint.return_value.create_call.return_value.start_call.return_value': mock_response} mock_backend.configure_mock(**config) something.backend = mock_backend something.method() # 一条断言验证整条链 chained = call.get_endpoint('foobar').create_call('spam', 'eggs').start_call() assert mock_backend.mock_calls == chained.call_list()

链式调用在代码中是一行,但会产生多条mock_calls记录,call.call_list()恰好能把一个call对象展开为对应的调用列表。configure_mock的实现位于 mock.py#L668。

部分 mock:patch date 但保留真实构造

datetime.date用 C 实现,无法直接 monkey-patch 其静态方法today。官方给出的方案是:把被测模块里的date整体替换为 mock,再通过side_effect把构造调用转发给真实的dt.date——date.today()返回固定日期,而date(...)仍创建真实日期对象:

import datetime as dt with patch('mymodule.date') as mock_date: mock_date.today.return_value = dt.date(2010, 10, 8) mock_date.side_effect = lambda *args, **kw: dt.date(*args, **kw) assert mymodule.date.today() == dt.date(2010, 10, 8) assert mymodule.date(2009, 6, 8) == dt.date(2009, 6, 8)

注意这里是 patch 使用方的模块(mymodule.date)而非全局datetime.date。构造调用也会被记入mock_date的call_count等属性。避免部分 mock 的替代做法是重写被测代码使其依赖注入日期,这也是官方文档指出的“用同被测代码的算法手算期望值”这一经典反模式的解法之一。

Mock 生成器方法

生成器函数被调用时返回生成器对象,真正的迭代发生在__iter__协议上,因此只需配置iter方法的return_value为一个真实迭代器:

class Foo: def iter(self): for i in [1, 2, 3]: yield i mock_foo = MagicMock() mock_foo.iter.return_value = iter([1, 2, 3]) list(mock_foo.iter()) # [1, 2, 3]

让每个测试方法共享同一组 patch

三种方式,由繁到简:

  1. 类装饰器:patch应用于类,自动作用于所有以test开头的方法(not_a_test不受影响):

    @patch('mymodule.SomeClass') class MyTest(unittest.TestCase): def test_one(self, MockSomeClass): self.assertIs(mymodule.SomeClass, MockSomeClass)
  2. setUp/tearDown 管理 start/stop:但要注意若setUp抛异常,tearDown不会被调用,stop 会漏掉。用addCleanup更稳:

    class MyTest(unittest.TestCase): def setUp(self): patcher = patch('mymodule.foo') self.addCleanup(patcher.stop) self.mock_foo = patcher.start() def test_foo(self): self.assertIs(mymodule.foo, self.mock_foo)
  3. 辅助方法消除嵌套缩进:当多个 patch 叠成深层with时,用create_patch+addCleanup拉平结构(“嵌套 patch”一节详述)。

Mock 未绑定方法:autospec=True 的妙用

直接用一个Mock替换类上的未绑定方法是有问题的:从实例取到的 mock 不会变成绑定方法,也就拿不到self。patch(..., autospec=True)解决此问题——它用一个真实函数对象做替换,该函数拥有与被替换函数相同的签名,内部再委托给 mock:

class Foo: def foo(self): pass with patch.object(Foo, 'foo', autospec=True) as mock_foo: mock_foo.return_value = 'foo' foo = Foo() foo.foo() # 'foo' mock_foo.assert_called_once_with(foo) # 断言时记得带上 self

从源码看,这正是_setup_func所为:autospec 创建的是一个funcopy(带__signature__的真实函数),把assert_called_with等断言方法与side_effect桥接到内部 mock 上;因为它是一个普通函数,Python 的描述符机制会自然地在实例属性访问时把它绑定,self便作为第一个参数传入。不使用autospec=True时,未绑定方法被换成普通Mock实例,不会被调用时带上self。

检查多次调用

assert_called_with与assert_called_once_with都只针对最近一次调用;assert_called_once_with还会断言call_count恰好为 1,多调一次即失败:

mock.foo_bar('baz', spam='eggs') mock.foo_bar.assert_called_once_with('baz', spam='eggs') mock.foo_bar() mock.foo_bar.assert_called_once_with('baz', spam='eggs') # AssertionError: Expected 'foo_bar' to be called once. Called 2 times.

要对所有调用断言,使用call_args_list配合call构造期望列表:

mock(1, 2, 3); mock(4, 5, 6); mock() mock.call_args_list # [call(1, 2, 3), call(4, 5, 6), call()] expected = [call(1, 2, 3), call(4, 5, 6), call()] mock.call_args_list == expected # True

应对可变参数:调用时快照

call_args/call_args_list保存的是参数的引用——若被测代码随后修改了可变参数,事后断言就会失真:

# mymodule 中: def frob(val): pass def grob(val): frob(val) val.clear() with patch('mymodule.frob') as mock_frob: val = {6} mymodule.grob(val) mock_frob.assert_called_with({6}) # AssertionError: Expected: (({6},), {}) Called with: ((set(),), {})

官方给出的解法是用side_effect函数在调用发生的瞬间深拷贝参数并转存到另一个 mock,且side_effect返回DEFAULT以保留原 mock 的正常返回值:

from copy import deepcopy from unittest.mock import Mock, patch, DEFAULT def copy_call_args(mock): new_mock = Mock() def side_effect(*args, **kwargs): args = deepcopy(args) kwargs = deepcopy(kwargs) new_mock(*args, **kwargs) return DEFAULT mock.side_effect = side_effect return new_mock with patch('mymodule.frob') as mock_frob: new_mock = copy_call_args(mock_frob) val = {6} mymodule.grob(val) new_mock.assert_called_with({6}) new_mock.call_args # call({6})

如果 mock 只会用一次,更简单的做法是直接在side_effect里断言;另一种更系统的做法是子类化Mock/MagicMock,在__call__中深拷贝参数:

class CopyingMock(MagicMock): def __call__(self, /, *args, **kwargs): args = deepcopy(args) kwargs = deepcopy(kwargs) return super().__call__(*args, **kwargs) c = CopyingMock(return_value=None) arg = set() c(arg) arg.add(1) c.assert_called_with(set()) # 成功 c.assert_called_with(arg) # 失败:实际记录的是快照 set()

由 mock 的子类派生出的动态属性和return_value会自动沿用同一子类,因此c.foo也是CopyingMock。

嵌套 patch 的拉平写法

多层with patch(...)会使缩进不断右移。借助addCleanup与 start/stop 可消除嵌套:

class MyTest(unittest.TestCase): def create_patch(self, name): patcher = patch(name) thing = patcher.start() self.addCleanup(patcher.stop) return thing def test_foo(self): mock_foo = self.create_patch('mymodule.Foo') mock_bar = self.create_patch('mymodule.Bar') mock_spam = self.create_patch('mymodule.Spam') assert mymodule.Foo is mock_foo # ...

测试结束后mymodule.Foo已还原为原对象。

用 MagicMock mock 字典

想要“像字典一样工作、同时记录所有访问”的 mock,用MagicMock配合side_effect把读写委托给真实字典即可;访问不存在的键会真实抛出KeyError:

my_dict = {'a': 1, 'b': 2, 'c': 3} def getitem(name): return my_dict[name] def setitem(name, val): my_dict[name] = val mock = MagicMock() mock.__getitem__.side_effect = getitem mock.__setitem__.side_effect = setitem mock['a'] # 1 mock['d'] # KeyError: 'd' mock['b'] = 'fish' mock['__getitem__'.join([]) or 'b'] # 'fish' mock.__getitem__.call_args_list # [call('a'), call('c'), call('d'), call('b'), ...]

官方文档还给出两个替代方案:其一,用Mock而只显式提供需要的魔术方法(mock.__getitem__ = Mock(side_effect=getitem)),行为更收敛;其二,MagicMock(spec_set=dict)让 mock 只暴露字典相关的魔术方法。

Mock 子类与 _get_child_mock

给Mock子类添加辅助方法是常见诉求。默认行为是:属性与return_value产生的子 mock 与父 mock 同类型,因此子类上的辅助方法会自动传播到所有子 mock:

class MyMock(MagicMock): def has_been_called(self): return self.called mymock = MyMock(return_value=None) mymock.foo # <MyMock name='mock.foo' id='...'>,同样有 has_been_called

若不希望子 mock 继承子类(例如某些适配器在属性上出现子类实例会引发错误),可以重写_get_child_mock——它接收**kwargs并转发给 mock 构造函数:

class Subclass(MagicMock): def _get_child_mock(self, /, **kwargs): return MagicMock(**kwargs) mymock = Subclass() assert not isinstance(mymock.foo, Subclass) assert not isinstance(mymock(), Subclass)

一个例外是不可调用的 mock:其属性会采用可调用变体,否则不可调用 mock 将永远无法拥有可调用方法(对应源码 mock.py#L1088-L1092 中的分支)。

用 patch.dict 替换 sys.modules 来 mock 导入

函数内部的局部 import 难以通过常规 patch 处理,因为 import 是从sys.modules字典中取对象,而该对象不必是模块。patch.dict可以在测试作用域内往sys.modules临时塞入 mock,结束自动还原:

import sys mock = Mock() with patch.dict('sys.modules', {'fooble': mock}): import fooble fooble.blob() assert 'fooble' not in sys.modules # 作用域结束后已还原 mock.blob.assert_called_once_with()

from fooble import blob形式同样可行;mock 包导入只需同时放入package与package.module两个键:

modules = {'package': mock, 'package.module': mock.module} with patch.dict('sys.modules', modules): from package.module import fooble fooble() mock.module.fooble.assert_called_once_with()

官方文档也提醒:局部 import 通常应避免(防循环依赖应重构,延迟导入可以用“类/模块属性 + 首次使用时导入”替代),mock 导入是最后手段。

跨多个 mock 的调用顺序跟踪

method_calls只能跟踪单个 mock 上的方法顺序。想要跨对象跟踪,可以制造一个“manager”父 mock,让各被测对象从它派生——由于访问任意属性都会创建子 mock,子 mock 的调用会按顺序记入父 mock 的mock_calls:

manager = Mock() mock_foo = manager.foo mock_bar = manager.bar mock_foo.something() mock_bar.other.thing() manager.mock_calls # [call.foo.something(), call.bar.other.thing()] expected_calls = [call.foo.something(), call.bar.other.thing()] manager.mock_calls == expected_calls # True

若 mock 是由patch创建的,可用attach_mock把它们挂到 manager 上(实现见 mock.py#L508):

manager = MagicMock() with patch('mymodule.Class1') as MockClass1: with patch('mymodule.Class2') as MockClass2: manager.attach_mock(MockClass1, 'MockClass1') manager.attach_mock(MockClass2, 'MockClass2') MockClass1().foo() MockClass2().bar() manager.mock_calls # [call.MockClass1(), call.MockClass1().foo(), call.MockClass2(), call.MockClass2().bar()]

如果只关心某个子序列,用assert_has_calls:只要该序列按序出现在mock_calls中即可,不要求是唯一调用;再加any_order=True则可忽略顺序:

m = MagicMock() m().foo().bar().baz() m.one().two().three() m.assert_has_calls(call.one().two().three().call_list()) m(1); m.two(2, 3); m.seven(7); m.fifty('50') m.assert_has_calls([call.fifty('50'), call(1), call.seven(7)], any_order=True)

更复杂的参数匹配:自定义 Matcher

当期望传入的参数是一个按身份比较的自定义对象,而你只关心其中几个属性时,可以为assert_called_with提供自定义Matcher:利用断言库内部用==比较实参与期望参数的机制,让Matcher.__eq__调用自定义比较函数:

class Foo: def __init__(self, a, b): self.a, self.b = a, b mock = Mock(return_value=None) mock(Foo(1, 2)) mock.assert_called_with(Foo(1, 2)) # AssertionError:两个 Foo 对象按身份不相等 def compare(self, other): if type(self) != type(other): return False if self.a != other.a: return False if self.b != other.b: return False return True class Matcher: def __init__(self, compare, some_obj): self.compare = compare self.some_obj = some_obj def __eq__(self, other): return self.compare(self.some_obj, other) mock.assert_called_with(Matcher(compare, Foo(1, 2))) # 成功 mock.assert_called_with(Matcher(compare, Foo(3, 4))) # AssertionError

小技巧:让比较函数直接抛出带更友好消息的AssertionError,失败输出会更有诊断价值。官方示例文档指出ANY(定义于 mock.py#L2529)就是同一思想的内置版本;社区库 PyHamcrest 也提供同类的 equality matcher。

测试与延伸阅读

unittest.mock的官方测试套件位于 Lib/test/test_unittest/testmock/testmock.py,覆盖了TestPatch、autospec、异步 mock 等本文涉及的各场景,是验证上述行为最权威的“活文档”;socket 相关的 mock 辅助则见 Lib/test/mock_socket.py。配套阅读路径:

  • 示例总入口(本文主体):Doc/library/unittest.mock-examples.rst
  • 完整 API 参考:Doc/library/unittest.mock.rst
  • 实现源码:Lib/unittest/mock.py
  • 模块包结构:Lib/unittest/

掌握本文内容后,你应能覆盖日常测试中绝大多数替身需求:用MagicMock与side_effect控制行为、用spec/autospec防止过度 mock、用patch系列在正确命名空间安全替换并自动还原,以及用mock_calls/call_list/assert_has_calls精确断言调用的参数、次数与顺序。

【免费下载链接】cpythonThe Python programming language项目地址: https://gitcode.com/GitHub_Trending/cp/cpython

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询