一、AOSP组件Mock回放概述:为什么需要Mock回放、核心概念与适用场景
我们先聊聊第一个问题:为什么需要 Mock 回放?
在做 AOSP 测试的时候,你一定遇到过这种情况——某个系统服务(比如 PackageManagerService)的行为非常复杂,一次调用可能涉及数据库、文件系统、底层 Binder 驱动。你想写一个单侧,但发现根本跑不起来。为什么?因为依赖太重了。
说白了,AOSP 的组件之间是高度耦合的。你要测 A,就得把 B、C、D 全都搭好。这是典型的环境依赖问题。我曾经在做一个双卡功能的回归测试时,就因为某个系统属性的状态不对,导致单侧跑了一整天都没通过。后来查了半天,发现是模拟器里的一个 Service 启动顺序乱了。嗯,就在那时候,我开始认真思考 Mock 回放的价值。
Mock 回放要解决的核心矛盾是:“我想测我的逻辑,但我不关心底层基础服务的具体行为”。你想想看,如果你能录一段真实系统里 Service 的响应,然后在测试时直接回放,是不是就可以把这些依赖剥离掉了?
1.1 核心概念拆解
在进入技术细节之前,我们先把这个概念说清楚。Mock 回放不是指简单的对象 Mock,它分为三个阶段:
- 录制(Record):在真实设备或模拟器上,拦截组件间的 Binder 调用、Intent 分发、ContentProvider 查询等交互,把请求和响应对记录下来。
- 存储(Store):把录制的数据序列化成可复用的文件格式(比如 JSON、Protobuf)。
- 回放(Replay):在测试环境中加载录制数据,拦截相同的调用,直接返回记录好的响应。
举个例子。你在测一个 App 的安装流程,其中调用了 PackageInstaller.commitSession()。正常情况下,这个方法会走一个很长的系统调用链。但在 Mock 回放模式下,你可以这样写:
// 伪代码示意
@MockReplay("package_install_commit_session.json")
fun testInstallSession() {
val sessionId = PackageInstaller.createSession(...)
// 这里所有对 PackageManagerService 的 Binder 调用
// 都会被代理层拦截,直接返回录制好的结果
val result = PackageInstaller.commitSession(sessionId)
assertTrue(result.isSuccess)
}
你可以看到,测试代码非常干净,完全不依赖真实的系统环境。我个人习惯把这种模式叫做“带上下文快照的 Mock”——它不仅 Mock 了返回值,还 Mock 了调用次序和参数匹配。
1.2 为什么传统 Mock 不够用?
我知道你要问:直接用 Mockito 之类的不行吗?当然行,但要分场景。
| 对比维度 | 传统 Mock | Mock 回放 |
|---|---|---|
| 依赖管理 | 需手写每个 Stub 的行为 | 自动录制真实行为 |
| 复杂回调 | 手写回调逻辑非常痛苦 | 回调数据自动回放 |
| 跨进程调用 | Mockito 无法直接 Mock Binder | 透明代理,Binder 调用也可拦截 |
| 回归成本 | API 变更需要手动更新 Mock 逻辑 | 重新录制即可 |
| 测试稳定性 | 依赖 Mock 实现者的理解是否准确 | 依赖真实录制数据,更接近生产环境行为 |
我记得有一次,我们团队用 Mockito 模拟 ContentProvider 的查询,结果写错了返回值格式,导致测试通过但上线后崩溃。改用 Mock 回放后就再没出过这种问题。
1.3 适用场景归纳
不是所有场景都适合用 Mock 回放。我根据自己的实际经验,总结了几个典型场景:
- 自动化回归测试:每次代码提交后跑一遍,验证核心组件的行为没有退化。Mock 回放可以极大缩短 CI 流水线的耗时,因为不用启动完整的模拟器环境。
- 本地环境快速验证:你开发了一个新功能,想快速度过“能不能跑起来”的阶段。用 Mock 回放把环境依赖剥离掉,几分钟就能得到反馈。
- 依赖不稳定或无法访问:比如某些系统 Service 只在特定硬件上存在(如 NFC Service)。使用 Mock 回放,你可以把这些 Service 的调用提前录制好,在普通模拟器上也能测试。
- 边界条件构造:你可以故意录制一些带有异常数据的回放文件,用来验证自己的代码在边界情况下的处理是否正确。
1.4 知识体系总览
下面的 SVG 图帮我们把刚才讲的内容串起来。你可以看到整个 Mock 回放的核心逻辑:左边是输入(Mock 规则),中间是核心引擎(代理层与录制/回放引擎),右边是输出(验证结果或行为快照)。这是本章知识体系的结构化表达。
WindowManagerService 的 addView() 调用,然后写一个测试验证回放是否正常工作。成功一次之后,再扩展到更复杂的多组件交互。
1.5 一个简单的入门示例
为了让你快速理解,我给你看一个最简单但完整的 Mock 回放流程。假设我们要录制一次 BatteryStatsService 的查询,然后在测试中回放:
- 录制阶段:在真实设备上启动录制代理,执行一次 getStatistics() 调用,代理层把请求参数和返回结果写入本地文件。
- 存储阶段:将录制文件 push 到测试工程目录,比如
test/resources/records/battery_stats.json。 - 回放阶段:在测试代码中,用 MockReplay 注解指定回放文件,然后调用相同的 getStatistics() 方法。
你想想看,这个过程如果能跑通,以后你测别的 Service 就只是录制一下的问题了。
核心要点:Mock 回放的关键不是“Mock”本身,而是“回放”的精确性。你必须确保代理层能完整捕获调用链的信息,包括参数类型、顺序、返回类型,甚至是跨进程调用的事务 ID。否则回放时会出现行为不匹配。