如何配置Spring+JUnit并行运行集成测试(Maven+双数据库场景)
没问题!要实现Spring+JUnit的并行测试(搭配多数据库避免数据污染),用Maven的话,咱们可以分几个关键步骤来配置,我给你一步步讲清楚:
首先得让JUnit本身支持并行运行,你需要在src/test/resources下创建junit-platform.properties文件,配置这些参数:
# 启用并行执行 junit.jupiter.execution.parallel.enabled = true # 方法级默认并发执行 junit.jupiter.execution.parallel.mode.default = concurrent # 类级也支持并发(如果你的测试类之间没有依赖的话) junit.jupiter.execution.parallel.mode.classes.default = concurrent # 设置并行策略为固定线程数,这里设2对应你要的两个数据库 junit.jupiter.execution.parallel.config.strategy = fixed junit.jupiter.execution.parallel.config.fixed.parallelism = 2
这些配置会让JUnit同时启动2个线程来执行测试方法/类,完全由JUnit自己管理并行逻辑。
并行测试最大的坑就是数据污染——多个测试线程共用同一个数据库的话,测试数据会互相干扰,所以必须给每个线程分配独立的数据库。这里分两种常用场景:
场景1:用嵌入式数据库(比如H2)快速实现
如果你的测试用的是嵌入式数据库,最简单的方式是给每个线程生成独立的数据库实例:
@Configuration @TestConfiguration public class TestDataSourceConfig { @Bean public DataSource dataSource() { // 用线程ID作为数据库名称的一部分,确保每个线程的库独立 String uniqueDbName = "test_db_" + Thread.currentThread().getId(); return new EmbeddedDatabaseBuilder() .setType(EmbeddedDatabaseType.H2) .setName(uniqueDbName) .addScript("classpath:schema.sql") // 初始化表结构 .build(); } }
这样每个测试线程都会拥有自己的H2数据库,完全隔离,不会有数据冲突。
场景2:用真实数据库(比如MySQL)的多Schema
如果必须用真实数据库,你可以提前创建多个测试Schema(比如test_1、test_2),然后用Spring的AbstractRoutingDataSource动态给线程分配Schema:
首先实现路由数据源:
public class ThreadRoutingDataSource extends AbstractRoutingDataSource { @Override protected Object determineCurrentLookupKey() { // 线程ID取模2,把线程分配到两个Schema中的一个 int schemaIndex = (int) (Thread.currentThread().getId() % 2 + 1); return "test_" + schemaIndex; } }
然后配置测试用的数据源:
@Configuration @TestConfiguration public class TestMultiDataSourceConfig { // 配置第一个测试Schema的数据源 @Bean public DataSource testDataSource1() { return DataSourceBuilder.create() .url("jdbc:mysql://localhost:3306/test_1") .username("root") .password("your_password") .driverClassName("com.mysql.cj.jdbc.Driver") .build(); } // 配置第二个测试Schema的数据源 @Bean public DataSource testDataSource2() { return DataSourceBuilder.create() .url("jdbc:mysql://localhost:3306/test_2") .username("root") .password("your_password") .driverClassName("com.mysql.cj.jdbc.Driver") .build(); } // 注册路由数据源,让Spring用它来分发连接 @Bean public DataSource routingDataSource() { ThreadRoutingDataSource routingDs = new ThreadRoutingDataSource(); Map<Object, Object> targetDataSources = new HashMap<>(); targetDataSources.put("test_1", testDataSource1()); targetDataSources.put("test_2", testDataSource2()); routingDs.setTargetDataSources(targetDataSources); routingDs.setDefaultTargetDataSource(testDataSource1()); return routingDs; } // 后续的JdbcTemplate、EntityManagerFactory等都要绑定这个routingDataSource }
Maven的Surefire插件负责运行测试,需要确保它不会干扰JUnit的并行配置。在pom.xml里修改Surefire的配置:
<build> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-surefire-plugin</artifactId> <version>3.1.2</version> <configuration> <!-- 让JUnit自己管理并行,不要让Surefire强制串行 --> <parallel>none</parallel> <!-- 如果你想让Surefire控制线程数,也可以这么配,但推荐用JUnit的统一配置 --> <!-- <parallel>methods</parallel> <threadCount>2</threadCount> --> <includes> <include>**/*Test.java</include> </includes> </configuration> </plugin> </plugins> </build>
这里设置<parallel>none</parallel>是告诉Surefire不要插手并行逻辑,完全交给JUnit处理,这样更灵活。
- 避免使用静态变量存储测试数据,静态变量是线程共享的,会导致数据污染;
- 尽量用
@BeforeEach和@AfterEach来初始化/清理每个测试方法的独立数据; - 如果用
@Transactional,注意事务的隔离级别——不过因为每个线程用独立数据库,事务冲突的问题基本可以忽略; - 测试类之间不要有依赖关系,确保每个测试都能独立运行。
可以在测试方法里打印线程ID,确认是否真的在并行执行:
@Test void testSample() { System.out.println("测试运行在线程:" + Thread.currentThread().getId()); // 你的测试逻辑 }
运行mvn test后,看控制台输出的线程ID,如果出现多个不同的ID,说明并行生效了;同时检查两个数据库/Schema里的数据,应该是各自独立的。
内容的提问来源于stack exchange,提问作者Combo

