3. 单元测试基础:JUnit框架、TestRunner配置、Gradle依赖管理
说实话,写单元测试这件事,我刚开始做Android开发时也觉得很烦。总觉得“代码能跑就行,写啥测试”?直到有一次我改了一个工具类的内部实现,结果把十几个页面里的权限校验逻辑全部带偏了——上线后用户疯狂反馈“App闪退”。嗯,那次之后我就老实了。
这一章我们来搞定单元测试的基础设施。你想想看,不管是做权限Mock还是普通业务逻辑回归,如果连测试框架都跑不起来,后面的一切都是空谈。
3.1 JUnit框架:我到底该用4还是5?
这个问题我被问过太多次了。我的建议很直接——新项目直接上JUnit 5。JUnit 5的架构更清晰,支持Lambda表达式,扩展机制也更灵活。但如果你维护的是老项目,别急着升级,JUnit 4完全够用。
核心对比
| 维度 | JUnit 4 | JUnit 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的输出。
核心要点总结
- JUnit 5 比JUnit 4更适合新项目,特别是参数化测试和扩展机制
- TestRunner 必须与测试类型匹配:本地测试用MockitoRunner,仪器化测试用AndroidJUnitRunner
- Gradle依赖 用Version Catalog统一管理,注意mockito-android的正确位置
- 写完配置先跑一个最简单的Mock测试做验证,不要直接写复杂用例
我个人习惯把这些基础设施写在项目的README.md里,方便团队其他成员快速上手。你想想看,如果每个新人都能5分钟内跑起第一个测试,那整个团队的测试文化就很容易建立起来。下一阶段我们会在这些基础上,正式引入权限Mock的核心技术。不过那是后面的事了,这一章你先把这些基础夯扎实。