1. 개요
Spring에서 제공하는 로컬 캐시를 사용하면 간단하게 성능을 향상시킬 수 있다. 그러나 로컬 캐시의 특성상 메모리에 데이터를 저장하므로 테스트 수행 시엔 예상치 못한 결과를 얻을 수 있고, 이는 테스트의 멱등성을 깨뜨리게 된다.
다음과 같이 상품의 재고 소진 여부를 체크하는 코드가 있다. 재고가 한 번 소진되면 다시 복구되는 일은 잘 없으므로 로컬 캐시를 적용했다고 가정해보자. (실제로는 서버가 한 대가 아니고서야 로컬 캐시 대신 글로벌 캐시를 적용하는 편이 좋을 것이다)
@Cacheable(value = ["item"], key = "#itemId")
fun isSoldOut(itemId: String): Boolean {
return itemRepository.existsInventory(itemId)
}
간단한 테스트 코드도 작성했다. 이제 테스트 코드가 성공할 것으로 기대하지만, 두 번째 테스트가 실패한다. 이는 메모리에 올라가 있는 로컬 캐시를 각 테스트 인스턴스가 공유하기 때문에 발생한 문제다.
private val item = Item(itemNo = "item1", ...)
@Test
fun `재고가 없는지 확인한다`() {
val isItemSoldOut = itemService.isSoldOut("item1")
assertThat(isItemSoldOut).isTrue()
}
@Test
fun `재고가 증가하는지 확인한다`() {
itemService.addInventory("item1")
val isItemSoldOut = itemService.isSoldOut("item1")
assertThat(isItemSoldOut).isFalse() // Assertion Error!!
}
2. 해결
1) 다른 캐싱 키를 가지는 테스트 픽스쳐 만들기
단순한 해결 방법은 아래와 같이 모든 테스트 케이스마다 유니크한 캐싱 키를 가진 테스트 픽스쳐를 생성하면 된다. 그러나 테스트 픽스쳐의 생성 비용이 높은 경우엔 이 방법의 여의치 않을 수 있다. 그럴 땐 다음 방법을 고려해보자.
private val item1 = Item(itemNo = "item1", ...)
private val item2 = Item(itemNo = "item2", ...)
@Test
fun `재고가 없는지 확인한다`() {
val isItemSoldOut = itemService.isSoldOut("item1")
assertThat(isItemSoldOut).isTrue()
}
@Test
fun `재고가 증가하는지 확인한다`() {
itemService.addInventory("item2")
val isItemSoldOut = itemService.isSoldOut("item2")
assertThat(isItemSoldOut).isFalse()
}
2) CacheManager를 주입받은 다음에 직접 캐시 비우기
테스트용 애플리케이션 컨텍스트는 CacheManager 또한 빈으로 등록한다. 다음과 같이 캐시를 참조한 후에 직접 비우는 방식을 사용하면 간단하게 문제를 해결할 수 있다. 이러한 방식은 이처럼 로컬 캐시가 운영 환경에선 정상적으로 동작하지만, 테스트 환경에선 동작하지 않도록 설정하고 싶은 경우에 유용하게 사용할 수 있다. 그러나 반대로 로컬 캐시가 동작하지 않음으로 실제 운영 환경과 동일하지 않은 동작이 발생할 수 있으므로 이에 주의해야 한다.
class Test {
@Autowired
lateinit var cacheManager: CacheManager
@BeforeEach
fun setup() {
cacheManager.getCache("item")?.clear() // cacheManger.getCache(캐시명)?.clear()
}
// ...
}
'Spring > Spring' 카테고리의 다른 글
[Spring] 스프링의 테스트 컨텍스트 캐싱(Spring TestContext Caching)에 대해 알아보자 (0) | 2023.07.17 |
---|---|
[Spring] @ContextConfiguration이란? (0) | 2023.07.16 |
[Spring] feign + hystrix 사용 시 특정 예외 무시하도록 처리하기 (0) | 2023.05.15 |
[Spring] Reactive Feign에서 커스텀 Serializer/Deserializer 사용하기 (0) | 2023.03.21 |
[Spring] @Bean 애너테이션의 name 속성과 value 속성 (0) | 2023.03.07 |