测试框架选择:JUnit、Mockito、Robolectric、Espresso的适用性对比

测试框架选型是 WorkManager Mock 回放方案的第一步。选错了,后面全是坑。我从做安卓测试开始,见过太多人把四个框架混成一团,结果跑个单元测试都要等三分钟。今天我把它们拆开讲清楚。

JUnit:一切测试的根基

JUnit 是 Android 测试中最基础的框架,没有它其他几个都没法跑。说白了,它就是一套用来写测试用例的规则——@Test 标记方法,@Before 做初始化,assertThat 做校验。你在任何测试里看到的 Runner 也是基于 JUnit 的。

我习惯把 JUnit 当作“底层地板”。不管用 Mockito 还是 Robolectric,最终执行的测试类都必须继承或关联 JUnit 的 Runner。举个最简单例子:

import org.junit.Test;
import static org.junit.Assert.assertEquals;

public class SimpleTest {
    @Test
    public void additionIsCorrect() {
        assertEquals(4, 2 + 2);
    }
}

这里要注意:JUnit 本身不提供任何 Android 环境,它只负责运行测试并报告结果。所以如果你的测试直接调用了 Context 或 WorkManager,JUnit 会直接抛出 NullPointerException。嗯,这就是为什么我们需要其他框架来“补环境”。

Mockito:轻松隔离外部依赖

Mockito 是我最常用的框架之一。它的核心能力就是“假对象”——把一个真实的类或者接口替换成 Mock 对象,并定义它的行为。我在做 WorkManager 的回放测试时,经常用 Mockito 把 WorkManager 的实例 mock 掉,避免真的去调度任务。

import static org.mockito.Mockito.*;

// 创建 mock 对象
WorkManager mockWM = mock(WorkManager.class);

// 定义行为:当 getWorkInfosByTag 被调用时返回一个空列表
when(mockWM.getWorkInfosByTag(eq("tag")))
    .thenReturn(LiveData<List<WorkInfo>>.empty());

// 验证调用
verify(mockWM).enqueue(any(OneTimeWorkRequest.class));

我曾经在项目里犯过一个错:用 Mockito 去 mock Context,结果系统底层方法太多,mock 出去后一大堆 NullPointerException。后来我改用 Robolectric 替真实环境,只在需要验证交互逻辑时才用 Mockito。记住:Mockito 适合“隔离外部服务”,不适合“模拟系统环境”。

个人技巧:如果你用 Mockito 配合 JUnit 4,别忘了加上 @RunWith(MockitoJUnitRunner.class) 或者使用 MockitoAnnotations.openMocks(this) 自动初始化 mock 对象。不然每次都要手动 Mockito.mock(),代码会变得很啰嗦。

Robolectric:托管 Android 框架模拟

Robolectric 解决了 JUnit 不能跑 Android 代码的问题。它通过一套叫作“Shadow”的机制,在 JVM 上模拟了 Android 的核心组件:Context、Resources、Activity、甚至 WorkManager 的内部实现。你写测试时感觉就像在真设备上运行。

@RunWith(RobolectricTestRunner.class)
public class MyWorkManagerTest {

    @Test
    public void testWorkScheduling() {
        Context context = ApplicationProvider.getApplicationContext();
        WorkManager workManager = WorkManager.getInstance(context);
        OneTimeWorkRequest request = new OneTimeWorkRequest.Builder(MyWorker.class)
                .addTag("sync")
                .build();
        workManager.enqueue(request);
        // 在这里可以断言 WorkManager 状态
    }
}

Robolectric 的好处是“零模拟成本”,你不需要写那么多 mock 代码。但代价是什么?速度慢。每个测试都要加载 Android SDK,如果项目大了,整个测试套件跑下来要十几分钟。我个人只在“需要验证 Android 生命周期或 WorkManager 状态流转”时才用 Robolectric,纯业务逻辑还是用 JUnit + Mockito 更快。

注意:Robolectric 4.x 之后默认使用 @Config(sdk = {Build.VERSION_CODES.P}) 来指定模拟的 API 版本。如果你不指定,它会使用你电脑上最高的版本,可能导致兼容性问题。我曾经因为这个让测试在 CI 上全挂——就是因为 CI 机器 SDK 版本比本地高,某些 Shadow 方法行为不一致。

Espresso:从 UI 角度验证整体流程

Espresso 是用来做 UI 测试的。它跟 WorkManager 的关系很微妙——你一般不会直接用 Espresso 去测 WorkManager,但测试一个包含后台任务的完整用户流程时,Espresso 是唯一能验证界面最终状态的框架。比如:点击“同步”按钮 → WorkManager 执行任务 → 进度条消失 → 显示完成文案。这个链条用 Espresso 来做最自然。

@Test
public void syncButtonTriggersWork() {
    onView(withId(R.id.btn_sync)).perform(click());
    // 等待 WorkManager 完成(需要配合 IdlingResource)
    onView(withId(R.id.tv_status))
            .check(matches(withText("已同步")));
}

但 Espresso 有个硬伤:它依赖真实设备或模拟器。你不能在 JVM 上直接跑,必须接入 Gradle 的 connectedCheck。而且 WorkManager 是异步的,你必须用 IdlingResource 或者 WorkManagerTestInitHelper 来等待后台任务完成。我建议只在核心业务场景上才写这种集成测试,覆盖率不用追求 100%。

四框架对比表

框架 运行环境 核心能力 适用场景 速度 我踩过的坑
JUnit JVM 测试运行器、断言 纯业务逻辑、工具类 极快 忘了加 Runner 导致 mock 不生效
Mockito JVM 对象隔离、行为定义、验证 WorkManager 交互逻辑、数据层 快 Mock Context 导致大量 NPE
Robolectric JVM + Shadow Android 环境模拟 Android 组件状态、WorkManager 调度 较慢 SDK 版本不匹配导致 Shadow 失效
Espresso 真实设备/模拟器 UI 交互、控件校验 端到端用户流程 极慢 异步等待没处理好,测试总超时

如何组合?给出我的推荐模式

你不需要在每次测试里都用上所有框架。我个人在 WorkManager Mock 回放方案里采用“分层测试”策略:

  1. 单元级:用 JUnit + Mockito 测试 Worker 类的业务逻辑。把 WorkManager 接口 mock 掉,只验证你写的 doWork 方法返回成功或失败。
  2. 集成级:用 Robolectric 测试 WorkManager 的调度和状态。检验 enqueue、cancel、getWorkInfos 等方法调用后状态是否正确。这部分跑得慢,只在关键路径写。
  3. UI 级:用 Espresso 写 1-2 个冒烟测试,覆盖“用户触发→后台任务→界面反馈”的全链路。这些测试作为预发布 Gate,跑一趟十几分钟,但能拦住致命问题。

这样分层后,80% 的测试都在 JVM 上跑,10 秒内就能得到反馈。只有 20% 的测试需要模拟环境或真实设备。我建议你也按这个思路来,不然测试会变成开发瓶颈。

流程图:测试框架选择路径

WorkManager 测试框架选择路径 JUnit 基础层 测试运行器 + 断言 Mockito 隔离层 Mock 外部依赖 Robolectric 模拟层 Android 环境 Shadow Espresso UI 层 端到端 UI 交互 服务端逻辑 vs 系统环境 vs 用户流程 根据测试目标选择:Mockito 用于逻辑隔离;Robolectric 用于环境模拟;Espresso 用于 UI 验证 最终:分层测试套件 80% 快速JVM测试 + 20% 慢速集成/UI测试

核心思路:先确定你要测的是“逻辑”还是“系统”还是“UI”。逻辑用 Mockito,系统用 Robolectric,UI 用 Espresso。它们之间不是互斥关系,而是互补的层次。我在实际项目中,一个 WorkManager 功能通常会写这样三种测试,覆盖率达到 90%,而维护成本并没有增加多少。