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?直接上真实依赖不行吗?嗯,理论上可以,但现实中你会遇到一堆麻烦。

  1. 隔离依赖 —— 被测单元可能依赖数据库、网络、硬件等。这些依赖不稳定、难搭建,用Mock把它们"切掉",测试只测自己的逻辑。
  2. 加速测试 —— 一个真实Binder调用可能要几十毫秒,Mock调用只要几微秒。你想想看,几千个测试用例,每个省几十毫秒,差距就出来了。
  3. 模拟异常 —— 真实环境中,网络超时、设备断开、返回空数据……这些异常很难复现。Mock可以一键"制造"任何异常,覆盖率直接拉满。
  4. 并行开发 —— 依赖的模块还没写好?没关系,先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 概念 什么是 Mock 为什么用 Mock Mock vs Stub 模拟真实对象 行为可控替代 隔离依赖加速 异常场景模拟 状态 vs 行为验证 Stub预设Mock验证 💡 核心要点 • Mock是"替身演员",替代真实依赖,让测试专注自身逻辑 • 使用Mock的核心动因:隔离、加速、模拟异常、并行开发 • Mock关注行为验证,Stub关注状态验证 —— 这是本质区别

回到开头那个比喻:替身演员不是"假演员",而是为了让电影拍得更好而存在的"专业替代"。Mock也是一样——它不是"假测试",而是为了让测试更可靠、更高效而存在的工程实践。搞清楚了Mock是什么、为什么用、跟Stub怎么区分,后面讲Mock回放设计时你才不会懵。