Spring Boot Kotlin项目中并行执行单元测试、串行执行SpringBootTests与DataJpaTests的技术咨询
我之前也遇到过一模一样的场景——普通单元测试想并行执行来提速,但@SpringBootTest和@DataJpaTest这类依赖Spring上下文的测试,一并行就会因为上下文初始化冲突、资源竞争等问题失败。你之前尝试的全局并行配置和单个类加@Execution(SAME_THREAD)之所以没用,是因为后者只能控制类内部方法串行,无法阻止多个上下文测试类之间的并行启动。
下面给你两个实操性强的解决方案,核心思路是通过构建工具把测试分成两类,分别控制执行模式:
方案1:用JUnit 5标签(Tags)区分测试类型
这种方式更灵活,适合测试类命名不固定的场景:
步骤1:给测试类打标签
给所有依赖上下文的测试类加上自定义标签,普通单元测试也可以打标签(或者只标记上下文测试):
// SpringBoot测试类 @SpringBootTest @Tag("context-dependent") class SpringBootTest1 { // 测试方法... } // DataJpa测试类 @DataJpaTest @Tag("context-dependent") class DataJpaTest1 { // 测试方法... } // 普通单元测试类 @Tag("unit") class UnitTest1 { // 测试方法... }
步骤2:在Gradle中配置分任务执行
如果你的项目用Gradle Kotlin DSL,修改build.gradle.kts,创建两个独立的测试任务:
// 禁用默认的test任务,我们用自定义任务替代 tasks.test { enabled = false } // 任务1:串行执行所有依赖上下文的测试 val contextDependentTest = tasks.register<Test>("contextDependentTest") { useJUnitPlatform { includeTags("context-dependent") } maxParallelForks = 1 // 强制串行,避免上下文冲突 } // 任务2:并行执行普通单元测试 val unitTest = tasks.register<Test>("unitTest") { useJUnitPlatform { excludeTags("context-dependent") // 如果只跑标记了unit的测试,就换成includeTags("unit") } maxParallelForks = Runtime.getRuntime().availableProcessors() // 用CPU核心数并行 // 配置JUnit 5并行参数 systemProperty("junit.jupiter.execution.parallel.enabled", "true") systemProperty("junit.jupiter.execution.parallel.mode.default", "concurrent") systemProperty("junit.jupiter.execution.parallel.mode.classes.default", "concurrent") } // 让默认的check任务依赖这两个自定义任务,执行顺序是先串行再并行 tasks.check { dependsOn(contextDependentTest, unitTest) }
执行测试
直接运行./gradlew check,就会先串行跑所有context-dependent的测试,再并行跑普通单元测试。
方案2:通过测试类注解/命名直接过滤
如果你的测试类命名有规律(比如带SpringBootTest或DataJpaTest后缀),或者不想打标签,可以直接通过注解类型过滤:
Gradle配置示例
tasks.test { enabled = false } // 串行执行SpringBootTest和DataJpaTest val contextTests = tasks.register<Test>("contextTests") { useJUnitPlatform { filter { includeAnnotation(org.springframework.boot.test.context.SpringBootTest::class.java) includeAnnotation(org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest::class.java) } } maxParallelForks = 1 } // 并行执行其他所有测试 val unitTests = tasks.register<Test>("unitTests") { useJUnitPlatform { filter { excludeAnnotation(org.springframework.boot.test.context.SpringBootTest::class.java) excludeAnnotation(org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest::class.java) } } maxParallelForks = Runtime.getRuntime().availableProcessors() systemProperty("junit.jupiter.execution.parallel.enabled", "true") systemProperty("junit.jupiter.execution.parallel.mode.default", "concurrent") systemProperty("junit.jupiter.execution.parallel.mode.classes.default", "concurrent") } tasks.check { dependsOn(contextTests, unitTests) }
为什么这个方案有效?
之前的全局并行配置会让所有测试类同时启动,包括SpringBootTest类——即使你给类加了@Execution(SAME_THREAD),也只能保证这个类内部的方法串行,但多个SpringBootTest类还是会同时初始化各自的上下文,导致数据库连接池、Bean实例等资源冲突。
通过构建工具把测试分成两个独立任务,先串行执行所有上下文依赖测试(让Spring上下文可以复用,减少初始化时间),再并行执行无上下文的单元测试,完美解决了冲突问题。
内容的提问来源于stack exchange,提问作者sigal notovich

