source 는 Github 에 있습니다.
@Slf4j
@Configuration
public class SimpleTaskletJob {
@Autowired
public JobBuilderFactory jobBuilderFactory;
@Autowired
public StepBuilderFactory stepBuilderFactory;
private SimpleTaskletBean simpleTaskletBean;
public SimpleTaskletJob(SimpleTaskletBean simpleTaskletBean) {
this.simpleTaskletBean = simpleTaskletBean;
}
@Bean
public Job simpleTaskletJob01() {
return jobBuilderFactory.get("simpleTaskletJob01")
.incrementer(new RunIdIncrementer())
.flow(simpleTaskletStep01())
.end()
.build();
}
@Bean
public Step simpleTaskletStep01() {
return stepBuilderFactory.get("simpleTaskletStep01")
.tasklet(simpleTaskletBean)
.build();
}
}
/**
* 통합 테스트 환경 구성 (Tasklet)
*/
@RunWith(SpringRunner.class)
@SpringBatchTest // a
@SpringBootTest(classes={SimpleTaskletJob.class, SimpleTaskletBean.class, TestConfig.class}) // b
public class SimpleTaskletJobTest {
@Autowired
private JobLauncherTestUtils jobLauncherTestUtils;
@Test
public void simpleTaskletJobTest() throws Exception {
JobParameters jobParameters = new JobParametersBuilder()
.addString("test", "testData123")
.toJobParameters();
JobExecution jobExecution = jobLauncherTestUtils.launchJob(jobParameters);
Assert.assertThat(jobExecution.getStatus(), is(BatchStatus.COMPLETED));
Assert.assertThat(jobExecution.getExitStatus(), is(ExitStatus.COMPLETED));
}
}
a. Spring Batch 에서 test 관련 bean 을 자동으로 등록해줍니다.
Registers a JobLauncherTestUtils bean with the BatchTestContextCustomizer.JOB_LAUNCHER_TEST_UTILS_BEAN_NAME which can be used in tests for launching jobs and steps.
Registers a JobRepositoryTestUtils bean with the BatchTestContextCustomizer.JOB_REPOSITORY_TEST_UTILS_BEAN_NAME which can be used in tests setup to create or remove job executions.
Registers the StepScopeTestExecutionListener and JobScopeTestExecutionListener as test execution listeners which are required to test step/job scoped beans.
b. @SpringBootTest 를 등록해서 필요한 자동설정 등을 자동으로 등록해주면 테스트 할 때 편합니다. (권장)
public class JpaConfiguration {
private final EntityManagerFactoryBuilder entityManagerFactoryBuilder;
@Primary
@Bean(name = "entityManagerFactory")
public LocalContainerEntityManagerFactoryBean entityManagerFactory (DataSource dataSource) {
return entityManagerFactoryBuilder
.dataSource(dataSource)
.packages(packages)
.properties(properties)
.persistenceUnit("testUnit")
.build();
}
}
@Override
public final void execute(StepExecution stepExecution) throws JobInterruptedException,
UnexpectedJobExecutionException {
Assert.notNull(stepExecution, "stepExecution must not be null");
if (logger.isDebugEnabled()) {
logger.debug("Executing: id=" + stepExecution.getId());
}
stepExecution.setStartTime(new Date());
stepExecution.setStatus(BatchStatus.STARTED);
Timer.Sample sample = BatchMetrics.createTimerSample();
getJobRepository().update(stepExecution);
// Start with a default value that will be trumped by anything
ExitStatus exitStatus = ExitStatus.EXECUTING;
doExecutionRegistration(stepExecution);
...
...
...
try {
getJobRepository().updateExecutionContext(stepExecution);
} catch (Exception e) {
stepExecution.setStatus(BatchStatus.UNKNOWN);
exitStatus = exitStatus.and(ExitStatus.UNKNOWN);
stepExecution.addFailureException(e);
logger.error(String.format("Encountered an error saving batch meta data for step %s in job %s. "
+ "This job is now in an unknown state and should not be restarted.", name, stepExecution.getJobExecution().getJobInstance().getJobName()), e);
}
@Override
public RepeatStatus doInChunkContext(RepeatContext repeatContext, ChunkContext chunkContext)
throws Exception {
StepExecution stepExecution = chunkContext.getStepContext().getStepExecution();
// Before starting a new transaction, check for
// interruption.
interruptionPolicy.checkInterrupted(stepExecution);
RepeatStatus result;
try {
result = new TransactionTemplate(transactionManager, transactionAttribute)
.execute(new ChunkTransactionCallback(chunkContext, semaphore));
}
catch (UncheckedTransactionException e) {
// Allow checked exceptions to be thrown inside callback
throw (Exception) e.getCause();
}
chunkListener.afterChunk(chunkContext);
// Check for interruption after transaction as well, so that
// the interrupted exception is correctly propagated up to
// caller
interruptionPolicy.checkInterrupted(stepExecution);
return result == null ? RepeatStatus.FINISHED : result;
}