测试基础:JUnit、Mockito、Robolectric 等工具介绍

开始做 AOSP 测试之前,咱们得先把地基打牢。

我记得刚转做 Android 平台测试时,面对 JUnit、Mockito、Robolectric 这些名字,心里直打鼓。到底先学哪个?它们各自解决什么问题?

说白了,这三兄弟分工非常明确:

  • JUnit —— 测试框架的骨架,所有单元测试的基础设施。
  • Mockito —— 模拟依赖的利器,帮我们把外部对象“隔离”掉。
  • Robolectric —— 在 JVM 上模拟 Android 环境,省去跑模拟器的麻烦。

下面我挨个聊聊,结合我自己踩过的坑,给你讲透它们怎么用。

AOSP 测试基础工具体系 AOSP 测试基础工具体系 JUnit 测试框架 Mockito 模拟 Robolectric 环境 @Test 注解 断言 Assert 测试套件 Suite Mock 创建 Stubbing Verify 验证 Shadow 类 Activity 模拟 资源加载 三个工具分层协作:JUnit 跑骨架,Mockito 隔离依赖,Robolectric 模拟 Android 运行时 在 AOSP 测试中,它们经常被组合使用

JUnit —— 测试框架的基石

JUnit 是 Java 生态里最基础的单元测试框架。做 AOSP 测试,你几乎每个测试类都会用到它。

我个人习惯用 JUnit 4 的注解风格,因为 AOSP 很多老模块还是 JUnit 4。当然 JUnit 5 也好,但这里咱们先以 JUnit 4 为例。

核心注解速查表

注解作用执行时机
@Test标记一个测试方法每次运行
@Before每个测试前执行,初始化对象每个 @Test 前
@After每个测试后执行,清理资源每个 @Test 后
@BeforeClass类中所有测试前执行一次(静态方法)类加载时
@AfterClass类中所有测试后执行一次(静态方法)类销毁时
@Ignore跳过某个测试不执行

举个例子:

public class CalculatorTest {
    private Calculator calc;

    @Before
    public void setUp() {
        calc = new Calculator();
    }

    @Test
    public void add_shouldReturnSum() {
        int result = calc.add(2, 3);
        assertEquals(5, result);
    }

    @After
    public void tearDown() {
        calc = null;
    }
}

这里 assertEquals 就是 JUnit 提供的断言。断言失败时,测试会红掉。

💡 我的一点经验

JUnit 的 @Before 和 @After 非常适合做资源准备和释放。比如 Android 的 Context 你可以在这里 mock 好再注入。千万别写在 @Test 里重复造轮子。

Mockito —— 模拟外部依赖

测试里最难搞的就是外部依赖:网络请求、数据库、系统服务…… 你不想真的去启动一个 ContentProvider 吧?Mockito 就是解决这个的。

Mockito 能帮我们:

  • 创建 Mock 对象:用 mock(Class) 生成一个假对象。
  • Stubbing(打桩):指定调用某个方法时返回什么值。
  • 验证(Verify):检查某个方法是否被调用了,调了几次。

来看一段代码:

// 创建 mock
Context mockContext = mock(Context.class);
// 打桩:当调用 getString(R.string.app_name) 时返回 "MyApp"
when(mockContext.getString(R.string.app_name)).thenReturn("MyApp");
// 测试代码...
String name = mockContext.getString(R.string.app_name);
assertEquals("MyApp", name);
// 验证:确保 getString 被调用了一次
verify(mockContext, times(1)).getString(R.string.app_name);

这里要注意:如果没打桩的方法被调用了,Mockito 会返回默认值(如 0、false、null)。

⚠️ 曾经踩过的坑

我曾经在一个测试里 mock 了 ContentResolver,但忘了打桩 query() 方法。结果跑测试时一直返回 null,排查了两小时才发现。建议对所有 mock 对象的每个可能被调用的方法都明确打桩,哪怕返回 null 也要写清楚。

Mockito 还有一个常用的功能:@InjectMocks 和 @Mock 注解。配合 MockitoJUnitRunner 或者 MockitoAnnotations.initMocks(this) 使用时,可以自动注入 mock 对象到被测类里。

@RunWith(MockitoJUnitRunner.class)
public class UserServiceTest {
    @Mock
    private UserDao userDao;
    @InjectMocks
    private UserService userService;

    @Test
    public void getUser_shouldReturnUser() {
        User expected = new User("1", "Alice");
        when(userDao.findById("1")).thenReturn(expected);
        User actual = userService.getUser("1");
        assertEquals(expected, actual);
    }
}

Robolectric —— 在 JVM 上跑 Android 测试

Android 单元测试最让人头疼的是:很多代码依赖 Android 框架类(比如 Activity、Context、Resource)。如果你用 JUnit 直接测,就会抛出 RuntimeException:Stub! —— 因为 Android 的 SDK 在 JVM 上是存根。

Robolectric 就是来拯救我们的。它在 JVM 上实现了 Android 框架的「影子(Shadow)」版本,让你的测试能跑起来,而不需要启动模拟器。

典型用法:

@RunWith(RobolectricTestRunner.class)
public class MainActivityTest {
    @Test
    public void activity_shouldCreate() {
        ActivityController<MainActivity> controller = Robolectric.buildActivity(MainActivity.class);
        controller.create().start().resume();
        MainActivity activity = controller.get();
        assertNotNull(activity);
    }
}

Robolectric 还给每个 Android 类配了一个 Shadow 类。比如 ShadowToast 可以检查 Toast 是否被弹出:

@Test
public void buttonClick_shouldShowToast() {
    MainActivity activity = Robolectric.setupActivity(MainActivity.class);
    activity.findViewById(R.id.btn).performClick();
    Toast latestToast = ShadowToast.getLatestToast();
    assertNotNull(latestToast);
    assertEquals("Hello", ShadowToast.getTextOfLatestToast());
}

🎯 使用建议

Robolectric 虽然强大,但它不是模拟器,有些特性无法模拟(比如真正的传感器、硬件编解码)。我建议用它测 UI 逻辑、资源加载、生命周期,涉及硬件或特殊系统服务时,还是走 Instrumentation 测试。

三剑客的协作模式

实际写 AOSP 测试时,这三者通常一起上。比如你要测试一个 ViewModel,它依赖 SharedPreferences(Android 类)和一个网络数据源:

  1. 用 Robolectric 提供 Android 环境(让 SharedPreferences 能创建)。
  2. 用 Mockito 把网络数据源 mock 掉,避免真实网络请求。
  3. 用 JUnit 的 @Test 和断言来定义测试逻辑。
@RunWith(RobolectricTestRunner.class)
public class MyViewModelTest {
    @Mock
    private DataSource dataSource;
    private MyViewModel viewModel;

    @Before
    public void setUp() {
        MockitoAnnotations.initMocks(this);
        viewModel = new MyViewModel(RuntimeEnvironment.application, dataSource);
    }

    @Test
    public void loadData_shouldPopulateLiveData() {
        List<Item> mockData = Arrays.asList(new Item("a"), new Item("b"));
        when(dataSource.fetchItems()).thenReturn(Observable.just(mockData));
        viewModel.loadData();
        assertEquals(2, viewModel.items.getValue().size());
    }
}

✅ 总结一下

JUnit 是骨架,Mockito 是隔离针,Robolectric 是环境模拟器。掌握这三样,AOSP 单元测试的基本功就到位了。后面几章咱们会深入到更复杂的场景:PowerMock、参数化测试、还有 AOSP 特有的测试基类。但先把这三个工具练熟,你会发现写测试越来越顺。