什么是Mock?——从一次“假”集成说起
聊到测试,很多人第一反应就是「写单元测试,把真实环境跑一遍」。但现实很骨感——你调用的第三方支付接口动不动就限流,数据库里永远缺几条脏数据,微服务间网络抖动让你测试跑得比生产还慢。这时候,就需要「假」的东西上场了。
我最早接触Mock是在一个电商项目里。当时要测订单结算流程,每次跑测试都得真的去扣库存、调支付网关。有一次线上支付网关临时维护,整个CI流水线挂了半天。后来我把支付模块Mock掉,测试速度从3分钟降到20秒。嗯,从那以后,我再也不敢裸测外联服务了。
Mock对象:模拟行为,验证交互
Mock的核心思想很简单:创建一个「替身」对象,让它按照你的脚本返回预设结果。测试时只关心被测代码和这个替身之间的交互对不对,而不关心替身内部逻辑。
# 假设你有一个支付网关客户端
class PaymentGateway:
def charge(self, amount):
# 真的去调远程API
pass
# 测试时用Mock替代真实网关
mock_gateway = Mock()
mock_gateway.charge.return_value = {"status": "success"}
order_service = OrderService(mock_gateway)
result = order_service.checkout(100)
assert result["status"] == "success"
# 验证charge是否被调用了一次
mock_gateway.charge.assert_called_once_with(100)
你看,Mock不但可以返回假数据,还能记住「谁调了我」「传了什么参数」「调了几次」。这些验证能力是Mock独有的。
Stub:提供数据,不问过程
Stub和Mock很像,但侧重点不同。Stub只负责「给数据」,它不关心你调没调,调了几次——它就是个沉默的数据源。
# Stub:永远返回固定值
class UserServiceStub:
def get_user(self, user_id):
return {"id": 1, "name": "Alice", "bal": 100}
user_service = UserServiceStub()
result = some_business_logic(user_service)
# 测试只关心逻辑,不关心user_service是否被调了多次
我在项目中经常用Stub来替代那些「只读」的外部依赖——比如配置中心、字典表、静态数据服务。它们没有写操作,验证交互意义不大,给个固定值就够了。
Fake:轻量实现,真逻辑假数据
Fake是三者中最「实诚」的——它内部有真正的业务逻辑,但数据存储被简化了。比如用内存HashMap模拟数据库,用本地文件系统模拟云存储。
# Fake:用dict模拟数据库
class FakeUserRepository:
def __init__(self):
self._store = {}
def save(self, user):
self._store[user.id] = user
def find_by_id(self, user_id):
return self._store.get(user_id)
def test_user_save():
repo = FakeUserRepository()
service = UserService(repo)
service.create_user({"id": 1, "name": "Bob"})
assert repo.find_by_id(1).name == "Bob"
回想有一次要测复杂的业务聚合逻辑,涉及多条记录的事务。用Mock和Stub根本覆盖不了数据状态流转,最后我写了一个Fake仓库,用内存表模拟了所有CRUD操作。测试跑得飞快,而且中途重构了存储层,Fake代码几乎没动——因为它复用了真实接口的逻辑骨架。
三者对比:一张表说清
| 特性 | Mock | Stub | Fake |
|---|---|---|---|
| 核心目的 | 验证交互行为 | 提供预设数据 | 轻量实现真实逻辑 |
| 是否验证调用次数 | 是(必须) | 否 | 否(但可通过辅助方法验证) |
| 内部是否有真实逻辑 | 否(只有模拟行为) | 否(只有返回值) | 是(简化版实现) |
| 典型场景 | 外部API、消息队列、日志 | 配置中心、静态数据 | 数据库、文件系统、缓存 |
| 维护成本 | 中等(需跟随接口变化) | 低 | 较高(需维护伪实现) |
| 适合的测试类型 | 单元测试、集成测试 | 单元测试 | 集成测试、契约测试 |
概念关系与选择指南
Mock管交互,Stub管数据,Fake管逻辑。
当你需要验证“调没调对”→ 用Mock;只需要“给个值别报错”→ 用Stub;需要“让测试跑起来且带真实逻辑”→ 用Fake。
• 对外部系统接口用Mock(因为它确实不稳定)
• 对自己的模块间交互尽量用Fake(因为重构时只改Fake不改变量)
• Stub只用于那些“永远不变”的配置值——否则你会在几十个测试里分别维护重复的Stub,痛苦。
Mock回放在CI中的价值
你想想看,CI流水线每次提交都去跑真实环境——等第三方返回,等数据库建表,等消息队列确认……整个流程能慢到什么程度?我见过一个项目的CI全量测试跑了2小时,其中80%在等待外部依赖。
用了Mock回放之后:录制一次真实请求-响应对,然后在每次CI运行时直接用录制好的数据「回放」给被测系统。这样速度提升了,而且确定性极强——再也不用担心测试因为网络抖动而红了。
说白了,Mock回放就是「用一场录好的戏来代替现场直播」。而理解Mock、Stub、Fake的区别,是搭建回放方案的第一块基石。
Mock像警察,查谁打过电话;
Stub像仓库,只管给货不管查;
Fake像小卖部,麻雀虽小五脏全。