SpringBatch : SpringBatch Test 하기

    이 글은 인프런 정수원님의 강의를 듣고 복습하며 작성한 글입니다. 

    SpringBatch Test → @SpringBatchTest

    SpringBatch Test를 할 때는 @SpringBatchTest 어노테이션을 이용하면 편리하다. @SpringBatchTest를 사용하면 Batch Test에서 사용할 수 있는 유용한 스프링 빈을 등록해준다.

    • JobLauncherTestUtils
      • launcherJob() / launcherStep() 같은 스프링 배치 테스트 필요 유틸성 메서드 지원
    • JobRepositoryTestUtils
      • JobRepository를 사용해서 jobExecution을 생성 및 삭제 기능 메서드 지원
    • StepScopeTestExecutionListener
      • @StepScope 컨텍스트를 생성해줌. 해당 컨텍스트를 통해 JobParameter 등을 단위 테스트에서 DI 가능
    • JobScopeTestExecutionListener
      • @JobScope 컨텍스트를 생성해줌. 해당 컨텍스트를 통해 JobParameter 등을 단위 테스트에서 DI 가능함. 

     

    JobLauncherTestUtils

    JobLauncherTestUtils 클래스는 내부적으로 Job 객체를 가진다. 그런데 이 객체는 단일 객체이다. 바꿔 말하면, 한 테스트 클래스에서 테스트 해볼 수 있는 Job은 1개 밖에 없다는 것을 의미한다.

    또한 JobLauncherTestUtils는 @SpringBatchTest 어노테이션이 있을 경우 자동으로 스프링 빈이 등록되는데, 이 때 @SpringBootTest에 import된 Job Bean을 자동으로 JobLauncherTestUtils 객체에 주입시켜준다. 따라서 테스트를 할 때는 한 개의 Job만 @SpringBootTest에 추가해주어야 한다는 의미다. 왜냐하면 여러 개의 Job이 있을 경우, 어떤 Job이 들어올지 모르기 때문이다.

     

    JobExecution launcherJob(JobParameters jobParamters)
    JobExecution launcherStep(String stepName)

    위의 두 메서드도 지원한다. Job만 실행시킬 수도 있고, Step만 실행시킬 수도 있다. 각각은 실행하고 난 후에 JobExecution을 반환해준다. 테스트 코드에서는 반환받은 JobExecution을 바탕으로 실제로 Job이 잘 진행되었는지 executionContext의 값을 활용해서 검증할 수 있다. 

     

    @EnableBatchProcessing / @EnableAutoConfiguration 필요

    SpringBatchTest를 할 때는 위의 어노테이션을 가지는 테스트 설정 정보 클래스를 생성해야한다. 그리고 실제 테스트 클래스에서 이 설정 클래스를 Import해야한다. 이유는 간단하다. 테스트는 스프링 메인 메서드를 실행하는 것이 아니기 때문이다. 

    기본적으로 Spring 메인함수에 @EnableBatchProcessing 어노테이션이 붙어있고, 이 어노테이션이 등록된 것을 인식하기 때문에 BatchProcessing이 가능하도록 이런저런 편의성 빈이 제공된다. 그렇지만 테스트 코드를 실행할 때는 메인 메서드를 실행하는 것이 아니다. 따라서 @EnableBatchProcessing을 다시 한번 설정할 필요가 있다. 

    @Configuration
    @EnableBatchProcessing
    @EnableAutoConfiguration
    public class TestBatchConfig {
    }
    
    
    @SpringBatchTest // 어노테이션 선언 시, 특정 빈 4개를 등록해줌.
    @SpringBootTest(classes = {SpringBatchTestConfig.class, TestBatchConfig.class})
    class SpringBatchTestConfigTest {
    ...
    }

    따라서 위와 같이 어노테이션만 가지고 있는 BatchTest용 설정 정보 클래스를 만들어준다. 그리고 그 클래스를 테스트 클래스에 Import를 해줘야한다.

     

    스프링 Batch Test 코드 작성

    스프링 Batch Test 코드를 직접 작성해보고, 테스트 환경에서 잘 돌아가는지를 확인해봤다. 처음에 ItemReader를 JpaCursorItemReader를 사용했는데, 스프링 메인 함수에서는 정상적으로 동작하는 것이 확인되었으나, 테스트 코드에서는 초기화에 실패했다는 메세지가 계속 떴다. 그런 이유 때문에 JdbcCursorItemReader로 변경해서 사용했다.

    이상하게, JdbcCursorItemReader로 읽어오면 PK 값이 안 읽히는 현상이 있다. 그런데 지금은 PK값이 안 읽히는 것이 중요한 게 아니라, 채번 하는 방식으로 id값을 처리해서 넣어주었다. 

     

    Job / Step 구성 

    @Configuration
    @RequiredArgsConstructor
    public class SpringBatchTestConfig {
    
    
        private final JobBuilderFactory jobBuilderFactory;
        private final StepBuilderFactory stepBuilderFactory;
        private final DataSource dataSource;
        private static Long myId = 0L;
    
    
    
        @Bean
        public Job springBatchTestJob() {
            return jobBuilderFactory.get("springBatchTestJob")
                    .incrementer(new RunIdIncrementer())
                    .start(springBatchTestStep())
                    .build();
        }
    
        @Bean
        public Step springBatchTestStep() {
            return stepBuilderFactory.get("springBatchTestStep")
                    .<Customer, Customer2>chunk(100)
                    .reader(springBatchTestReader2())
                    .processor(new ItemProcessor<Customer, Customer2>() {
                        @Override
                        public Customer2 process(Customer item) throws Exception {
                            System.out.println("item = " + item);
                            return Customer2.builder()
                                    .id(myId ++)
                                    .firstName(item.getFirstName())
                                    .lastName(item.getLastName())
                                    .birthDate(item.getBirthDate())
                                    .build();
                        }
                    })
                    .writer(springBatchTestWriter())
                    .build();
        }
    
        @Bean
        public ItemWriter<? super Customer2> springBatchTestWriter() {
            return new JdbcBatchItemWriterBuilder<Customer2>()
                    .dataSource(dataSource)
                    .beanMapped()
                    .sql("insert into customer2(customer2_id, birth_date, first_name, last_name) values (:id, :birthDate, :firstName, :lastName)")
                    .build();
        }
    
        @Bean
        public ItemReader<Customer> springBatchTestReader2() {
            return new JdbcCursorItemReaderBuilder<Customer>()
                    .name("springBatchTestReader2")
                    .maxRows(10000)
                    .currentItemCount(0)
                    .sql("SELECT customer_id, first_name, last_name, birth_date FROM Customer")
                    .dataSource(dataSource)
                    .fetchSize(10000)
                    .beanRowMapper(Customer.class)
                    .build();
    
    
        }
    
    }

     

    테스트 설정 클래스 → 테스트 메인 클래스에 Import 필수.

    @Configuration
    @EnableBatchProcessing
    @EnableAutoConfiguration
    public class TestBatchConfig {
    }
    

     

    테스트 클래스

    @SpringBatchTest // 어노테이션 선언 시, 특정 빈 4개를 등록해줌.
    @SpringBootTest(classes = {SpringBatchTestConfig.class, TestBatchConfig.class})
    class SpringBatchTestConfigTest {
    
    
        @Autowired
        private JobLauncherTestUtils jobLauncherTestUtils;
    
        @Autowired
        private JdbcTemplate jdbcTemplate;
    
        @Test
        public void simpleJob_test() throws Exception {
    
            //given
            JobParameters jobParameters = new JobParametersBuilder()
                    .addString("requestDate", "20210101")
                    .addLong("date", new Date().getTime())
                    .toJobParameters();
    
            //when
            JobExecution jobExecution = jobLauncherTestUtils.launchJob(jobParameters);
            JobExecution jobExecution1 = jobLauncherTestUtils.launchStep("springBatchTestStep");
    
    
            //then
            Assertions.assertThat(jobExecution.getStatus()).isEqualTo(BatchStatus.COMPLETED);
            Assertions.assertThat(jobExecution.getExitStatus()).isEqualTo(ExitStatus.COMPLETED);
            Collection<StepExecution> stepExecutions = jobExecution1.getStepExecutions();
            Object o = ((java.util.List) stepExecutions).get(0);
            StepExecution stepExecution = (StepExecution) o;
    
            Assertions.assertThat(stepExecution.getReadCount()).isGreaterThan(10);
            Assertions.assertThat(stepExecution.getWriteCount()).isGreaterThan(10);
            Assertions.assertThat(stepExecution.getCommitCount()).isGreaterThan(10);
    
        }
    
        @AfterEach
        public void clear() {
            jdbcTemplate.execute("delete from customer2");
        }
    }
    • 테스트를 하기 위해 JobParamter를 만들었다. 그리고 jobLauncher를 통해 실행시켜주었다. 이 때, JobParameter만 전달하는데 JobLauncherTestUtils 클래스는 이미 DI 받은 Job을 가지고 있기 때문에 Job을 넘겨주지 않는다. 
    • jobLuancherTestUtils를 실행시키면 실행된 Job의 JobExecution을 받는다. 이걸 바탕으로 Step이 정상실행되었는지를 확인할 수 있다. 
    • Step에서 받은 JobExecution을 바탕으로 StepExecution을 받을 수 있고, StepExecution의 read, write, commit count 정보를 확인할 수 있다. 

    댓글

    Designed by JB FACTORY