@SpringBootTest를 사용하여 테스트 코드를 실행하면 스프링의 ApplicationContext가 로드됩니다. 하지만, 매 테스트마다 ApplicationContext가 초기화 된다면 모든 빈(Bean)이 새로 생성되므로 테스트 속도가 매우 느려질텐데요.
이 문제를 해결하기 위한 방법으로 Context Caching(컨텍스트 캐싱)이 사용됩니다.
Spring의 TestContext프레임워크는 한 번 ApplicationContext을 로드하면, 같은 컨텍스트 구성을 사용하는 모든 테스트에서 이를 캐싱하여 재사용합니다.
그렇다면 같은 컨텍스트는 어떤 기준으로 판단되는걸까요?
이는 특정 구성 매개변수(설정값)의 조합에 의해 식별됩니다. 즉, 특정 구성 매개변수가 동일하다면 해당 컨텍스트를 캐싱하여 공유하고, 다르다면 새로운 컨텍스트가 생성됩니다.
스프링의 TestConxt 프레임워크는 아래 구성 매개변수(Configuration Parameter)를 조합하여 Context Cache Key를 생성합니다.
이 키를 기준으로 컨텍스트를 캐싱 및 재사용할지, 새로운 컨텍스트를 생성할지 결정합니다.
| 구성 요소 | 설명 |
|---|---|
locations (@ContextConfiguration) | XML 기반 설정(app-config.xml, test-config.xml 등) |
classes (@ContextConfiguration) | Java 기반 설정 (@Configuration 클래스) |
contextInitializerClasses (@ContextConfiguration) | 컨텍스트 초기화 클래스 |
contextCustomizers (ContextCustomizerFactory) | @DynamicPropertySource 메서드, Spring Boot의 @MockBean, @SpyBean 사용 여부 |
contextLoader (@ContextConfiguration) | 컨텍스트 로더 (AnnotationConfigContextLoader, GenericXmlContextLoader 등) |
parent (@ContextHierarchy) | 부모 컨텍스트 설정 |
activeProfiles (@ActiveProfiles) | 활성 프로필 (dev, test, prd 등) |
propertySourceDescriptors (@TestPropertySource) | 테스트 프로퍼티 소스 파일 (classpath:test.properties 등) |
propertySourceProperties (@TestPropertySource) | 테스트 프로퍼티 소스에서 직접 지정한 속성 값 |
resourceBasePath (@WebAppConfiguration) | @WebAppConfiguration을 사용할 때 기본 리소스 경로 |
아래 예제에서 같은 activeProfiles값을 사용한 두 개의 테스트 클래스가 같은 ApplicationContext를 공유하는지 확인 해보겠습니다.
@SpringBootTest(value = "spring.profiles.active=test")
public class TestClassA {
@Autowired
private ApplicationContext applicationContext;
@Test
public void 테스트A() {
System.out.println("테스트A : " + applicationContext);
}
}
@SpringBootTest(value = "spring.profiles.active=test")
public class TestClassB {
@Autowired
private ApplicationContext applicationContext;
@Test
public void 테스트B() {
System.out.println("테스트B : " + applicationContext);
}
}
ApplicationContext을 주입받아 직접 콘솔에 찍어보았습니다.
테스트A : org.springframework.web.context.support.GenericWebApplicationContext@1b52699c, started on Sun Feb 02 18:40:00 KST 2025
테스트B : org.springframework.web.context.support.GenericWebApplicationContext@1b52699c, started on Sun Feb 02 18:40:00 KST 2025
1b52699c로 클래스 고유번호가 동일하므로 TestClassA와 TestClassB가 같은 ApplicationContext를 공유하는 것을 확인할 수 있었습니다.
@MockBean)실제 테스트 코드를 작성할 땐 Configuration이나 activeProfiles을 다르게 하지 않는 이상 대부분은 컨텍스트 캐싱이 적용될 것이라 예상됩니다. 하지만, 외부 의존성이 있는 경우 특히 외부 통신이 있는 경우 @MockBean을 사용하게 되면 컨텍스트가 변경되므로 새로운 ApplicationContext가 생성될 수 있습니다.
따라서 진짜 @MockBean을 사용하면 ApplicationContext가 달라지는지 테스트 해보겠습니다.
Test할 Bean을 정의해주고
@Component
public class TestBean {
public void hi(){
System.out.println("Hi I'm Test Bean");
}
}
TestClassA에서는 TestBean을 @MockBean으로 주입받아 hi메서드를 호출해줍니다.
@SpringBootTest(value = "spring.profiles.active=test")
public class TestClassA {
@Autowired
private ApplicationContext applicationContext;
@MockBean
private TestBean testBean;
@Test
public void 테스트A() {
testBean.hi();
System.out.println("테스트A : " + applicationContext);
}
}
그리고 TestClassB에서는 실제 Bean을 주입받아 hi메서드를 호출해주었습니다.
@SpringBootTest(value = "spring.profiles.active=test")
public class TestClassB {
@Autowired
private ApplicationContext applicationContext;
@Autowired
TestBean testBean;
@Test
public void 테스트B() {
testBean.hi();
System.out.println("테스트B : " + applicationContext);
}
}
테스트A : org.springframework.web.context.support.GenericWebApplicationContext@58f254b1, started on Sun Feb 02 18:58:42 KST 2025
Hi I'm Test Bean
테스트B : org.springframework.web.context.support.GenericWebApplicationContext@176939ad, started on Sun Feb 02 18:58:47 KST 2025
결과는 예상대로 고유번호가 각각 다른 ApplicationContext가 구성된것을 확인할 수 있었습니다.
TestClassA에서는 @MockBean을 사용하여 TestBean을 Mock객체로 만들었기에 새로운 컨텍스트가 생성되었고 TestClassB는 실제 빈을 주입받았으므로 TestClassA와 다른 ApplicationContext가 로드되었습니다.
따라서 @MockBean을 사용한 경우 컨텍스트 캐싱이 적용되지 않은 것을 확인할 수 있었습니다.
테스트 코드를 작성하다보면, 클래스마다 @MockBean을 사용할 수 도 있고, 사용하지 않을 수 도 있으며, 정의하는 빈(Bean)도 각각 다를 것입니다. 이러한 차이로 인해 Context Caching이 비활성화 되면 테스트 속도가 저하될 수 있습니다.
이를 방지하기 위해 공통 @MockBean을 한번에 정의하는 Supporter클래스 만들어 테스트 클래스들이 동일한 ApplicationContext를 공유하도록 설정하면 실행 속도를 최적화 할 수 있습니다.
아래처럼 Supporter 클래스를 만들고
@SpringBootTest(value = "spring.profiles.active=test")
public class BeanCachingSupporter {
@MockBean
TestBean testBean;
//.. 이 외에도 사용할 mock Bean 정의
}
이를 상속받아 테스트코드를 작성해줍니다.
public class TestClassA extends BeanCachingSupport {
@Autowired
private ApplicationContext applicationContext;
@Test
public void 테스트A() {
System.out.println("테스트A : " + applicationContext);
}
}
이렇게 되면 해당 Supporter 클래스를 상속받은 모든 테스트 클래스는 동일한 ApplicationContext를 공유하게 되어 Context Caching이 활성화되고 테스트 실행속도 또한 빨라질 것입니다.
@MockBean뿐 아니라@ActiveProfiles,@TestPropertySource등과 같은 구성 매개변수(Configuration Parameter)도 공통 Supporter 클래스에 정의하면 테스트 환경이 일관되게 유지 하면서 Context Caching을 더욱 효과적으로 적용할 수 있습니다.
@MockBean과 같은 특정 Configuration Parameter를 사용하면 새로운 컨텍스트가 생성되어 캐싱이 비활성화 된다.이 내용은 Bean을 캐싱할 수 있는 부모 Suppoter 클래스를 활용하는게 어떻겠냐는 코드 리뷰를 받으면서 정리하게 되었습니다. 처음에는 ApplicationContext가 캐싱될 수 있다는 사실조차 몰랐지만, 리뷰를 받고 직접 예제를 만들어보면서 실제로 캐싱 가능하다는 것을 알게되었네요.
통합 테스트 시 ApplicationContext가 새로 생성되는지 여부를 체크해보시고, 필요한 경우 Context Caching을 활용하여 속도 개선해보시기를 추천드립니다!