1. 개요
Spring은 테스트에 사용되는 애플리케이션 컨텍스트를 생성/관리/적용해주는 기능을 가진 테스트 프레임워크를 제공하며, 이를 Spring TestContext Framework라고 한다. Spring TestContext Framework는 다양한 기능들을 제공하는데, 이 중 대표적인 것이 바로 테스트 컨텍스트 캐싱(TestContext Caching)이다.
본격적으로 알아보기에 앞서 테스트 컨텍스트의 의미에 대해 한 번 짚어보자. 사실 정확히 말하자면 '테스트 컨텍스트'는 테스트에서 사용되는 애플리케이션 컨텍스트(= ApplicationContext or WebApplicationContext)를 생성하고 관리해주는 오브젝트를 가리키는 용어다. 그러나 테스트가 사용하는 애플리케이션 컨텍스트도 테스트 컨텍스트라고 지칭하기도 하므로 문맥에 따라 잘 파악해야 한다. 참고로 테스트 컨텍스트 캐싱에서 말하는 '테스트 컨텍스트'는 후자인 테스트용 애플리케이션 컨텍스트를 의미한다.
2. 캐싱이 제공되는 이유?
기본적으로 JUnit은 @Test 애너테이션이 붙어있는 테스트 메서드를 실행할 때마다 매번 새로운 테스트 클래스 인스턴스를 생성한다(이는 테스트 프레임워크마다 다른데, kotest 등의 프레임워크는 클래스 단위로 인스턴스를 생성한다). 이는 테스트를 독립적으로 실행함으로써 멱등성을 보장하기 위함이다. 그러나 테스트가 독립적으로 실행된다고 해서 컨텍스트까지 새로 생성하는 것은 매우 비효율적이다.
이는 Spring 자체의 오버헤드도 문제지만, 빈 오브젝트를 생성하고 초기화하는 과정에서 시간이 오래 걸리는 경우가 있기 때문이다. 예를 들어 Hibernate와 같은 ORM을 사용할 땐 엔티티 클래스를 로딩하거나 스레드를 생성하는 데 많은 부가 작업을 필요로 하는데, 이런 객체 초기화 작업이 모든 테스트마다 수행된다면 테스트 실행시간은 기하급수적으로 늘어나게 될 것이다.
3. 그래서 어떻게 캐싱되는데?
Spring은 컨텍스트를 캐싱해두고 동일한 컨텍스트 구성을 갖는 테스트끼리는 같은 컨텍스트를 공유하게 한다. 애플리케이션 컨텍스트는 유일하게 식별되는 설정 파라미터(configuration paramters)의 조합에 따라 로드된다. 즉, 설정 파라미터의 유니크한 조합을 통해서 컨텍스트가 캐시되는 키가 생성된다.
여기서 설정 파라미터란 게 무엇인지 의문이 들 것이다. Spring에선 테스트를 위한 다양한 애너테이션들을 제공하는데, 그 중 테스트 컨텍스트 구성에 관여할 수 있는 애너테이션들이 있다. 설정 파라미터란 이러한 애너테이션들의 속성을 의미한다. 설정 파라미터의 종류엔 다음과 같은 것들이 있다.
- locations (from @ContextConfiguration)
- classes (form @ContextConfiguration)
- contextInitializerClasses (from @ContextConfiguration)
- contextCustomizer (from ContextCustomizerFactory)
- 여기에는 @DynamicPropertySource가 붙은 메서드나 @MockBean이나 @SpyBean 등이 포함된다
- parent (from @ContextHierarchy)
- activeProfiles (from @ActiveProfiles)
- propertySourceLocations (from @TestPropertySource)
- propertySourceProperties (from @TestPropertySource)
- resourceBasePath (from @WebAppConfiguration)
이 설명만 봐서는 잘 이해가 가지 않을 수 있으므로 예제 코드를 살펴보자. 한 번 코드를 본 뒤 잠시 멈추고 몇 개의 애플리케이션 컨텍스트가 생성될지 고민해보자.
@ContextConfiguration(classes = TestConfig1.class)
class Test1 {
// ...
}
@ContextConfiguration(classes = TestConfig1.class)
class Test2 {
// ...
}
@ContextConfiguration(classes = TestConfig2.class)
class Test3 {
// ...
}
@ContextConfiguration(classes = TestConfig1.class)
@ActiveProfiles("dev")
class Test4 {
// ...
}
위 예제에서는 총 3종류의 애플리케이션 컨텍스트가 생성될 것이다.
// 1번
{ classes = TestConfig1.class, ... }
// 2번
{ classes = TestConfig2.class, ... }
// 3번
{ classes = TestConfig1.class, activeProfiles = "dev" ... }
3. 공유 컨텍스트 사용 시 주의할 점
마지막으로 캐싱된 공유 컨텍스트를 사용할 때 주의할 점 몇 가지를 소개하고 마무리하고자 한다.
1) 테스트들이 각기 다른 프로세스에서 실행되면 컨텍스트 캐싱 기능을 사용할 수 없다.
Spring TextCotext Framework는 애플리케이션 컨텍스트를 static 변수에 저장하여 캐싱한다. 따라서 다른 프로세스 간에 캐싱된 컨텍스트를 공유해서 사용할 수 없다. 따라서 컨텍스트 캐싱의 이점을 누리기 위해선 같은 프로세스 또는 JVM 상에서 테스트를 실행시켜야 한다. 별도의 설정을 하지 않는 한, 테스트는 같은 프로세스에서 실행되므로 크게 주의할 점은 아닌 듯 싶다.
2) 컨텍스트의 캐시의 크기는 기본적으로 32로 제한된다.
컨텍스트 캐시의 크기는 기본적으로 32로 제한되며, LRU(Least Recently Used) 알고리즘에 의해 갱신된다. spring.test.context.cache.maxSize 옵션을 통해 이 캐시 사이즈를 바꿀 수 있다. 그러나 너무 많은 애플리케이션 컨텍스트가 로드되면 테스트 수행 시간이 느려지므로 반드시 필요한 경우가 아니라면 그대로 두자.
3) 애플리케이션 컨텍스트가 손상되면 테스트의 멱등성을 보장할 수 없다.
테스트에서 사용되는 애플리케이션 컨텍스트는 공유되므로 그 구성이나 내부 정보를 함부로 변경하면 테스트의 멱등성이 깨질 수 있다. @DirtiesContext 애너테이션을 붙이면 테스트를 통해 컨텍스트가 오염될 것으로 예상하고 테스트가 수행된 후에 사용한 컨텍스트를 제거한다. 해당 애너테이션은 메서드와 클래스 레벨에 모두 붙일 수 있는데, 메서드에 붙이면 테스트 메서드가 실행될 때마다 컨텍스트가 제거되고 클래스에 붙이면 테스트 클래스 내 모든 테스트 메서드가 실행된 뒤 컨텍스트가 제거된다.
@Test
@DirtiesContext
public void test() { ... }
@DirtiesContext // @DirtiesContext(classMode = ClassMode.AFTER-EACH_TEST_METHOD)와 동일하게 동작
class TestClass { ... }
그러나 이와 같이 @DirtiesContext를 사용하게 되면 애플리케이션 컨텍스트의 생성과 초기화가 반복되므로 컨텍스트 캐싱의 이점을 취할 수 없다는 단점이 있다. 따라서 반드시 필요한 경우에 한해 적절하게 사용해야 하며, 기본적으로 애플리케이션 컨텍스트나 빈 객체의 상태를 변경하지 않도록 주의를 기울여야 한다.
4. 참고
https://docs.spring.io/spring-framework/reference/testing/integration.html#testing-ctx-management
토비의 스프링 3.1 (이일민 저)
'Spring > Spring' 카테고리의 다른 글
[Spring] 로컬 캐시의 영향 없이 테스트 환경 구축하기 (0) | 2023.07.18 |
---|---|
[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 |