1. 引言
自 JUnit 4 起,我们可以在大型测试套件中并行运行测试以提高速度。但在 Spring 5 之前,Spring TestContext Framework 对并发测试执行的支持并不完整。
在这篇简短的文章中,我们将展示如何使用 Spring 5 在 Spring 项目中并发运行测试。
2. Maven 配置
为了在并行模式下运行 JUnit 测试,我们需要配置 maven-surefire-plugin 来启用这个功能:
<build>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>2.22.2</version>
<configuration>
<parallel>methods</parallel>
<useUnlimitedThreads>true</useUnlimitedThreads>
</configuration>
</plugin>
</build>
关于并行测试执行的更详细配置,请参考官方文档。
3. 并发测试
以下示例测试在 Spring 5 之前的版本中并行运行时会失败。但在 Spring 5 中,它将顺利进行:
@RunWith(SpringRunner.class)
@ContextConfiguration(classes = Spring5JUnit4ConcurrentTest.SimpleConfiguration.class)
public class Spring5JUnit4ConcurrentTest implements ApplicationContextAware, InitializingBean {
@Configuration
public static class SimpleConfiguration {}
private ApplicationContext applicationContext;
private boolean beanInitialized = false;
@Override
public void afterPropertiesSet() throws Exception {
this.beanInitialized = true;
}
@Override
public void setApplicationContext(
final ApplicationContext applicationContext) throws BeansException {
this.applicationContext = applicationContext;
}
@Test
public void whenTestStarted_thenContextSet() throws Exception {
TimeUnit.SECONDS.sleep(2);
assertNotNull(
"The application context should have been set due to ApplicationContextAware semantics.",
this.applicationContext);
}
@Test
public void whenTestStarted_thenBeanInitialized() throws Exception {
TimeUnit.SECONDS.sleep(2);
assertTrue(
"This test bean should have been initialized due to InitializingBean semantics.",
this.beanInitialized);
}
}
顺序执行时,上述测试大约需要6秒通过。在并发执行下,只需约4.5秒——对于大型测试套件,这是我们可以期望节省的时间典型水平。
4. 深入理解
TestContext Manager 是以前框架版本不支持并发测试的主要原因,因为它负责管理 TestContext。
在 Spring 5 中,TestContext Manager 使用线程局部变量— TestContext —来确保每个线程对 TestContexts 的操作不会相互干扰,从而保证了大多数方法级和类级并发测试的线程安全:
public class TestContextManager {
// ...
private final TestContext testContext;
private final ThreadLocal<TestContext> testContextHolder = new ThreadLocal<TestContext>() {
protected TestContext initialValue() {
return copyTestContext(TestContextManager.this.testContext);
}
};
public final TestContext getTestContext() {
return this.testContextHolder.get();
}
// ...
}
需要注意的是,并发支持并不适用于所有类型的测试。我们需要排除那些:
- 修改外部共享状态的测试,如缓存、数据库、消息队列等的状态。
- 需要特定执行顺序的测试,例如使用 JUnit 的@FixMethodOrder。
- 修改 ApplicationContext 的测试,通常标记为@DirtiesContext。
5. 总结
在这篇简短教程中,我们展示了使用 Spring 5 进行并行测试的基本示例。
如往常一样,示例代码可以在GitHub上找到。