
스프링 기반 애플리케이션에서 테스트를 작성하다 보면 테스트 실행 중 컨텍스트가 반복적으로 로드되는 상황을 종종 마주하게 된다. 기본적으로 스프링 테스트 프레임워크는 동일한 설정을 사용하는 테스트에 대해 컨텍스트를 재사용하도록 설계되어 있다. 그러나 설정 매개변수가 조금이라도 달라지면 새로운 컨텍스트를 다시 로드하게 되며, 이 과정은 시간과 메모리 측면에서 상당한 비용을 발생시킨다.
Spring Test Context 캐싱
Spring Test Context는 컨텍스트를 구성하는 매개변수 조합을 기준으로 캐시 여부를 판단한다. 이 조합에는 로드되는 설정 클래스, 프로파일, 활성화된 빈 정의, 테스트 어노테이션의 종류 등이 포함된다. 즉, 테스트 코드에서 사용하는 설정이 완전히 동일해야만 하나의 컨텍스트를 재사용할 수 있다.
예를 들어 첫 번째 테스트에서 A, B, C 설정을 사용하고 두 번째 테스트에서 A, B만 사용한다면 비록 일부 설정이 겹치더라도 컨텍스트는 새로 로드된다. 이러한 특성 때문에 테스트 설정을 통일하지 않으면 컨텍스트 재사용의 이점을 얻기 어렵다.
컨텍스트 캐싱 상태는 로그를 통해 확인할 수 있다. 테스트 전용 설정 파일에 아래와 같이 로그 레벨을 추가하면 캐시 히트 여부를 추적할 수 있다.
# application-test.yml
logging:
level:
org.springframework.test.context.cache: TRACE
로그에는 캐시 크기, 히트 횟수, 미스 횟수 등이 출력된다. missCount가 증가하는 시점은 새로운 컨텍스트가 로드되었음을 의미하며, hitCount가 증가한다면 기존 컨텍스트가 재사용되고 있다는 뜻이다. 대규모 테스트 환경에서는 이 로그를 통해 설정 통일 여부를 점검하는 것이 중요하다.
Spring Test Mock 설정
Mock 객체 또한 컨텍스트 재사용에 큰 영향을 미친다. MockBean이나 @TestConfiguration을 통해 새로운 빈 정의가 추가되면 스프링 입장에서는 새로운 설정 조합으로 인식하게 된다. 그 결과 기존 컨텍스트를 재사용하지 않고 새로 로드하게 된다.
이를 방지하기 위해 Mock 객체를 개별 테스트 클래스마다 정의하기보다는 공통 설정 클래스로 분리하는 것이 좋다. 아래 예시는 외부 API를 모킹하기 위한 TestConfiguration 예시다.
@TestConfiguration
public class MockBeanConfig {
@Bean
@Primary
public LkdCodeApi mockLkdCodeApi() {
return BDDMockito.mock(LkdCodeApi.class);
}
@Bean
@Primary
public AnotherCodeApi mockAnotherCodeApi() {
return BDDMockito.mock(AnotherCodeApi.class);
}
}
Mock 객체는 빈으로만 등록해서는 충분하지 않다. 테스트마다 다른 행위를 정의해야 하는 경우가 많기 때문에 @BeforeEach 단계에서 reset과 스텁 설정을 함께 수행해야 한다.
public abstract class BaseLkdCodeMockBeanInitializeSupport {
@Autowired
protected LkdCodeApi lkdCodeApi;
@Autowired
protected AnotherCodeApi anotherCodeApi;
@BeforeEach
void init() {
BDDMockito.reset(lkdCodeApi);
BDDMockito.willDoNothing().given(lkdCodeApi).someMethod(any());
BDDMockito.reset(anotherCodeApi);
BDDMockito.given(anotherCodeApi.someMethod(any())).willReturn("lkdCode");
}
}
reset을 수행하지 않으면 이전 테스트에서 설정한 스텁이 남아 예상치 못한 테스트 실패를 유발할 수 있다. 공통 초기화 로직을 분리하면 컨텍스트 재사용과 테스트 안정성을 동시에 확보할 수 있다.
Spring Test 확장 전략
컨텍스트 재사용을 극대화하려면 테스트 어노테이션 구성 또한 일관되게 관리해야 한다. @ContextConfiguration을 활용해 공통 설정을 묶은 추상 테스트 클래스를 만들면 여러 테스트에서 동일한 컨텍스트를 공유할 수 있다.
@ContextConfiguration(classes = {MockBeanConfig.class})
@ExtendWith(SpringExtension.class)
@SpringBootTest
public abstract class BaseSpringBootSupport {
}
JUnit5의 @ExtendWith는 테스트 생명주기 전반에 개입할 수 있는 확장 포인트를 제공한다. 이를 활용하면 테스트 전후 로직, 파라미터 주입, 공통 초기화 작업 등을 일관되게 처리할 수 있다.
아래는 여러 콜백 인터페이스를 구현한 확장 클래스 예시다.
public class LkdCodeTestExtension implements
BeforeAllCallback, AfterAllCallback,
BeforeEachCallback, AfterEachCallback {
@Override
public void beforeAll(ExtensionContext context) {
System.out.println("Before all tests");
}
@Override
public void afterAll(ExtensionContext context) {
System.out.println("After all tests");
}
@Override
public void beforeEach(ExtensionContext context) {
System.out.println("Before each test");
}
@Override
public void afterEach(ExtensionContext context) {
System.out.println("After each test");
}
}
이러한 확장 전략을 공통 베이스 클래스로 관리하면 테스트 코드의 중복을 줄이고 컨텍스트 재사용률을 높일 수 있다. 결과적으로 테스트 실행 속도와 유지보수성을 동시에 개선할 수 있다.