精通 unittest.mock:Python 测试替身完全指南

unittest.mock 深度教程:Mock 对象、行为控制、patch 打补丁、MagicMock、常见陷阱与最佳实践,写出真正隔离的单元测试。

最佳实践
并发任务的多路径协同插画

直接回答:unittest.mock 用可控的"替身"换掉测试中的真实对象——外部 API、数据库、时间、随机数——让单元测试快速、确定、可重复。 掌握 Mock、patch 与 MagicMock 三件套,再刁钻的依赖也能隔离。

Mock 的本质

被测代码依赖的"外部世界"(网络、时钟、第三方服务)在测试里都是不稳定源。Mock 用一个可编程的假对象取而代之:你规定它返回什么、记录它被怎么调用——测试从此只验证你的代码。

Mock 类

from unittest.mock import Mock

api = Mock()
api.get_user.return_value = {"name": "张三"}

result = api.get_user(1)          # 返回预设值
api.get_user.assert_called_once_with(1)   # 断言调用方式

未限制规格的 Mock 会为许多属性访问惰性创建子 Mock;使用 spec/spec_set 后会限制属性范围,某些保留名称也不会自动创建,调用后被默默记录——.called、.call_count、.call_args 任你断言。

控制行为三式

mock = Mock()
mock.method.return_value = 42                    # 固定返回
mock.method.side_effect = [1, 2, ValueError()]   # 逐次返回/抛错
mock.method.side_effect = lambda x: x * 2        # 函数计算

上面三行是不同的配置方式,后一次赋值会覆盖前一次;要验证各自行为应分开运行。side_effect 传列表玩"三次调用三种结果",传异常类/实例玩"这次调用炸了",传函数玩动态计算——测试各种分支全靠它。

patch:替换的真功夫

from unittest.mock import patch
from datetime import datetime, timezone
import mymodule  # 被测模块需提供 fetch_data,并按示例导入 requests/datetime

FIXED_TIME = datetime(2026, 1, 1, tzinfo=timezone.utc)

@patch("mymodule.requests.get")          # 在被使用的地方打补丁
def test_fetch(mock_get):
    mock_get.return_value.json.return_value = {"ok": True}
    assert mymodule.fetch_data() == {"ok": True}

# 上下文管理器形式
def test_other():
    with patch("mymodule.datetime") as mock_dt:
        mock_dt.now.return_value = FIXED_TIME
        ...

铁律:patch 的目标是"被测代码引用它的位置",不是定义它的位置。 mymodule 里 import requests 后用的是 mymodule.requests——补错位置是 mock 不生效的头号原因。

MagicMock 与专用 Mock

MagicMock 是默认 Mock 的增强版:支持魔术方法——len()、[] 取值、迭代、上下文管理器协议都能模拟:

from unittest.mock import MagicMock

m = MagicMock()
m.__len__.return_value = 5
len(m)        # 5

patch.object(补对象属性)、patch.dict(改字典)、AsyncMock(异步函数专用,3.8+ 自动识别)——各有各的战场。

常见陷阱

  1. 补错位置:见上;应按被测模块的实际导入方式定位;
  2. 过度 Mock:把整个宇宙都 Mock 掉,测试就成了验证 Mock 配置——集成层留少量真实测试;
  3. Mock 规格漂移:真实接口改了 Mock 没改,测试绿灯线上爆炸——autospec=True 让 Mock 遵守真实签名;
  4. 断言笔误:mock.assert_called_once(少了括号)永远不会失败——用 assert_called_once_with() 等真方法。

最佳实践

  • 每个测试只 Mock 直接依赖,别 Mock 依赖的依赖;
  • 优先 autospec 保真;
  • 测试结构保持 Arrange(配 Mock)→ Act → Assert(断行为 + 断调用)三段清晰;
  • 用 mock_calls 断言调用顺序(对时序敏感的逻辑)。

常见问题(FAQ)

Q:patch 装饰器和 with 形式怎么选?
A:整个测试函数都需要用装饰器;局部使用用 with(作用域即生命周期,更安全)。类装饰器批量补一组场景少见但存在。

Q:异步函数怎么 Mock?
A:AsyncMock:mock.fetch = AsyncMock(return_value=data)。直接塞普通 Mock 会报"不可 await"。

Q:什么时候不该用 Mock?
A:纯函数、值对象不需要;数据库逻辑复杂时,起一个测试库(testcontainers)比层层 Mock 更真实。Mock 是工具,不是信仰。

官方参考

本文基于官方文档整理,未进行运行时或性能测试。示例中的业务函数、数据模型和部署地址需结合项目补全;局部片段不等同于完整生产应用。

延伸阅读

获取专属方案

联系我们

加入社区

微信扫码
加入官方交流群

立即体验

在线开通,按量计费,真正的云服务!

立即开始

选择观测云版本

代码托管平台