[개발] spring batch와 Querydsl의 활용

도현김·2023년 6월 23일
post-thumbnail

N. 테스트


1. @SpringBatchTest 가 필요하다.

  • ApplicationContext 에 테스트에 필요한 여러 유틸 Bean을 등록하여 아래의 것들을 @Autowired 할 수 있음.
  • 이전에는 직접 @Configuration 해서 빈 주입을 해줬어야 했음.
  1. JobLauncherTestUtils : (통합 테스트)

    • 스프링 배치 테스트에 필요한 전반적인 유틸 기능들을 지원한다.

    • CLI 등으로 실행하는 Job을 테스트 코드에서 Job을 실행할 수 있도록 지원한다.

    • jobLauncherTestUtils.launchJob(jobParameters) : JobParameter와 함께 Job을 실행한다.

      • 운영 환경에서는 CLI로 배치를 수행하겠지만, 지금 같은 테스트 코드에서는 JobLauncherTestUtils 를 통해 Job을 수행하고 결과를 검증함.
    • 해당 Job의 결과는 JobExecution에 담겨 반환한다.

    • 성공적으로 Batch가 수행되었는지는 jobExecution.getStatus()로 검증한다.

    • jobLauncherTestUtils.getUniqueJobParametersBuilder() 로 jobParameter를 만들면 job 실행시 매번 다른 값을 넣어줄 필요가 없다.

  2. JobRepositoryTestUtils : (통합 테스트)

    • DB에 생성된 JobExecution을 쉽게 생성/삭제 가능하게 지원
  3. StepScopeTestExecutionListener : (단위 테스트)

    • 배치 단위 테스트시 StepScope 컨텍스트를 생성
    • 해당 컨텍스트를 통해 JobParameter등을 단위 테스트에서 DI 받을 수 있음
  4. JobScopeTestExecutionListener : (단위 테스트)

    • 배치 단위 테스트시 JobScope 컨텍스트를 생성
    • 해당 컨텍스트를 통해 JobParameter등을 단위 테스트에서 DI 받을 수 있음

2. @Transactional 사용 불가

  • Spring Batch에서는 @Transactional을 사용할 수 없다.
    • 이유는 스프링 배치는 Job과 Step의 실행 상태를 관리하는 JobRepository가 있다. JobRepository의 오퍼레이션에 스프링 배치 외부의 트랜잭션 개입을 막기 위해 Job 실행 시점에 외부에 기존 트랜잭션이 열려있는 지를 검사한다. 만약 트랜잭션이 활성화 되어 있다면 Exception을 던져 Job 실행을 중단한다.
  • 그렇기 때문에 테스트 전 데이터를 초기화하고 싶거나, 테스트 후 데이터를 삭제하고 싶다면 직접 entitymanager로 트랜잭션 시작하고 커밋한 다음 job을 실행하거나 테스트를 종료해야한다.
entityManager.getTransaction().begin();

// ... 데이터 초기화 ...

entityManager.getTransaction().commit();

// ... job 실행 ...

entityManager.getTransaction().begin();

// ... 데이터 삭제 ...

entityManager.getTransaction().commit();

// ... 테스트 종료 ...

3. @ConditionalOnProperty, @TestPropertySource 의 사용

  • job을 지정하는 방법은 2가지가 있다.
    1. @SpringBootTest(classes={...})@Configurarion 클래스를 함께 로드하고 그 @Configurarion 클래스에서 특정 job을 지정하는 방법.
    2. 테스트 코드에 @TestPropertySource, 배치 코드에 @ConditionalOnProperty를 사용해서 여러 job 중 하나를 지정하는 방법 (+ @SpringBootTest)

3-1. @SpringBootTest(classes={...}) 의 사용

  • @SpringBootTest(classes={...}) 를 하면 classes에 지정된 클래스만 빈 주입을 한다.

  • 내부적으로 @ContextConfiguration를 사용한다.

  • 그냥 @SpringBootTest으로 job Test를 실행시킬 경우 여러 job이 중복되어 있기 때문에 어떤 것을 선택해야하는 것인지 알 수가 없어 에러가 발생한다.

  • @SpringBootTest(classes={...}) 를 통해 단일 job Config만 선택하도록 해야한다.

  • 장점 :

    • 테스트 코드에서 @Configuration 클래스를 작성한 후 클래스를 classes={…}에 포함시키면 되기 때문에 배치 코드에서 @ConditionalOnProperty와 같이 코드를 작성할 필요가 없다.
    • 전체 테스트 수행 시 매번 Spring Context가 실행되지 않는다.
  • 단점 :

    • classes={…}에 올려질 클래스가 누락되면 오류가 발생한다.
    • job 테스트마다 다른 @Configuration 클래스를 만들어야 하기 때문에 번거롭다.
    • 빈 충돌이 발생할 수도 있다.

3-2. @ConditionalOnProperty, @TestPropertySource 의 사용

  • @ConditionalOnProperty

    • @ConditionalOnProperty(name = "job.name", havingValue = job명)
    • @ConditionalOnProperty 어노테이션은 애플리케이션 코드에서 사용되며, 특정 조건이 충족될 때만 빈이나 설정이 활성화된다. 주로 application.properties 또는 application.yml과 같은 애플리케이션 프로퍼티 파일에서 지정한 프로퍼티의 값을 기준으로 동작함.
  • @TestPropertySource

    • @TestPropertySource(properties = {"job.name=" + job명})
    • 테스트 환경에서는 일반적으로 @TestPropertySource를 사용하여 특정 프로퍼티 값을 설정하고, 이러한 테스트 프로퍼티를 기반으로 테스트를 수행한다.
  • 즉, @ConditionalOnProperty는 설정된 ‘name’과 ‘havingValue’가 application.yml이나 application.properties에 있다면 실행이 되는 것인데, 그 값을 테스트할 때 @TestPropertySource로 값을 주입시켜 @ConditionalOnProperty가 있는 빈을 동작시킬 수 있는 것이다.

  • 장점

    1. Bean 충돌을 걱정안해도 된다.
      • 운영 환경에서도 @ConditionalOnProperty 덕분에 Job / Step / Reader 등의 Bean 생성시 다른 Job에서 사용된 Bean 이름에 대해서 크게 신경쓰지 않아도 된다.
    2. 운영 환경에서의 Spring 실행 속도가 빠르다.
      • 1번과 마찬가지로 운영 환경에서 배치가 수행될때 단일 Job 설정들만 로딩되기 때문에 경량화된 상태로 실행 가능하다.
    3. @ConditionalOnProperty@TestPropertySource 에 실행할 Batch job에 대한 이름이 적혀있기 때문에 직관적이다.
  • 단점

    1. 흔히 말하는 행사 코드가 많이 필요하다.
      • 배치 코드에서는 @ConditionalOnProperty(name = "job.name", havingValue = job명), 테스트 코드에서는 @TestPropertySource(properties = {"job.name=" + job명}) 등의 코드가 항상 필요하다.
    2. 전체 테스트 수행시 매번 Spring Context가 재실행된다.
      • @TestPropertySource로 인해 전체 테스트 수행시 매번 Spring의 Context가 다시 생성된다.
      • 단일 테스트 속도는 빠르나 전체 테스트에선 너무 느리다.
    3. @SpringBootTest 에 classes={…} 를 지정하지 않기 때문에 모든 빈이 스프링 컨테이너에 로드되기 때문에 무겁다.

    나는 @ConditionalOnProperty, @TestPropertySource 의 방법이 더 편리하고 직관적이라고 생각하며 전체 테스트 수행시 매번 Spring Context가 재실행된다고 하지만 프로젝트에서 배치 테스트의 개수가 적기 때문에 큰 차이를 느끼지 못하고 있다. 그리고 @Configuration 클래스를 작성하는 것이 번거롭다고 생각하기 때문에 @SpringBootTest(classes={...}) 방법을 사용하고 있지 않다.

※ 참고

profile
안녕하세요! 신입 개발자 김도현입니다.

0개의 댓글