第3章:常用Mock框架对比 —— 选对工具少走弯路

做AOSP组件Mock回放,说白了就是替外部依赖“演一场戏”。你想想看,手机系统里那么多服务、硬件、NDK接口,总不能让每个单元测试都去跑真机吧?那太慢了,也太容易翻车。所以我个人一直觉得,选对Mock框架,是写好测试的前提。今天咱们就把几个主流框架拉出来遛遛。

3.1 先画一张全景图

下面这张图,我总结了几大框架的定位和核心差异。你看一眼,心里就有数了。

常用Mock框架对比全景图 Java Mock框架 Mockito EasyMock PowerMock jMock Mockito 注解驱动+简洁API 不能mock静态/私有 EasyMock 录制-回放模式 Stub控制精细 PowerMock 可mock静态/私有/构造 基于字节码修改 jMock 行为驱动测试 自动验证调用顺序 适用场景对比 推荐 普通接口/类Mock → Mockito(最常用) 严格调用顺序验证 → jMock / EasyMock 需要mock静态/私有/构造 → PowerMock

从图上你能看到,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经常玩不转。

小技巧:Mockito从4.5版本开始支持inline mock的experimental模式,可以有限度地mock final类。但静态方法?你还是得靠下一节说的那个家伙。

3.3 EasyMock —— 老牌稳重型

我记得刚入行那会儿,EasyMock是的主流。它的「录制-重播」模式,说白了就是三步走:

  1. 录制:告诉框架,当这个方法被调用时,你该返回什么、抛什么异常。
  2. 切换:调用 replay(mock) 进入回放模式。
  3. 验证:调用 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,结果类加载冲突,两个堆栈堆到一起,查了两天才发现是运行期冲突。

避坑指南:PowerMock + Mockito不要混用!如果实在需要同时支持,用 @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是你最后的保险,但不是你的日常武器。