LocalDateTime 을 사용하는 함수는 어떻게 테스팅 해야하지?
테스트 코드를 작성하는 도중, LocalDateTime 에 따라 기능이 달라지는 함수를 테스팅할 필요성을 느끼게 됐다.
근데 LocalDateTime.now()를 사용하게 되면, 테스트가 현재 시간에 따라 테스트 결과가 달라지는 상태가 되고, 이는 테스트는 반복가능해야 한다라는 테스트 규칙을 위반하게 된다.
가장 단순한 해결책은 LocalDateTime을 Mocking 하는 것이겠지만, 이는 권장되지 않는 방법이다.
- @Mock 어노테이션은 매 테스트마다 새로 초기화되기 때문에, 반복가능한 테스트 문제에 대한 걱정이 없지만,
- LocalDateTime 은 static 메소드라 메소드 영역에 존재하고, 다음 테스트에서 여전히 그 모킹이 유지되어 있을 수 있다.
- 따라서 절차지향적인 테스트 코드가 되어버리고, 반복가능한 테스트를 만들지 못한다.
그렇다면 어떤 방식으로 해결하는게 좋을까?
해결책 → clock 객체로 주입받기
자바는 이런 문제를 이미 예상하고, LocalDateTime 의 static 메소드를 "순수 스태틱 메소드"로 만들어두지 않았다.
LocalDateTime.now() 직접 시간을 계산하지 않고, 실제로는 LocalDateTime.now(Clock clock) 를 호출한다.
실제로 now() 메소드는 now(clock) 을 호출하고, clock 에 담긴 정보를 바탕으로 현재 시간을 불변 객체로 반환하게 된다.
LocalDateTime 객체는 "불변 객체로 만드는 책임"을 담당하고, 실제 시간 계산은 clock 객체에 위임하는 형태로 구현한 것이다.
따라서 개발자는 의존관계 주입을 통해 Clock 객체를 주입받고, 이에 따라 현재 시간을 불변 객체로 사용할 수 있도록 권장하고 있는 것이다.
하지만, 현재 나의 메소드는 도메인 객체에서 직접 LocalDateTime.now() 를 호출하고 있었고, 의존관계를 주입받으려면 "도메인 객체가 스프링 빈으로 등록"되는 요상한(?) 상황에 놓여지게 되는 문제가 발생하였다.
진화된 해결책 → 시간 자체를 서비스 로직에서 주입받기
따라서 어떠한 방법으로 현 상황을 우아하게 타개할 수 있을까 고민한 끝에, 도메인 객체가 LocalDateTime 메소드를 직접 사용하지 않도록 하였다.
그렇다면 남은 문제는 Clock 를 어떻게 의존관계 주입받고, 내가 생각하는 적절한 시간으로 바꿔치기 할 수 있을까? 라는 고민이었다. 이를 해결하기 위해서, Clock 의 구조와 정의, API 의 사용법을 알아보았다.
Clock 의 구조
- 인스턴스 생성
- 시스템 시계: Clock.systemDefaultZone()
- 실제 시스템의 현재 시간과 기본 시간대를 사용한다.
- 시간이 계속 흐르며 실제 시스템 시간을 반영한다.
- 고정 시계: Clock.fixed(Instant, ZoneId)
- 생성 시 지정된 특정 시점의 시간을 계속 반환한다.
- 시간이 흐르지 않고 항상 같은 값을 반환한다.
- 오프셋 시계: Clock.offset(Clock, Duration)
- 기준이 되는 Clock에서 지정된 Duration만큼 오프셋된 시간을 제공한다.
- 눈금 시계: Clock.tick(Clock, Duration)
- 지정된 Duration 단위로만 시간이 진행되는 것처럼 동작한다.
- 시스템 시계: Clock.systemDefaultZone()
- 현재 시간 반환: clock.Instant()
- 해당 객체가 가리키는 현재 시간 반환
- 해당 메소드를 모킹하면 된다!
- 현재 위치 반환: clock.getZone()
- 해당 객체가 가리키는 현재 시간대(ZoneId) 반환
- 해당 메소드도 모킹하면 된다!
따라서, clock 객체를 Mock 객체로 생성하고, clock 객체가 수행할 동작을 모킹으로 대체함으로써 원하는 테스트 코드를 작성할 수 있었다.
테스트 구현 예시
'WEB BE > JAVA' 카테고리의 다른 글
Java Thread Model 의 역사 (0) | 2025.01.06 |
---|