ShedLock 사용 시 스케줄러 통합테스트 주의점

은찬·2024년 1월 12일

Test

목록 보기
2/2

배경


프로젝트를 수행하며 스케줄러 통합테스트 관련해서 내가 겪은 문제 상황과 슈팅한 과정에 대해서 공유해보려한다 🫡

내가 맡은 스케줄러 로직을 구현하고 하위 단위 테스트를 마친 후에 마지막으로 스케줄러 통합 테스트를 짜고 문제없이 성공해서 PR 올리고.. CI 통과하고.. 잘 진행되고있었다.

그리고 팀원이 PR 을 올리고 CI 가 돌던 중 테스트가 깨졌길래 팀원에게 테스트가 깨지니까 확인해달라고 했다. 근데 팀원의 테스트가 아니라 내가 올린 스케줄러 통합테스트가 실패했었다 😲

트러블 상황


실제 로직 흐름

로직의 흐름은 아래와 같다

  1. 활성상태인 예매대기 정보를 가져온다 (활성상태 예약대기는 만료시간 이라는 속성을 가진다)
  2. 현재 시간과 예약대기의 만료시간을 비교해서 만료시간이 현재시간을 만료시간이 현재시간을 지났으면 만료된 예약대기를 종료상태로 변경하고 예약대기와 연관된 좌석의 상태를 대기상태에서 예매가능상태 로 변경한다

간단하게 말하자면 예매대기의 만료시간이 현재시간보다 빠르면 예매대기와 예매대기에 묶인 좌석의 상태가 변경되고 현재시간보다 느리면 그대로 둔다.

테스트 코드 흐름

테스트 코드 세팅 및 기댓값

[세팅]

1번 : WAITING 상태의 좌석 3개를 준비한다

2번 : 현재 시간을 정의하고 그걸 기준으로 이전, 이후 시간을 정의한다

3번 : 0번, 1번 데이터에는 이전 시간을, 2번 데이터에는 이후 시간을 할당 & 좌석은 하나씩 가짐

[기댓값 및 검증]

직접 만든 검증 메소드인데 이름 그대로 좌석과 예매대기의 상태를 검증하는 메소드다.
파라미터 : (검증 대상, 기대되는 상태)

위 세팅 코드를 보면 0번, 1번 좌석은 beforeNow 시간이 할당된 예매대기에 속한다.

그래서 0번, 1번은 상태가 바뀐 AVAILABLE 을 기대하고 2번은 afterNow 시간이 할당된 예매대기에 속하기 때문에 바뀌지 않은 WAITIING 을 기대한다.

테스트 결과

성공했다! 근데 세네번에 한번꼴로 실패했다… 🫨(어질어질)

처음엔 이유가 LocalDateTime 관련한 이슈인줄 알았다. 하지만 해당 이슈를 해결하고 나서도 같은 상황이 나오길래 디버깅을 열심히 해봤다.

해결 과정


문제는 두가지였다.

먼저 문제를 유발하는 스케줄러는 크론식으로 5초 단위로 돌아가는 스케줄러다.

💡 1 - 통합테스트를 하며 애플리케이션 컨텍스트를 띄워서 테스트 도중 5초 단위의 시간이 되면 스케줄러가 실행된다.

💡 2 - 스케줄러가 돌아가고 ShedLock 이 적용돼서 해당 스케줄러에 락을 건다. 나는 설정에 최소 3초는 락을 잡고있는 설정이 있어서 5초에 스케줄러가 실행된다면 8초까진 스케줄러 메소드가 실행이 막힌다.

이런 조건속에서 타이밍이 안좋으면 테스트 메소드를 실행되기 전에 스케줄러가 먼저 돌아서 테스트 메소드에서 호출하는 스케줄러 로직이 실행이 안되는 것이었다

이게 뭐가 문제냐… 테스트 메소드가 실행되기 전에는 테스트 데이터가 세팅이 돼있지 않아서 스케줄러 로직이 실행돼도 아무일도 일어나지 않는다. 아무일도 일어나지 않는데 shedlock 으로 이후 실행을 블로킹 해버리니 간헐적으로 테스트가 실패하는 것이었다…

로그로 분석

실제로 확인하기 위해 스케줄러에 로그를 추가했다

그리고 테스트가 실패할 때 로그를 보면

로그가 실행된 스레드를 보면 Test Worker 스레드가 아닌 별도의 스케줄러 스레드에서 실행되는걸 볼 수 있다.(스케줄러는 별도로 지정한 스레드 풀에서 스레드를 할당하도록 설정돼있다)

그리고 테스트가 성공할 때 로그를 보면

Test Woker 스레드에서 실행되는 걸 볼 수 있다

해결 방법

간단하다!

스케줄러 설정을 테스트할 때 빼버리면 된다! 👍 그러면 스케줄러가 테스트할 때 돌아가지 않는다.

@Configuration
@EnableScheduling
@EnableAsync
@Profile("!test")
public class SchedulerConfig {

	@Bean("scheduler")
	public TaskScheduler taskScheduler() {
		ThreadPoolTaskScheduler executor = new ThreadPoolTaskScheduler();
		executor.setPoolSize(5);
		executor.setThreadNamePrefix("scheduler-thread-");
		executor.initialize();
		return executor;
	}
}

이런식으로 스케줄러 설정을 테스트 환경에서만 제외시키면 된다!

이렇게 되면 스케줄러는 테스트할 때 컨텍스트가 올라와도 돌아가지 않아서 문제가 해결된다 😎

마치며


이번에 겪은 문제는 원인을 찾기가 정말 힘들었다. 생각지도 못한 부분이었고, 그리고 간헐적으로 문제가 발생해서 디버깅에 더 힘들었다.

하지만 그만큼 문제 원인을 발견하고 해결했을 때, 정말 뿌듯했다… 이게 개발의 묘미가 아닐까 싶다.. ㅎㅎ😎

해당 이슈는 shedLock 을 사용하지 않더라도 단순히 스케줄러가 먼저 실행되는 것 만으로도 문제가 생길 수 있는 부분이니 스케줄러 환경을 통합테스트한다면 조심하자… 😓

profile
서버 개발하는 사람입니다

0개의 댓글