1. 소개
다른 Spring 기반 애플리케이션과 달리 배치 작업 테스트에는 작업이 실행되는 방식의 비동기 특성으로 인해 몇 가지 특정 문제가 있습니다.
이 예제에서는 Spring Batch 작업 을 테스트하기위한 다양한 대안을 탐색 할 것입니다.
2. 필수 의존성
우리가 사용하고있는 스프링 부팅 스타터 배치를 그래서 먼저 우리의에서 필요한 종속까지의 설정을하자, pom.xml 파일 :
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-batch</artifactId>
<version>2.4.0</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<version>2.4.0</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.batch</groupId>
<artifactId>spring-batch-test</artifactId>
<version>4.3.0.RELEASE</version>
<scope>test</scope>
</dependency>
우리는 포함 Spring - 부의 t-선발 테스트 와 스프링 배치 테스트 스프링 배치 응용 프로그램을 테스트하기 위해 몇 가지 필요한 도우미 메서드, 청취자와 주자에 가져.
3. 스프링 배치 작업 정의
Spring Batch가 테스트 문제를 어떻게 해결하는지 보여주는 간단한 애플리케이션을 만들어 보겠습니다.
우리 애플리케이션은 구조화 된 도서 정보가 포함 된 CSV 입력 파일을 읽고 도서 및 도서 세부 정보를 출력 하는 2 단계 작업 을 사용합니다 .
3.1. 작업 단계 정의
두 개의 후속 단계 는 BookRecord 에서 특정 정보를 추출한 다음이를 Book s (단계 1) 및 BookDetails ( 단계 2)에 매핑합니다 .
@Bean
public Step step1(
ItemReader<BookRecord> csvItemReader, ItemWriter<Book> jsonItemWriter) throws IOException {
return stepBuilderFactory
.get("step1")
.<BookRecord, Book> chunk(3)
.reader(csvItemReader)
.processor(bookItemProcessor())
.writer(jsonItemWriter)
.build();
}
@Bean
public Step step2(
ItemReader<BookRecord> csvItemReader, ItemWriter<BookDetails> listItemWriter) {
return stepBuilderFactory
.get("step2")
.<BookRecord, BookDetails> chunk(3)
.reader(csvItemReader)
.processor(bookDetailsItemProcessor())
.writer(listItemWriter)
.build();
}
3.2. 입력 판독기 및 출력 기록기 정의
이제 FlatFileItemReader 를 사용하여 CSV 파일 입력 판독기를 구성하여 구조화 된 책 정보를 BookRecord 객체 로 역 직렬화 해 보겠습니다 .
private static final String[] TOKENS = {
"bookname", "bookauthor", "bookformat", "isbn", "publishyear" };
@Bean
@StepScope
public FlatFileItemReader<BookRecord> csvItemReader(
@Value("#{jobParameters['file.input']}") String input) {
FlatFileItemReaderBuilder<BookRecord> builder = new FlatFileItemReaderBuilder<>();
FieldSetMapper<BookRecord> bookRecordFieldSetMapper = new BookRecordFieldSetMapper();
return builder
.name("bookRecordItemReader")
.resource(new FileSystemResource(input))
.delimited()
.names(TOKENS)
.fieldSetMapper(bookRecordFieldSetMapper)
.build();
}
이 정의에는 몇 가지 중요한 사항이 있으며 이는 우리가 테스트하는 방식에 영향을 미칩니다.
우선, 우리는 어노테이션 FlatItemReader의 와 Bean @StepScope을 , 그리고 그 결과로, 이 객체와 수명 공유 StepExecution을 .
이를 통해 런타임에 동적 값을 주입하여 4 행 의 JobParameter 에서 입력 파일을 전달할 수 있습니다 . 반대로 BookRecordFieldSetMapper에 사용되는 토큰 은 컴파일 타임에 구성됩니다.
그런 다음 유사하게 JsonFileItemWriter 출력 작성기를 정의합니다 .
@Bean
@StepScope
public JsonFileItemWriter<Book> jsonItemWriter(
@Value("#{jobParameters['file.output']}") String output) throws IOException {
JsonFileItemWriterBuilder<Book> builder = new JsonFileItemWriterBuilder<>();
JacksonJsonObjectMarshaller<Book> marshaller = new JacksonJsonObjectMarshaller<>();
return builder
.name("bookItemWriter")
.jsonObjectMarshaller(marshaller)
.resource(new FileSystemResource(output))
.build();
}
두 번째 단계 에서는 메모리 내 List에 물건을 덤프 하는 Spring Batch 제공 ListItemWriter 를 사용합니다.
3.3. 사용자 지정 JobLauncher 정의
다음으로 application.properties 에서 spring.batch.job.enabled = false 를 설정하여 Spring Boot Batch 의 기본 Job 시작 구성을 비활성화하겠습니다 .
Job을 시작할 때 커스텀 JobParameters 인스턴스 를 전달 하도록 자체 JobLauncher 를 구성 합니다 .
@SpringBootApplication
public class SpringBatchApplication implements CommandLineRunner {
// autowired jobLauncher and transformBooksRecordsJob
@Value("${file.input}")
private String input;
@Value("${file.output}")
private String output;
@Override
public void run(String... args) throws Exception {
JobParametersBuilder paramsBuilder = new JobParametersBuilder();
paramsBuilder.addString("file.input", input);
paramsBuilder.addString("file.output", output);
jobLauncher.run(transformBooksRecordsJob, paramsBuilder.toJobParameters());
}
// other methods (main etc.)
}
4. 스프링 배치 작업 테스트
스프링 배치 테스트 의존성은 테스트 동안 스프링 배치 컨텍스트를 구성하는 데 사용할 수있는 유용한 도우미 방법과 청취자의 집합을 제공합니다.
테스트를위한 기본 구조를 만들어 보겠습니다.
@RunWith(SpringRunner.class)
@SpringBatchTest
@EnableAutoConfiguration
@ContextConfiguration(classes = { SpringBatchConfiguration.class })
@TestExecutionListeners({ DependencyInjectionTestExecutionListener.class,
DirtiesContextTestExecutionListener.class})
@DirtiesContext(classMode = ClassMode.AFTER_CLASS)
public class SpringBatchIntegrationTest {
// other test constants
@Autowired
private JobLauncherTestUtils jobLauncherTestUtils;
@Autowired
private JobRepositoryTestUtils jobRepositoryTestUtils;
@After
public void cleanUp() {
jobRepositoryTestUtils.removeJobExecutions();
}
private JobParameters defaultJobParameters() {
JobParametersBuilder paramsBuilder = new JobParametersBuilder();
paramsBuilder.addString("file.input", TEST_INPUT);
paramsBuilder.addString("file.output", TEST_OUTPUT);
return paramsBuilder.toJobParameters();
}
@SpringBatchTest 어노테이션은 제공 JobLauncherTestUtils 및 JobRepositoryTestUtils 헬퍼 클래스를. 테스트에서 Job 및 Step 을 트리거하는 데 사용합니다 .
우리의 애플리케이션은 기본 메모리 내 JobRepository 를 활성화하는 Spring Boot 자동 구성 을 사용합니다 . 결과적으로 동일한 클래스에서 여러 테스트를 실행하려면 각 테스트 실행 후 정리 단계가 필요합니다 .
마지막으로 여러 테스트 클래스에서 여러 테스트를 실행하려면 컨텍스트를 dirty 로 표시해야합니다 . 이는 동일한 데이터 소스를 사용하는 여러 JobRepository 인스턴스 의 충돌을 방지하기 위해 필요합니다 .
4.1. 엔드-투-엔드 작업 테스트
가장 먼저 테스트 할 것은 작은 데이터 세트 입력 이있는 완전한 엔드 투 엔드 작업 입니다.
그런 다음 결과를 예상되는 테스트 출력과 비교할 수 있습니다.
@Test
public void givenReferenceOutput_whenJobExecuted_thenSuccess() throws Exception {
// given
FileSystemResource expectedResult = new FileSystemResource(EXPECTED_OUTPUT);
FileSystemResource actualResult = new FileSystemResource(TEST_OUTPUT);
// when
JobExecution jobExecution = jobLauncherTestUtils.launchJob(defaultJobParameters());
JobInstance actualJobInstance = jobExecution.getJobInstance();
ExitStatus actualJobExitStatus = jobExecution.getExitStatus();
// then
assertThat(actualJobInstance.getJobName(), is("transformBooksRecords"));
assertThat(actualJobExitStatus.getExitCode(), is("COMPLETED"));
AssertFile.assertFileEquals(expectedResult, actualResult);
}
Spring Batch Test는 AssertFile 클래스를 사용하여 출력을 확인 하는 데 유용한 파일 비교 방법을 제공합니다 .
4.2. 개별 단계 테스트
전체 작업을 종단 간 테스트하는 데 비용이 많이 들기 때문에 대신 개별 단계 를 테스트하는 것이 좋습니다.
@Test
public void givenReferenceOutput_whenStep1Executed_thenSuccess() throws Exception {
// given
FileSystemResource expectedResult = new FileSystemResource(EXPECTED_OUTPUT);
FileSystemResource actualResult = new FileSystemResource(TEST_OUTPUT);
// when
JobExecution jobExecution = jobLauncherTestUtils.launchStep(
"step1", defaultJobParameters());
Collection actualStepExecutions = jobExecution.getStepExecutions();
ExitStatus actualJobExitStatus = jobExecution.getExitStatus();
// then
assertThat(actualStepExecutions.size(), is(1));
assertThat(actualJobExitStatus.getExitCode(), is("COMPLETED"));
AssertFile.assertFileEquals(expectedResult, actualResult);
}
@Test
public void whenStep2Executed_thenSuccess() {
// when
JobExecution jobExecution = jobLauncherTestUtils.launchStep(
"step2", defaultJobParameters());
Collection actualStepExecutions = jobExecution.getStepExecutions();
ExitStatus actualExitStatus = jobExecution.getExitStatus();
// then
assertThat(actualStepExecutions.size(), is(1));
assertThat(actualExitStatus.getExitCode(), is("COMPLETED"));
actualStepExecutions.forEach(stepExecution -> {
assertThat(stepExecution.getWriteCount(), is(8));
});
}
통지 우리가 사용 launchStep의 트리거 특정 단계에 방법을 .
기억 우리는 우리의 설계 ItemReader 와 ItemWriter를 런타임에 동적 값을 사용 하는 방법, 우리는 우리의 I / O 파라미터를 전달할 수 JobExecution (라인 9 및 23).
첫 번째 단계 테스트에서는 실제 출력과 예상 출력을 비교합니다.
반면에 두 번째 테스트에서는 예상되는 항목에 대한 StepExecution 을 확인합니다 .
4.3. 단계 범위 구성 요소 테스트
이제 FlatFileItemReader를 테스트 해 보겠습니다 . @StepScope 빈 으로 노출 했으므로 Spring Batch의 전용 지원을 사용하고 싶습니다 .
// previously autowired itemReader
@Test
public void givenMockedStep_whenReaderCalled_thenSuccess() throws Exception {
// given
StepExecution stepExecution = MetaDataInstanceFactory
.createStepExecution(defaultJobParameters());
// when
StepScopeTestUtils.doInStepScope(stepExecution, () -> {
BookRecord bookRecord;
itemReader.open(stepExecution.getExecutionContext());
while ((bookRecord = itemReader.read()) != null) {
// then
assertThat(bookRecord.getBookName(), is("Foundation"));
assertThat(bookRecord.getBookAuthor(), is("Asimov I."));
assertThat(bookRecord.getBookISBN(), is("ISBN 12839"));
assertThat(bookRecord.getBookFormat(), is("hardcover"));
assertThat(bookRecord.getPublishingYear(), is("2018"));
}
itemReader.close();
return null;
});
}
MetadataInstanceFactory는 사용자 정의 생성 StepExecution 우리 스텝 범위 주입 할 필요가 ItemReader를.
이 때문에 doInTestScope 메서드 를 사용하여 판독기의 동작을 확인할 수 있습니다 .
다음으로 JsonFileItemWriter를 테스트 하고 출력을 확인 하겠습니다 .
@Test
public void givenMockedStep_whenWriterCalled_thenSuccess() throws Exception {
// given
FileSystemResource expectedResult = new FileSystemResource(EXPECTED_OUTPUT_ONE);
FileSystemResource actualResult = new FileSystemResource(TEST_OUTPUT);
Book demoBook = new Book();
demoBook.setAuthor("Grisham J.");
demoBook.setName("The Firm");
StepExecution stepExecution = MetaDataInstanceFactory
.createStepExecution(defaultJobParameters());
// when
StepScopeTestUtils.doInStepScope(stepExecution, () -> {
jsonItemWriter.open(stepExecution.getExecutionContext());
jsonItemWriter.write(Arrays.asList(demoBook));
jsonItemWriter.close();
return null;
});
// then
AssertFile.assertFileEquals(expectedResult, actualResult);
}
이전 테스트와 달리 이제 테스트 개체를 완전히 제어 할 수 있습니다 . 결과적으로 우리는 I / O 스트림을 열고 닫을 책임이 있습니다 .
5. 결론
이 예제에서는 Spring Batch 작업을 테스트하는 다양한 접근 방식을 살펴 보았습니다.
종단 간 테스트는 작업의 전체 실행을 확인합니다. 개별 단계를 테스트하면 복잡한 시나리오에서 도움이 될 수 있습니다.
마지막으로 단계 범위 구성 요소의 경우 spring-batch-test에서 제공하는 여러 도우미 메서드를 사용할 수 있습니다 . 그들은 Spring Batch 도메인 객체를 스터 빙하고 조롱하는 데 도움을 줄 것입니다.