[ 정수원 스프링 배치 #17 ] - 테스트와 스케줄링

정동욱·2023년 11월 16일
0
post-thumbnail

이번 글에서는 배치 테스트 코드를 작성하는 법과 Quartz 라이브러리를 이용해 실행시키는 방법을 알아보겠습니다.

먼저 알아야 할 점은 배치는 하나의 Job에 대해서만 테스트 코드를 작성할 수 있다는 점입니다. 코드로 보겠습니다. @SpringBootTest 어노테이션에 테스트할 Job 클래스를 지정하는 방식으로 Job을 테스트합니다. 그리고 JobLauncherTestUtils를 주입받는데, Job이나 Step을 실행하는 API를 사용하기 위함입니다. 외에도 JobRepositoryTestUtils를 이용해 JobExecution 등 배치 테이블을 관리할 수 있습니다.

// 1개의 Job에 대해서만 테스트 코드를 작성할 수 있다.
@RunWith(SpringRunner.class)
@SpringBootTest(classes = JobConfiguration11.class)
@SpringBatchTest
@EnableBatchProcessing
@EnableAutoConfiguration
public class SimpleJobTest {

    @Autowired private JobLauncherTestUtils launcherTestUtils;
    @Autowired private JdbcTemplate jdbcTemplate;

    @Test
    public void simpleJob_test() throws Exception {
        // given
        JobParameters jobParameters = new JobParametersBuilder()
                .addString("name", "user1")
                .addLong("date", new Date().getTime())
                .toJobParameters();

        // when
        JobExecution jobExecution = launcherTestUtils.launchJob(jobParameters);
        JobExecution jobExecution1 = launcherTestUtils.launchStep("step11");
		StepExecution stepExecution = (StepExecution) ((List) jobExecution1.getStepExecutions()).get(0);

        // then
        assertEquals(jobExecution.getExitStatus(), ExitStatus.COMPLETED);
        assertEquals(jobExecution.getStatus(), BatchStatus.COMPLETED);

        assertEquals(stepExecution.getCommitCount(), 2);
        assertEquals(stepExecution.getReadCount(), 100);
        assertEquals(stepExecution.getWriteCount(), 100);
    }

    @After
    public void clear() {
        jdbcTemplate.execute("DELETE FROM Customer2");
    }
}

그리고 이제 배치를 Quartz를 이용해 스케줄링으로 실행시켜보겠습니다. 지금부터 나올 내용은 제가 만든 가계부 프로젝트의 기존 스케줄러를 배치 스케줄러로 바꾸는 과정에 적용해본 것입니다.

배치를 실행시키기 위해선 JobLauncherJobRunner(ApplicationRunner) 두 객체와 여러 설정이 필요합니다. 우리가 만들 CustomJobLauncherQuartzJobBean을 상속받으며 executeInternal() API를 오버라이딩해 사용합니다. 코드로 보겠습니다. 먼저 필드에 accountHistoryJob을 볼 수 있는데, 이는 우리가 생성한 Job이 매서드명으로 Bean 등록된 이름입니다. 저렇게 Bean으로 등록된 객체의 이름을 그대로 가져다 쓰면 해당 객체를 사용하는 것이 됩니다. 보면 JobLauncher가 run() API를 사용하면서 JobJobParameters를 인자로 받습니다. 정리하자면 Job을 실행시키기 위해서는 JobLauncher가 필요하고, JobLauncher는 인자로 JobJobParameters를 받아야 하는데, 이는 강의 초반부에 나오는 내용입니다.

@Component
@RequiredArgsConstructor
public class AccountHistoryJobLauncher extends QuartzJobBean {

    private final Job accountHistoryJob;
    private final JobLauncher jobLauncher;

    @Override
    @SneakyThrows
    protected void executeInternal(JobExecutionContext context) throws JobExecutionException {
        JobParameters jobParameters = new JobParametersBuilder()
                .addLong("id", new Date().getTime())
                .toJobParameters();

        jobLauncher.run(accountHistoryJob, jobParameters);
    }
}
@Bean
public Job accountHistoryJob() {
    return jobBuilderFactory.get("accountHistoryJob")
            .incrementer(new RunIdIncrementer())
            .start(accountHistoryStep())
            .listener(accountHistoryJobListener())
            .build();
}

그리고 JobRunner 쪽을 보겠습니다. JobDetail, Trigger, Scheduler 3개의 객체가 등장합니다 모두 Quartz 라이브러리가 제공하는 것들입니다. JobDetail은 Job의 이름, 파라미터 등의 Job을 실행하기 위한 정보를 가지고 있으며 Job의 인스턴스를 생성합니다. Trigger는 cron 표현식 등을 이용해 Job의 실행 시점을 정의합니다. Scheduler**는 scheduleJob() API를 사용해 스케줄링을 지정합니다.

@Component
@RequiredArgsConstructor
public class AccountHistoryJobRunner implements ApplicationRunner {

    private final Scheduler scheduler;

    @Value("${accountHistory.scheduler.cron}")
    private String schedulerCron;

    @Override
    public void run(ApplicationArguments args) throws Exception {


        JobDetail jobDetail = buildJobDetail(AccountHistoryJobLauncher.class, "accountHistoryJob", "batch", new HashMap<>());
        Trigger trigger = buildJobTrigger(schedulerCron);

        try {
            scheduler.scheduleJob(jobDetail, trigger);
        } catch (SchedulerException e) {
            e.printStackTrace();
        }
    }

    public Trigger buildJobTrigger(String scheduleExp) {
        return TriggerBuilder.newTrigger()
                .withSchedule(CronScheduleBuilder.cronSchedule(scheduleExp))
                .build();
    }

    public JobDetail buildJobDetail(Class job, String name, String group, Map params) {
        JobDataMap jobDataMap = new JobDataMap();
        jobDataMap.putAll(params);

        return newJob(job).withIdentity(name, group)
                .usingJobData(jobDataMap)
                .build();
    }
}

그리고 마지막으로 yml파일에 배치 프로그램이 자동 실행되지 않도록 하는 설정을 해주어야 하는데요, 이렇게 해야 자동으로 실행되지 않고 cron 표현식에 의해 실행됩니다. 그리고 톰캣을 동작시키면 스케줄러가 정상적으로 실행됩니다.

batch:
  job:
    enabled: false

이번 글로 배치 강의에 대한 끝이 났습니다. 다음 글에서는 제가 만든 가계부 프로젝트를 도커+젠킨스+AWS를 이용해 배포하는 과정에 대해 작성해보겠습니다. 감사합니다.

profile
거인의 어깨 위에서 탭댄스를

1개의 댓글

comment-user-thumbnail
2024년 3월 4일

글을 너무 잘쓰셔서 잘 읽었습니다 감사합니다~!

답글 달기

관련 채용 정보