第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
JUnit4 vs JUnit5 Android 配置流程 JUnit4 依赖引入 AndroidJUnitRunner 参数化 @RunWith 模拟规则 ActivityScenarioRule JUnit5 Platform依赖 Gradle测试适配 (android-junit5) 参数化 @ParameterizedTest Extension 替代 ActivityScenarioRule 同项目内并行 官方推荐 JUnit5 (新项目)

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 测试运行器对比

特性JUnit4JUnit5
运行器类AndroidJUnitRunnerAndroidJUnitRunner (通过适配层)
注解引擎@RunWith(AndroidJUnit4.class)无需 @RunWith,默认识别 @Test
多引擎支持仅JUnit4JUnit3/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反射相关类,否则参数化测试的构造函数参数绑定会失败。

2.8 本章小结