Mock概念:什么是Mock、为什么用Mock、Mock与Stub的区别
说实话,Mock这个词在测试圈里已经被说烂了。但要真问它到底是什么、为什么非用不可、跟Stub有啥区别——能说清楚的人还真不多。我个人习惯把Mock比作"替身演员":主角拍不了的动作戏,替身上;测试里依赖跑不了的环境,Mock上。
你想想看,在AOSP测试里,一个Service可能依赖Binder、依赖HAL、依赖电源管理……要是每次跑测试都得把这些真实环境搭好,那测试就变成了"环境搭建大赛"。Mock就是来解决这个问题的。
1. 什么是Mock
Mock,说白了就是一个"假的"实现。它模拟真实对象的行为,但所有逻辑都由你控制。你让它返回什么它就返回什么,你希望它被调用几次它就被调用几次——一切尽在掌握。
Mock有三个核心特征:
- 模拟真实对象 —— 实现相同的接口或协议,外部调用者感知不到差异
- 行为可控 —— 返回值、抛出异常、延时等都可以预设
- 轻量替代 —— 不需要初始化真实环境,没有副作用
我记得在给AOSP音频服务写测试时,需要依赖底层的Audio HAL。真实HAL涉及硬件通信,测试环境根本跑不起来。后来我用Mock替代了HAL接口,测试才真正"活"了过来。
Mock的本质:用一个可控的"替身"替代真实依赖,让测试聚焦在被测逻辑本身。
2. 为什么用Mock
为什么要用Mock?直接上真实依赖不行吗?嗯,理论上可以,但现实中你会遇到一堆麻烦。
- 隔离依赖 —— 被测单元可能依赖数据库、网络、硬件等。这些依赖不稳定、难搭建,用Mock把它们"切掉",测试只测自己的逻辑。
- 加速测试 —— 一个真实Binder调用可能要几十毫秒,Mock调用只要几微秒。你想想看,几千个测试用例,每个省几十毫秒,差距就出来了。
- 模拟异常 —— 真实环境中,网络超时、设备断开、返回空数据……这些异常很难复现。Mock可以一键"制造"任何异常,覆盖率直接拉满。
- 并行开发 —— 依赖的模块还没写好?没关系,先Mock一个接口,两端同时开发,互不阻塞。
说实话,我早期写测试时就踩过这个坑:为了"真实",所有依赖都用实际实现。结果测试跑一次要5分钟,还经常因为环境问题失败。后来改用Mock,跑完只要10秒,而且基本不出现"环境挂了"的假失败。
我的经验:用Mock不是为了"造假",而是为了控制变量。测试的目标是验证自己的代码,不是验证别人的依赖。
3. Mock与Stub的区别
这个问题,面试常问,项目里也常搞混。我曾经在code review时看到有人把Stub当Mock用,结果漏了一个关键调用没验证——线上出了bug才追悔莫及。
核心区别就一句话:Stub关注"返回什么",Mock关注"怎么被调用的"。
Stub像一台"自动售货机":你投币,它出货。至于你投了几次、什么时候投的——它不关心。Mock则像一个"监控摄像头":它不仅能出货,还能记录你投了几次币、按了哪个按钮、顺序对不对。
| 对比维度 | Mock | Stub |
|---|---|---|
| 核心目标 | 验证交互行为 | 提供预设返回值 |
| 是否记录调用 | 是 —— 记录调用次数、参数、顺序 | 否 —— 只返回固定数据 |
| 验证方式 | 行为验证(verify) | 状态验证(assert) |
| 返回值 | 可动态预设,不同场景不同值 | 通常硬编码,固定不变 |
| 典型场景 | 验证某个方法是否被调用、调用次数 | 提供测试数据,让被测逻辑走通 |
看代码更直观。假设有一个远程服务接口:
// 真实接口
public interface IRemoteService {
String fetchData(String key);
}
这是Stub的实现——它只负责"给数据":
// Stub —— 只返回固定值
public class StubRemoteService implements IRemoteService {
@Override
public String fetchData(String key) {
return "stub_data"; // 永远返回同一个值
}
}
这是Mock的实现——它既给数据,又记录调用:
// Mock —— 可预设 + 可验证
public class MockRemoteService implements IRemoteService {
private String mockData;
private int callCount = 0;
private String lastKey;
public void setMockData(String data) {
this.mockData = data;
}
public int getCallCount() {
return callCount;
}
public String getLastKey() {
return lastKey;
}
@Override
public String fetchData(String key) {
this.lastKey = key;
this.callCount++;
return mockData;
}
}
⚠️ 避坑指南:我曾经因为分不清Mock和Stub,用一个Stub去验证"某个依赖是否被调用了3次"——结果Stub根本不记录调用次数,测试永远通过,但线上行为完全不对。记住:要验证行为,用Mock;只想要数据,用Stub。
4. 实践中的选择建议
那什么时候用Mock,什么时候用Stub?我个人的经验是:
- 用Mock的场景:你需要确认某个方法被调用了、调用了几次、参数对不对。比如验证"用户点击按钮后,上报接口被调用了1次"。
- 用Stub的场景:你只需要依赖返回一个值让被测逻辑走通,不关心它怎么被调用的。比如"数据库查询返回一个用户列表,界面显示出来"。
简单记法:Stub是"哑巴演员"——按剧本说台词就行;Mock是"间谍演员"——说完台词还要汇报谁找了你、说了啥。
一个项目里通常两者都会用到。我在AOSP测试中,对于ServiceManager、Binder等需要验证调用的地方用Mock;对于配置读取、数据提供等只关心返回值的用Stub。混着用,舒服。
回到开头那个比喻:替身演员不是"假演员",而是为了让电影拍得更好而存在的"专业替代"。Mock也是一样——它不是"假测试",而是为了让测试更可靠、更高效而存在的工程实践。搞清楚了Mock是什么、为什么用、跟Stub怎么区分,后面讲Mock回放设计时你才不会懵。