如何让JUnit 5暂停其他线程,单独执行指定测试类?
我原本以为给测试类添加@Execution(ExecutionMode.SAME_THREAD)注解就能让它单独执行,但实际没生效——其他测试类还是会和它并行运行。我需要实现的是:让所有其他测试都等待AaInitialStateITCase执行完成后再启动,比如现在设置forkCount = 4时,实际执行顺序是混着的:
- 运行pl.mol.molnet.initsaas.Dummy2ITCase
- 运行pl.mol.molnet.initsaas.Dummy1ITCase
- 运行pl.mol.molnet.initsaas.AaInitialStateITCase
- 运行pl.mol.molnet.initsaas.Dummy3ITCase
- Dummy1ITCase测试完成,耗时27.68秒
- 运行pl.mol.molnet.initsaas.Dummy4ITCase
- Dummy4ITCase测试完成,耗时0.008秒
本质上我希望初始化类这类特殊测试能先单独执行,其他测试再并行启动。
示例测试类
/** * 初始化主数据库对象。 * @author Maciej Nux Jaros */ @ExtendWith(SpringExtension.class) @ContextConfiguration({"/testContextPg.xml"}) @TestInstance(TestInstance.Lifecycle.PER_CLASS) @Execution(ExecutionMode.SAME_THREAD) // 不能并行执行 public class AaInitialStateITCase extends TestDatabaseCleaner { @BeforeAll public void mockTenantService() throws SaasException { initAll(); } @Test public void testInit_shouldInitTenants() throws Exception { assertNotNull(tenant1); assertNotNull(tenant2); } } /** * 用于占用线程的测试类。 * @author Maciej Nux Jaros */ @ExtendWith(SpringExtension.class) @ContextConfiguration({"/testContextPg.xml"}) @TestInstance(TestInstance.Lifecycle.PER_CLASS) public class Dummy1ITCase extends TestDatabaseCleaner { @Test public void test_forthreads() throws Exception { assertEquals(2, 1+1); } }
Maven配置
<!-- ITCase 配置 --> <plugin> <artifactId>maven-failsafe-plugin</artifactId> <version>${maven.surefire-failsafe.plugin.version}</version> <executions> <execution> <goals> <goal>integration-test</goal> <goal>verify</goal> </goals> </execution> </executions> <configuration> <skipTests>${skip.integration.tests}</skipTests> <forkCount>4</forkCount> <reuseForks>true</reuseForks> <argLine>-Xms512m -Xmx1280m -Dfile.encoding=UTF-8</argLine> <properties> <configurationParameters> junit.jupiter.execution.parallel.enabled = true junit.jupiter.execution.parallel.mode.default = concurrent junit.jupiter.execution.parallel.mode.classes.default = same_thread # 按类名排序以安全初始化数据库 junit.jupiter.testclass.order.default = org.junit.jupiter.api.ClassOrderer$ClassName </configurationParameters> </properties> </configuration> </plugin>
解决方案
问题根源
你当前配置中的forkCount=4意味着Maven会启动4个独立的JVM进程并行执行测试。而JUnit的@Execution注解仅能控制单个JVM进程内的测试线程,跨进程的测试无法通过该注解同步,所以初始化类和其他测试会在不同进程中同时运行。
可行方案
1. 分阶段执行测试
通过Maven Failsafe插件的多执行阶段,先单独运行初始化测试类,再执行其他测试:
<plugin> <artifactId>maven-failsafe-plugin</artifactId> <version>${maven.surefire-failsafe.plugin.version}</version> <executions> <!-- 第一阶段:单进程执行初始化测试 --> <execution> <id>run-init-tests</id> <goals> <goal>integration-test</goal> </goals> <configuration> <forkCount>1</forkCount> <includes> <include>**/AaInitialStateITCase.java</include> </includes> </configuration> </execution> <!-- 第二阶段:多进程执行其他测试 --> <execution> <id>run-other-tests</id> <goals> <goal>integration-test</goal> <goal>verify</goal> </goals> <configuration> <forkCount>4</forkCount> <excludes> <exclude>**/AaInitialStateITCase.java</exclude> </excludes> </configuration> </execution> </executions> <configuration> <skipTests>${skip.integration.tests}</skipTests> <reuseForks>true</reuseForks> <argLine>-Xms512m -Xmx1280m -Dfile.encoding=UTF-8</argLine> <properties> <configurationParameters> junit.jupiter.execution.parallel.enabled = true junit.jupiter.execution.parallel.mode.default = concurrent junit.jupiter.execution.parallel.mode.classes.default = same_thread junit.jupiter.testclass.order.default = org.junit.jupiter.api.ClassOrderer$ClassName </configurationParameters> </properties> </configuration> </plugin>
该配置会先以单进程完成AaInitialStateITCase的执行,再启动4个进程并行执行其余测试。
2. 改用JUnit线程级并行
将forkCount设为1,完全依赖JUnit的线程级并行控制执行顺序,此时@Execution和类排序配置会生效:
<forkCount>1</forkCount>
JUnit会在单个进程内按类名排序(你已配置ClassOrderer$ClassName),优先执行AaInitialStateITCase(类名以Aa开头,排序最靠前),且因为加了@Execution(ExecutionMode.SAME_THREAD),它会在单线程执行,其他测试类会在它完成后再按配置的并行模式启动。
3. 使用测试标签分组
给初始化类添加自定义标签,分阶段执行标签组:
在测试类上添加标签:
@Tag("init") @Execution(ExecutionMode.SAME_THREAD) public class AaInitialStateITCase extends TestDatabaseCleaner { // ... }
修改Maven配置的执行阶段:
<executions> <execution> <id>run-init-tests</id> <goals> <goal>integration-test</goal> </goals> <configuration> <forkCount>1</forkCount> <groups>init</groups> </configuration> </execution> <execution> <id>run-other-tests</id> <goals> <goal>integration-test</goal> <goal>verify</goal> </goals> <configuration> <forkCount>4</forkCount> <excludedGroups>init</excludedGroups> </configuration> </execution> </executions>
内容的提问来源于stack exchange,提问作者Nux

