第3章:常用Mock框架对比 —— 选对工具少走弯路
做AOSP组件Mock回放,说白了就是替外部依赖“演一场戏”。你想想看,手机系统里那么多服务、硬件、NDK接口,总不能让每个单元测试都去跑真机吧?那太慢了,也太容易翻车。所以我个人一直觉得,选对Mock框架,是写好测试的前提。今天咱们就把几个主流框架拉出来遛遛。
3.1 先画一张全景图
下面这张图,我总结了几大框架的定位和核心差异。你看一眼,心里就有数了。
从图上你能看到,Mockito动如脱兔,EasyMock稳如老狗,PowerMock专治各种不服,jMock则是行为派的代表。下面咱们逐个拆解。
3.2 Mockito —— 我的主力框架
Mockito是我用得最多的框架,没有之一。它的API简洁到让人想哭。举个例子,你要mock一个网络接口:
// 前置:引入Mockito依赖(Gradle)
testImplementation "org.mockito:mockito-core:5.x"
@RunWith(MockitoJUnitRunner.class)
public class NetworkTest {
@Mock
private HttpService httpService;
@Test
public void testFetchData() {
// 只需要一行就能定义返回
when(httpService.get("/api/data")).thenReturn("mock data");
// 然后调用被测逻辑
String result = new DataFetcher(httpService).fetch();
assertEquals("mock data", result);
}
}
我个人习惯用注解方式,少写很多模板代码。不过它有个硬伤:不能mock静态方法、私有方法、final类。你说AOSP里那些鬼东西,动不动就是静态工具类、私有构造、系统Service,单靠Mockito经常玩不转。
3.3 EasyMock —— 老牌稳重型
我记得刚入行那会儿,EasyMock是的主流。它的「录制-重播」模式,说白了就是三步走:
- 录制:告诉框架,当这个方法被调用时,你该返回什么、抛什么异常。
- 切换:调用
replay(mock)进入回放模式。 - 验证:调用
verify(mock)检查是否按预期执行了。
代码感受一下:
@Before
public void setUp() {
mock = createMock(DataSource.class);
// 录制:当调用query时,返回一个假结果
expect(mock.query("select * from users")).andReturn(Arrays.asList("张三", "李四"));
replay(mock); // 切换到回放
}
EasyMock对调用顺序的验证非常精细。我曾在一次Systrace数据解析的测试中,需要严格保证A方法必须在B方法之前被调用,用EasyMock的 ordered 模式一秒搞定。但缺点也明显:API比Mockito啰嗦,而且同样是普通Java对象,无法处理静态方法。
3.4 PowerMock —— 专治各种不服
讲真,PowerMock就是那种「万不得已才用,但真香」的框架。它基于字节码修改,能mock静态、私有、final甚至构造函数。在AOSP的单元测试里,有些系统Service类的构造是包级私有的,new都new不出来。这时候PowerMock派上用场:
@RunWith(PowerMockRunner.class)
@PrepareForTest({SystemProperties.class})
public class SystemPropTest {
@Test
public void testGetProperty() {
mockStatic(SystemProperties.class);
when(SystemProperties.get("ro.build.version.sdk")).thenReturn("30");
assertEquals("30", SystemProperties.get("ro.build.version.sdk"));
}
}
但是——注意这里有个但是——PowerMock会修改类加载器,和Mockito的inline mock共存时容易出幺蛾子。我曾经在同一个测试类里同时用了 @RunWith(MockitoJUnitRunner.class) 和 PowerMock,结果类加载冲突,两个堆栈堆到一起,查了两天才发现是运行期冲突。
@PowerMockRunnerDelegate(MockitoJUnitRunner.class) 做桥接。但个人建议,只在真正需要mock静态/私有方法时才单独引入PowerMock。
3.5 jMock —— 行为驱动的清流
jMock跟前面几个不同,它强调「行为驱动测试」——你写的不只是mock期望,更是契约。它的API非常注重表达「什么时候、谁调用了什么、顺序如何」。比如:
Mockery context = new Mockery();
final MyInterface mock = context.mock(MyInterface.class);
context.checking(new Expectations() {{
oneOf(mock).doSomething("hello");
will(returnValue("world"));
}});
jMock的 oneOf()、exactly(2)、never() 这些约束词很语义化。我在「AOSP的Binder通信Mock」场景中用过它,因为需要精确验证跨进程调用的次数和顺序。不过它不支持静态方法,而且社区活跃度远不如Mockito,如果你的团队只有你一个人用,维护起来会比较孤独。
3.6 横向对比表
| 特性 | Mockito | EasyMock | PowerMock | jMock |
|---|---|---|---|---|
| API简洁度 | ⭐⭐⭐⭐⭐ | ⭐⭐⭐ | ⭐⭐⭐ | ⭐⭐⭐⭐ |
| mock静态/私有 | ✘ | ✘ | ✔ | ✘ |
| mock final类 | 半支持(>=4.5) | ✘ | ✔ | ✘ |
| 调用顺序验证 | 需额外写InOrder | 原生支持ordered | 弱 | 原生强验证 |
| 社区活跃度 | 极高 | 中等 | 低(已停更) | 低 |
| 在AOSP中的适用性 | 普通Java层可用 | 普通Java层可用 | 可对付系统Service | 适合严格行为测试 |
3.7 我的选型建议
最后给你一个我实际项目的选择逻辑,直接抄作业就行:
- 80%场景:只用Mockito。注解清爽、社区文档多、和JUnit 5整合完美。
- 15%场景:需要严格验证调用顺序或数量 —— 考虑jMock或EasyMock。我个人偏向EasyMock,因为API更接近Mockito的思维模式。
- 5%场景:非要mock静态/私有/构造怎么办?上PowerMock。但你得做好心理准备:它可能会和你的IDE或构建工具打架。我曾经因为PowerMock和JaCoCo冲突导致覆盖率变0,调了半天才找到原因。
嗯,你别贪多。把Mockito用到炉火纯青,再按需引入其他框架,这样既高效又不会把自己绕晕。你在AOSP的测试工程里,碰到最多的就是那种通过SystemProperties读取属性的工具类,或者通过BinderService获取的系统服务——记住了,PowerMock是你最后的保险,但不是你的日常武器。