第2章 JUnit4与JUnit5在Android中的配置
2.1 原理概述
JUnit4与JUnit5在Android测试中的核心差异在于依赖模型、扩展机制和运行时兼容性。
JUnit4依赖单一的 junit:junit 库,通过 AndroidJUnitRunner 运行测试;JUnit5则采用分离式架构(JUnit Platform + Jupiter + Vintage),允许同时运行JUnit3/4/5测试。
在Android项目中配置两种版本时,需注意Instrumentation测试与本地单元测试的类加载顺序差异,以及JUnit5对Android Gradle Plugin (AGP) 的最低版本要求。
关键指标:
- JUnit4:依赖
junit:junit:4.13.2+androidx.test.ext:junit:1.1.5 - JUnit5:依赖
org.junit.jupiter:junit-jupiter-engine:5.10.2+org.junit.vintage:junit-vintage-engine:5.10.2 - 运行器:JUnit4使用
AndroidJUnitRunner,JUnit5需GradleRunner或AndroidJUnitRunner+ 适配库 - 规则(Rule)与扩展(Extension)的兼容性:JUnit4 @Rule 在JUnit5中需改为 @RegisterExtension 或 Extension API
2.2 依赖引入与Gradle配置
2.2.1 JUnit4 基础依赖
// app/build.gradle (Module)
android {
defaultConfig {
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}
}
dependencies {
// JUnit4
testImplementation 'junit:junit:4.13.2'
androidTestImplementation 'androidx.test.ext:junit:1.1.5'
// Android测试工具(可选)
androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.1'
androidTestImplementation 'androidx.test:rules:1.5.0'
}
2.2.2 JUnit5 依赖配置(使用 android-junit5 插件)
// project/build.gradle (Project)
buildscript {
dependencies {
classpath 'de.mannodermaus.gradle.plugins:android-junit5:1.10.0.0'
}
}
// app/build.gradle (Module)
apply plugin: 'de.mannodermaus.android-junit5'
android {
defaultConfig {
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}
}
dependencies {
// JUnit5 Platform + Jupiter
testImplementation 'org.junit.jupiter:junit-jupiter-api:5.10.2'
testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.10.2'
androidTestImplementation 'org.junit.jupiter:junit-jupiter-api:5.10.2'
androidTestRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.10.2'
// Vintage引擎(运行旧版JUnit3/4)
testImplementation 'org.junit.vintage:junit-vintage-engine:5.10.2'
}
提示:若项目仍使用JUnit4
@RunWith 注解或 @Rule,务必添加 vintage-engine,否则这些测试会被忽略。
2.3 测试运行器对比
| 特性 | JUnit4 | JUnit5 |
|---|---|---|
| 运行器类 | AndroidJUnitRunner | AndroidJUnitRunner (通过适配层) |
| 注解引擎 | @RunWith(AndroidJUnit4.class) | 无需 @RunWith,默认识别 @Test |
| 多引擎支持 | 仅JUnit4 | JUnit3/4 + JUnit5 (Vintage) |
| Instrumentation兼容 | 原生支持 | 需 android-junit5 插件桥接 |
| Gradle任务 | ./gradlew testDebugUnitTest | ./gradlew testDebugUnitTest (同) |
2.4 参数化测试实现
2.4.1 JUnit4 参数化(@RunWith(Parameterized.class))
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
import org.junit.runners.Parameterized.Parameters;
import java.util.Arrays;
import java.util.Collection;
@RunWith(Parameterized.class)
public class CalculatorParamTest {
private int input1;
private int input2;
private int expected;
public CalculatorParamTest(int input1, int input2, int expected) {
this.input1 = input1;
this.input2 = input2;
this.expected = expected;
}
@Parameters
public static Collection<Object[]> data() {
return Arrays.asList(new Object[][]{
{2, 3, 5},
{0, 0, 0},
{-1, -1, -2}
});
}
@Test
public void testAdd() {
assertEquals(expected, Math.add(input1, input2));
}
}
2.4.2 JUnit5 参数化(@ParameterizedTest + @ValueSource)
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.CsvSource;
import static org.junit.jupiter.api.Assertions.assertEquals;
class CalculatorParamTestJ5 {
@ParameterizedTest
@CsvSource({
"2, 3, 5",
"0, 0, 0",
"-1, -1, -2"
})
void testAdd(int input1, int input2, int expected) {
assertEquals(expected, Math.add(input1, input2));
}
}
差异总结:
- JUnit4需要
@RunWith(Parameterized.class)+ 构造函数 + 静态@Parameters方法,样板代码较多。 - JUnit5直接使用
@ParameterizedTest+ 多种参数源注解(@ValueSource, @CsvSource, @MethodSource等),代码更简洁,且支持并行测试。 - JUnit5参数化测试默认每个参数实例独立运行,错误隔离性更好。
2.5 规则(Rule)与扩展(Extension)使用
2.5.1 JUnit4 典型规则
import androidx.test.ext.junit.rules.ActivityScenarioRule;
import org.junit.Rule;
import org.junit.Test;
public class MainActivityTest {
@Rule
public ActivityScenarioRule<MainActivity> activityRule =
new ActivityScenarioRule<>(MainActivity.class);
@Test
public void testActivityCreated() {
activityRule.getScenario().onActivity(activity -> {
// 测试Activity生命周期
});
}
}
2.5.2 JUnit5 扩展(替代规则)
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.RegisterExtension;
import androidx.test.ext.junit.rules.ActivityScenarioExtension;
public class MainActivityJ5Test {
@RegisterExtension
static ActivityScenarioExtension<MainActivity> activityExtension =
new ActivityScenarioExtension<>(MainActivity.class);
@Test
void testActivityCreated() {
activityExtension.getScenario().onActivity(activity -> {
// 相同功能
});
}
}
常见错误:在JUnit5测试类中直接使用
@Rule 注解会抛出 UnsupportedOperationException。必须改用 @RegisterExtension 或实现 Extension 接口。若需要同时使用JUnit4规则,请通过Vintage引擎并保持JUnit4风格。
2.6 调试与常见错误
| 问题现象 | 原因 | 解决方案 |
|---|---|---|
测试运行时提示 NoClassDefFoundError: org/junit/platform/engine/... |
缺少JUnit5 Platform核心库或运行时依赖 | 确保 testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine' 和 androidTestRuntimeOnly 均已配置 |
@RunWith(Parameterized.class) 不被识别 |
Vintage引擎未添加 | 在JUnit5项目中加入 junit-vintage-engine 依赖 |
ActivityScenarioRule 在JUnit5下无法实例化 |
JUnit5没有 @Rule 处理能力 | 改为 @RegisterExtension 静态字段或使用 ActivityScenarioExtension |
2.7 性能与稳定性优化
- 依赖冲突:若项目同时引入JUnit4和JUnit5,务必通过
configurations.all { exclude module: 'junit' }排除旧版以防方法重复。 - 测试执行速度:JUnit5支持动态测试(@TestFactory)和条件测试(@EnabledIf),可减少无效执行,提升CI流水线效率。
- 规则线程安全:使用
@RegisterExtension时建议将字段设为static以避免每个测试实例重复创建Activity,节省开销。 - 混淆配置:开启ProGuard/R8时需保留JUnit反射相关类,否则参数化测试的构造函数参数绑定会失败。