Mock基础:什么是Mock、Mock与Stub的区别、Mock在测试中的角色
做接口测试的同学,多半都经历过这种尴尬:上游服务还没写好,下游服务动不动就挂,测试跑着跑着就卡住了。这时候就需要一位“替身演员”——Mock。说白了,Mock就是一个可以模拟真实对象的假对象,专门用来帮你把被测单元从外部依赖中解耦出来。
我个人最早接触Mock是在一个微服务改造项目中。那时团队要测支付模块,但第三方支付网关的沙箱环境三天两头出问题。我记得有次凌晨两点,测试同事在群里喊“网关又超时了”,整个CI流水线全红。从那之后,我下定决心要把Mock好好用起来——不依赖真实的网络,不依赖真实的数据库,测试环境才能稳定。
什么是Mock?
Mock,中文常翻译为“模拟对象”或“替身”。它的核心作用是:代替真实依赖,让你只专心测自己的代码逻辑。
先看一个最简单的例子。你写了一个用户注册服务,内部要调短信网关发验证码:
class UserService:
def __init__(self, sms_client):
self.sms_client = sms_client
def register(self, phone, password):
# 做校验...
self.sms_client.send(phone, "验证码:1234")
# 保存用户...
如果直接测这个类,你得配好短信网关地址、账号、密钥。而且在单测里真的发一条短信出去,既不经济也不可靠。这时候我就用Mock来替换sms_client:
from unittest.mock import Mock
def test_register():
sms_mock = Mock()
service = UserService(sms_mock)
service.register("13800138000", "abc123")
# 验证是否调用了 send
sms_mock.send.assert_called_once_with(
"13800138000", "验证码:1234"
)
看到没?我们没有真的发短信,只是检查了“有没有用正确的参数调用过send”。这就够了——因为短信发送的逻辑不是当前测试的重点,重点在于注册流程的正确性。
Mock vs Stub:差别在哪?
很多新手容易搞混这两个概念。我见过好几个项目,代码里混着用,测试可读性很差。咱们一次性说清楚。
| 维度 | Stub | Mock |
|---|---|---|
| 核心目标 | 提供预定义的数据(状态验证) | 验证行为是否发生、调用顺序、次数(行为验证) |
| 典型用法 | 返回固定值,比如stub.get_user() -> {"name":"张三"} |
记录调用历史,然后断言mock.method.assert_called_once() |
| 是否需要断言 | 通常不需要对Stub本身做断言,只对被测代码的返回值做断言 | 必须对Mock对象做断言,验证交互是否正确 |
| 举个例子 | 模拟数据库返回一条用户记录 | 验证“通知服务”的notify()被调用了几次 |
我以前带过一个新人,他写测试时总喜欢在Mock对象上调用return_value,然后又不做任何assert_xxx,测试跑完也不知道到底有没有调用。那就是把Mock当Stub用了——功能上也能跑,但失去了Mock最重要的意义:验证行为。
side_effect,导致测试通过但实际API调用根本不会触发重试逻辑。后来排查了一整天才发现——Mock把异常吞了。所以用Mock时,要确认它的行为足够接近真实场景。
Mock在测试中的角色
Mock在测试生态里到底扮演什么角色?我给它归纳了三个核心角色:
- 隔离器——切断被测代码对外部依赖(网络、数据库、文件系统、第三方服务)的直接联系,保证测试只运行在内存中,速度快、结果稳定。
- 行为记录仪——Mock会记住所有调用细节:调用了哪些方法、传入了什么参数、调用了多少次、顺序如何。你可以基于这些记录做精确断言。
- 场景模拟器——通过
side_effect模拟异常、超时、返回特定错误码等边界情况。这在真实环境下极难触发,但用Mock一行代码就能搞定。
你想想看,如果没有Mock,怎么模拟支付接口返回“余额不足”?你得到数据库里先把用户余额改掉,再调真实接口——万一没回滚干净,线上数据就乱了。用Mock:mock_pay.side_effect = InsufficientBalanceError,干净利落。
知识体系:Mock的三层认知
为了帮你快速建立整体认知,我画了一张结构图。这是本章最核心的框架,建议你盯着看两分钟。
这张图从左到右梳理了本章的核心脉络。你注意看,Mock与Stub的关键区别在于验证的目标——一个盯行为,一个盯状态。而Mock在测试中的三大角色,正好对应了我们日常写单测的核心需求:解耦、记录、模拟。
什么时候不该用Mock?
说完了优点,得泼盆冷水。Mock不是万能的。我自己总结过三条红线:
- 不要Mock自己当前测试的核心逻辑。比如你在测一个加密算法,把加密函数Mock掉——那这个测试就毫无意义了。
- 不要过度Mock。如果一个测试里Mock了七八个对象,代码读起来很痛苦,而且一改接口全崩。这时候不如考虑集成测试或写一个更精简的接口抽象层。
- 不要用Mock代替契约测试。Mock只能模拟调用签名,但无法保证真实服务的行为是否一致。如果你和第三方服务有接口约定,建议配合契约测试(如Pact)来证实双方的理解一致。
小结
Mock是什么?一个替身对象,帮你从外部依赖中解脱出来。Mock和Stub怎么分?一个验行为,一个给数据。Mock在测试中做什么?隔离、记录、模拟——三个角色缺一不可。