3. 单元测试基础:JUnit框架、TestRunner配置、Gradle依赖管理

说实话,写单元测试这件事,我刚开始做Android开发时也觉得很烦。总觉得“代码能跑就行,写啥测试”?直到有一次我改了一个工具类的内部实现,结果把十几个页面里的权限校验逻辑全部带偏了——上线后用户疯狂反馈“App闪退”。嗯,那次之后我就老实了。

这一章我们来搞定单元测试的基础设施。你想想看,不管是做权限Mock还是普通业务逻辑回归,如果连测试框架都跑不起来,后面的一切都是空谈。

单元测试基础三层架构 JUnit 4/5 框架 TestRunner 配置 AndroidJUnitRunner Mockito / PowerMock Gradle 依赖声明 testImplementation 配置 androidTestImplementation 💡 核心流程:JUnit编写用例 → TestRunner执行 → Gradle管理依赖版本 三者缺一不可,否则测试无法稳定运行或集成到CI流水线

3.1 JUnit框架:我到底该用4还是5?

这个问题我被问过太多次了。我的建议很直接——新项目直接上JUnit 5。JUnit 5的架构更清晰,支持Lambda表达式,扩展机制也更灵活。但如果你维护的是老项目,别急着升级,JUnit 4完全够用。

核心对比

维度JUnit 4JUnit 5 (Jupiter)
注解@Test / @Before / @After@Test / @BeforeEach / @AfterEach
断言Assert.assertEquals()Assertions.assertEquals() (静态导入更爽)
参数化测试@RunWith(Parameterized.class)@ParameterizedTest + @ValueSource
扩展机制@Rule + @ClassRule@ExtendWith (ResgisterExtension)

我个人习惯用JUnit 5的@DisplayName来给测试用例起中文描述。你想想看,当CI报告里出现「测试用户权限校验失败时返回false」而不是「testPermissionCheckFailed」,读起来是不是舒服多了?

3.2 TestRunner配置:别让Runner成为你的绊脚石

我记得刚带团队时,有个同事怎么都跑不起本地单元测试。折腾了两天,最后发现是build.gradle里忘了配testInstrumentationRunner。嗯,这种坑我踩过,所以这里给你总结清楚。

在Android项目中,TestRunner分两种场景:

  • 纯单元测试(本地JVM):默认使用 JUnitRunner,不需要额外配置。但如果你要Mock Android框架类,必须用 MockitoJUnitRunner 或 PowerMockRunner。
  • 仪器化测试(真机/模拟器):必须配置 AndroidJUnitRunner。

提示:我个人习惯在 build.gradle 的 defaultConfig 里显式声明Runner,这样别人接手项目时一眼就能看懂。

android {
    defaultConfig {
        testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
    }
}

还有一个坑:如果你用JUnit 5的@ExtendWith,记得先确保Runner是JUnit 5的,或者用@RunWith(JUnitPlatform.class)做过渡。我之前在迁移一个老模块时,就因为Runner没同步,导致所有@ExtendWith注解都没生效。

3.3 Gradle依赖管理:版本冲突的噩梦怎么破?

做Android测试,依赖管理是个技术活。特别是当你的项目依赖了十几个第三方库时,版本冲突会让你想砸键盘。我经历过一次:因为mockito-core和mockito-android版本不一致,导致when().thenReturn()一直报NullPointerException,排查了整整一下午。

给你一个我验证过的黄金依赖组合:

// 项目级 build.gradle (根目录)
buildscript {
    ext {
        junit5_version = '5.9.3'
        mockito_version = '5.4.0'
        androidx_test_version = '1.5.0'
    }
}

// 模块级 build.gradle
dependencies {
    // JUnit 5 核心
    testImplementation "org.junit.jupiter:junit-jupiter-api:$junit5_version"
    testRuntimeOnly "org.junit.jupiter:junit-jupiter-engine:$junit5_version"

    // Mockito — 注意:Android项目要用 mockito-android
    testImplementation "org.mockito:mockito-core:$mockito_version"
    testImplementation "org.mockito:mockito-android:$mockito_version"

    // AndroidX Test(仪器化测试需要)
    androidTestImplementation "androidx.test:runner:$androidx_test_version"
    androidTestImplementation "androidx.test:rules:$androidx_test_version"
}

警告:千万不要把 mockito-android 误放到 androidTestImplementation 里!它其实是为纯单元测试提供Android类Mock支持的。放错位置会导致仪器化测试时出现ClassNotFoundException。

关于版本管理,我建议统一使用Gradle Version Catalog(即libs.versions.toml)。说白了就是把所有依赖版本号集中到一个文件里,避免到处散落硬编码。这样做的好处是:

  • 升级时只需改一个地方,不会遗漏
  • 不同模块之间依赖版本自动对齐
  • 新人接手项目时,打开gradle/libs.versions.toml就能看到全部依赖清单

3.4 快速验证:你的测试环境搭好了吗?

配置完成后,写一个最简单的测试用例来验证环境是否正常:

import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.*;
import static org.mockito.Mockito.*;

class PermissionManagerTest {

    @Test
    @DisplayName("权限校验 - 已授权应返回true")
    void testPermissionGranted() {
        // 这里先Mock一个最简单的场景
        PermissionManager mockManager = mock(PermissionManager.class);
        when(mockManager.checkPermission(anyString())).thenReturn(true);

        boolean result = mockManager.checkPermission("CAMERA");
        assertTrue(result);
    }

    @Test
    @DisplayName("权限校验 - 未授权应返回false")
    void testPermissionDenied() {
        PermissionManager mockManager = mock(PermissionManager.class);
        when(mockManager.checkPermission(anyString())).thenReturn(false);

        boolean result = mockManager.checkPermission("LOCATION");
        assertFalse(result);
    }
}

如果你在Android Studio里右键这个测试类,能看到绿色的勾,那就说明环境配置成功了。如果报错,别慌——八成是依赖版本冲突,去检查一下gradle dependencies的输出。

核心要点总结

  1. JUnit 5 比JUnit 4更适合新项目,特别是参数化测试和扩展机制
  2. TestRunner 必须与测试类型匹配:本地测试用MockitoRunner,仪器化测试用AndroidJUnitRunner
  3. Gradle依赖 用Version Catalog统一管理,注意mockito-android的正确位置
  4. 写完配置先跑一个最简单的Mock测试做验证,不要直接写复杂用例

我个人习惯把这些基础设施写在项目的README.md里,方便团队其他成员快速上手。你想想看,如果每个新人都能5分钟内跑起第一个测试,那整个团队的测试文化就很容易建立起来。下一阶段我们会在这些基础上,正式引入权限Mock的核心技术。不过那是后面的事了,这一章你先把这些基础夯扎实。