반응형

환경: springboot3.1.5, spring batch5, junit5

 

어찌어찌 배치 프로그램은 짰는데, 테스트코드는 어떻게 짜야할지 막막했다.

심지어 이 배치는 디비에서 오늘에 해당하는 데이터를 읽어 다른 디비에 적재하는 배치인데 

  1. "오늘"이라는 날짜 디펜덴시가 있는 데이터가 필요하고
  2. 이걸 타 디비에 실제로 넣어야 한다.

h2를 추가하여 로컬 배치로 돌리는 방법이 있겠지만 돌리는 날짜에 기반한 샘플 데이터를 만들어 넣는 게 좀 귀찮았고

디비 작업이야, 쿼리만 정확하면 보증되는 것이라(이미 다른 곳에서 돌고 있는 쿼리라서 실행이 보장되어 있음)

내가 검증하고 싶은 건 데이터를 정확히 꺼내오는 것이 아닌 job, step 등이 순차적으로 잘 도는지에 대해 작성하고 싶었다.

 

하여 db select, insert 부분을 mocking 할 수 있으면 좋겠다는 생각을 했다.

 

step1. get job launcher 

bean으로 등록하거나

(아래 코드 테스트 안 해봄)

import org.springframework.batch.core.configuration.annotation.EnableBatchProcessing;
import org.springframework.batch.test.JobLauncherTestUtils;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class TestBatchConfig {

    @Bean
    public JobLauncherTestUtils jobLauncherTestUtils() {
        return new JobLauncherTestUtils();
    }
}
@SpringBootTest
@SpringBatchTest // mandatory?
@Import({TestBatchConfig.class, YourJobConfig.class})  // Replace YourJobConfig with your actual job configuration class
public class YourJobTest {

    @Autowired
    private JobLauncherTestUtils jobLauncherTestUtils;

util로 만들어 빈으로 등록

public class JobTestUtils {

      @Autowired private ApplicationContext applicationContext;
      @Autowired private JobRepository jobRepository;
      @Autowired private JobExplorer jobExplorer;
      @Autowired private JobLauncher jobLauncher;

      public JobLauncherTestUtils getJobTester(String jobName) {
        Job bean = applicationContext.getBean(jobName, Job.class);
        JobLauncherTestUtils jobLauncherTestUtils = new JobLauncherTestUtils();
        jobLauncherTestUtils.setJobLauncher(jobLauncher);
        jobLauncherTestUtils.setJobRepository(jobRepository);
        jobLauncherTestUtils.setJob(bean);
        return jobLauncherTestUtils;
      }

      public JobParameters makeJobParameters(JobParameters parameters) {
        return new JobParametersBuilder(jobExplorer).addJobParameters(parameters).toJobParameters();
      }
      ...
  }
@TestConfiguration
public class TestBatchConfig {

  @Bean
  public JobTestUtils jobTestUtils() {
    return new JobTestUtils();
  }
}
@ActiveProfiles("test")
@Import({TestBatchConfig.class})
@SpringBootTest
class DailyRankingJobConfigTest {

  @Autowired private JobTestUtils jobTestUtils;

...
}

 

step2. mocking 하고자 하는 reader/writer가 빈으로 등록되어야 한다.

실제 job class에서 아래와 같이 item reader/writer가 주입되도록 하고..

@Configuration
@RequiredArgsConstructor
public class DailyRankingJobConfig {

  private final DailyRankingJobParameter jobParameter;

  @Qualifier("dailyRankingMatchCntReader")
  private final MyBatisCursorItemReader<Ranking> dailyRankingMatchCntReader;

  @Qualifier("dailyRankingGameMoneyReader")
  private final MyBatisCursorItemReader<Ranking> dailyRankingGameMoneyReader;

  @Qualifier("dailyRankingWriter")
  private final ItemWriter<Ranking> dailyRankingWriter;

테스트 코드에도 빈을 주입하는데.. @MockBean어노테이션을 이용한다. 여기서 주의할 건 name에 꼭 빈 이름을 넣어야 한다.. 안 그럼 못 찾는 듯.. 에러가 발생한다.

@ActiveProfiles("test")
@Import({TestBatchConfig.class})
@SpringBootTest
class DailyRankingJobConfigTest {

  @Autowired private JobTestUtils jobTestUtils;

  @MockBean(name = "dailyRankingMatchCntReader")
  private MyBatisCursorItemReader<Ranking> dailyRankingMatchCntReader;

  @MockBean(name = "dailyRankingGameMoneyReader")
  private MyBatisCursorItemReader<Ranking> dailyRankingGameMoneyReader;

  @MockBean(name = "dailyRankingWriter")
  private ItemWriter<Ranking> dailyRankingWriter;
  
  ...
  
   @Test
  @DisplayName("성공 케이스")
  void job__success() throws Exception {
    // given
    JobParameters parameters =
        new JobParametersBuilder()
            .addString(
                "date", LocalDate.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd")), true)
            .addString("test version", UUID.randomUUID().toString(), true)
            .toJobParameters();

    given(dailyRankingMatchCntReader.read()).willReturn(getRanks().get(0), getRanks().get(1), null);
    given(dailyRankingGameMoneyReader.read()).willReturn(getRanks().get(1), null);
    doNothing().when(dailyRankingWriter).write(any());

    // when
    JobExecution jobExecution =
        jobTestUtils
            .getJobTester(DailyRankingJobConfig.JOB_NAME)
            .launchJob(jobTestUtils.makeJobParameters(parameters));

    // then
    assertThat(jobExecution.getStatus()).isEqualTo(BatchStatus.COMPLETED);
    // reader의 경우 chunk의 갯수만큼 호출
    verify(dailyRankingMatchCntReader, times(3)).read();
    verify(dailyRankingGameMoneyReader, times(2)).read();

    // writer의 경우 chunk 당 한번 호출(여기선 갯수가 적어 스텝 당 한 번임)
    final ArgumentCaptor<Chunk> captor = ArgumentCaptor.forClass(Chunk.class);
    verify(dailyRankingWriter, times(2)).write(captor.capture());
    List<Chunk> chunks = captor.getAllValues();
    assertThat(chunks.size()).isEqualTo(2);
    assertThat(chunks.get(0).size()).isEqualTo(2);
    assertThat(chunks.get(1).size()).isEqualTo(1);
  }

그러면 given.. willReturn/willThrow 등 기존에 사용하던 mocking 함수를 사용할 수 있게 된다!!


참고

https://jojoldu.tistory.com/236

 

SpringBatch에서 ItemReader를 Mock객체로 교체하기

안녕하세요? 이번 시간엔 SpringBatch에서 ItemReader를 Mock객체로 교체하는 예제를 진행해보려고 합니다. 모든 코드는 Github에 있기 때문에 함께 보시면 더 이해하기 쉬우실 것 같습니다. (공부한 내용

jojoldu.tistory.com

 

728x90
반응형

+ Recent posts