java - 由于线程执行,Spring Batch 的集成测试失败
问题描述
我有一个 Spring 批处理项目。目标是对 Spring Job 中存在的各个步骤执行集成测试。
我正在使用 JobLauncherTestUtils 来启动该步骤。但是,当此实用程序启动这些步骤时,它会在单独的线程中运行它。一旦线程完成执行,应该有一些值分配给jobExecution.getStepExecutions()
.
问题:由于某种原因,即使在线程完成执行之前,测试也会进入下一行 List<StepExecution> actualStepExecutions = new ArrayList<>(jobExecution.getStepExecutions())
,jobExecution.getStepExecutions()
当前为空,因此测试失败,NullPointerException
并Index 0 out of bounds for length 0
在下面的测试类中标记了 - 。
问题:是否有任何优雅的方法来等待 Step 执行 THREAD 完成,然后继续测试中的下一行进行验证?
代码:
集成测试类:
@Slf4j
@SpringBatchTest
@SpringBootTest
@ActiveProfiles({"test", "master"})
@ContextConfiguration(classes = {InhouseClass3.class, InhouseClass1.class, InhouseClass2.class})
public class BatchJobIntegrationTest {
private static final String Param1 = "someParam";
@Autowired
@Qualifier("hikariDatasource")
DataSource hikariDatasource;
@Autowired
Job BatchJob;
@Autowired
JobLauncher jobLauncher;
JobExecution jobExecution;
@Autowired
CreateDirectoryTasklet createDirectoryTasklet;
JobParameters jobParameters;
JobLauncherTestUtils jobLauncherTestUtils;
@BeforeEach
void setUp() {
String startTimestamp = Timestamp.from(Instant.now()).toString();
jobParameters = new JobParametersBuilder()
.addString(Param1, startTimestamp)
.toJobParameters();
jobLauncherTestUtils = new JobLauncherTestUtils();
jobLauncherTestUtils.setJob(BatchJob);
jobLauncherTestUtils.setJobLauncher(jobLauncher);
}
@SneakyThrows
@Test
void TaskletToTest_Test() {
jobExecution = jobLauncherTestUtils.launchStep("loadTaskletToTest", jobParameters);
List<StepExecution> actualStepExecutions = new ArrayList<>(jobExecution.getStepExecutions());
// ERROR: jobExecution.getStepExecutions() is NULL.
ExitStatus actualJobExitStatus = actualStepExecutions.get(0).getExitStatus();
// ERROR: Index 0 out of bounds for length 0
assertEquals("loadGdxClaims", actualStepExecutions.get(0).getStepName());
assertEquals(ExitStatus.COMPLETED, actualJobExitStatus);
}
@SneakyThrows
@Test
// This is my workaround to make my above test run.
// I added a sleep for 2 seconds. But this doesn't look like an ideal way, coz what
// if the launchstep thread running the tasklet took more than 2 seconds?
void loadGDXClaimTaskletTest_Working_() {
jobExecution = jobLauncherTestUtils.launchStep("createDirectory", jobParameters);
boolean counter = true;
while(counter) {
if (jobExecution.getStepExecutions().size()!=0 ) {
List<StepExecution> actualStepExecutions = new ArrayList<>(jobExecution.getStepExecutions());
ExitStatus actualJobExitStatus = actualStepExecutions.get(0).getExitStatus();
log.info("------- step executions : {}", actualStepExecutions);
assertEquals("createDirectory", actualStepExecutions.get(0).getStepName());
assertEquals(ExitStatus.COMPLETED, actualJobExitStatus);
counter = false;
} else {
TimeUnit.SECONDS.sleep(2);
}
}
}
}
Tasklet 测试步骤:
@Slf4j
@Component
public class TaskletToTest implements Tasklet {
private final InhouseService inhouseService;
public TaskletToTest(InhouseService inhouseService) {
this.inhouseService = inhouseService;
}
@Override
public RepeatStatus execute(StepContribution contribution, ChunkContext chunkContext) throws InterruptedException, IllegalJobNameException, JSchException, IOException {
log.info("TaskletToTest before");
inhouseService.retry();
log.info("TaskletToTest after");
return RepeatStatus.FINISHED;
}
}
包含需要测试的步骤列表的批处理作业:
@Slf4j
@Profile("master")
@Configuration
public class MasterConfig extends DefaultBatchConfigurer {
@Bean(name = "BatchJob")
public Job remoteChunkingJob(TaskletStep someOtherTasklet,
TaskletStep loadTaskletToTest,
JobExecutionListener jobExecutionListener) {
return this.jobBuilderFactory.get("extract gdx load")
.incrementer(new RunIdIncrementer())
.listener(jobExecutionListener)
.start(someOtherTasklet)
.next(loadTaskletToTest)
.build();
}
@Bean
TaskletStep loadTaskletToTest(TaskletToTest taskletToTest) {
return this.stepBuilderFactory.get("loadTaskletToTest").tasklet(taskletToTest).build();
}
}
解决方案
我正在使用 JobLauncherTestUtils 来启动该步骤。但是,当此实用程序启动这些步骤时,它会在单独的线程中运行它。
问题:由于某种原因,甚至在线程完成执行之前,测试进入下一行
JobLauncherTestUtils
使用 aJobLauncher
来启动作业和步骤。因此,根据JobLauncher
您使用的实现,您可以在当前线程或单独的线程中运行作业/步骤。
您没有分享JobLauncher
测试中自动装配的内容,但您似乎已经定义了一个基于异步TaskExecutor
实现的作业启动器。这就是您的工作/步骤在后台执行的原因:
// This returns immediately with an asynchrnous TaskExecutor
// However, with a synchronous TaskExectuor it will block waiting for the step to finish
jobExecution = jobLauncherTestUtils.launchStep("loadTaskletToTest", jobParameters);
List<StepExecution> actualStepExecutions = new ArrayList<>(jobExecution.getStepExecutions());
因此,您需要检查在您的测试中哪个JobLauncher
(通常是SimpleJobLauncher
带有 synchronous 或 asynchronous 的TaskExecutor
)是自动装配的。
推荐阅读
- python - 标准库日志记录加 loguru
- autodesk-forge - 如何将自定义标记导出回 AutoCAD
- kotlin - Kotlin 数据类中的循环引用可能会产生堆栈溢出错误
- ruby-on-rails - 创建对象后用父类扩展类
- graphql - 如何根据 GraphQL (NestJS) 中的条件发送不同的订阅响应?
- android - 如何从 Main Activity 到 Fragment,并从同一个 Fragment 返回到 Main Activity?
- firebase - (Flutter/Dart)从 Firebase 存储中检索,同时从 Firebase Firestore 中检索,异步中的异步
- logging - 使用 log4rs 将每个主机变量附加到 Rust 中的所有日志消息
- javascript - 如何在 Javascript 中循环访问来自 API 的数据?
- java - 有没有办法使用 JsonPath 动态地部分复制 Json?