fragile and resilient

Spring

[Spring] 테스트 시간 최적화 (feat.Context Caching)

Green Lawn 2022. 8. 15. 23:23

기능 구현이 늘어나면서 테스트 코드 또한 많아졌는데요.

그에 따라 필연적으로 테스트를 돌리는 시간이 점점 늘어나게 되고, 피드백을 받는 시간이 딜레이됐습니다.

전체 테스트를 돌리는 시간이 꽤 걸리다 보니, 개발을 진행하다가 흐름이 끊기는 것 같은 느낌이 들 때도 있었습니다.

 

만약 각각의 테스트마다 새로운 스프링 컨텍스트를 띄우면 어떨까요?

매번 새로운 컨텍스트를 띄우기 때문에 테스트를 돌리는 시간이 매우 오래 걸릴 것입니다.

 

따라서 스프링에서는 Context Caching이라는 것을 제공하는 데요.

스프링은 테스트가 사용하는 컨택스트를 캐싱해서 여러 테스트에서 하나의 컨텍스트를 공유할 수 있는 방법을 제공합니다.

동일한 컨텍스트 구성을 갖는 테스트끼리는 같은 컨텍스트를 공유하는 것입니다.

  • 테스트용 컨텍스트 공유는 테스트 클래스 내의 메서드 사이에서만 가능한 게 아니라, 여러 테스트 클래스 사이에서도 가능합니다.

따라서 테스트를 매우 빠르게 실행시킬 수 있습니다.

ApplicationContext는 로드하는데 사용되는 configuration parameters의 조합으로 고유하게 식별할 수 있습니다. (configuration parameters의 조합을 사용해서 cache key를 생성합니다.)


TestContext 프레임워크는 다음 구성 매개 변수를 사용하여 컨텍스트 cache key를 구성합니다.

  • locations (from @ContextConfiguration)
  • classes (from @ContextConfiguration)
  • contextInitializerClasses (from @ContextConfiguration)
  • contextCustomizers (from ContextCustomizerFactory) – this includes @DynamicPropertySource methods as well as various features from Spring Boot’s testing support such as @MockBean and @SpyBean.
  • contextLoader (from @ContextConfiguration)
  • parent (from @ContextHierarchy)
  • activeProfiles (from @ActiveProfiles)
  • propertySourceLocations (from @TestPropertySource)
  • propertySourceProperties (from @TestPropertySource)
  • resourceBasePath (from @WebAppConfiguration)

여기서 중요한 점은 어떤 bean을 mock으로 했는지(@MockBean)가 컨텍스트 재사용 여부에 영향을 준다는 것입니다(4번째 항목).

따라서 @MockBean 처리한 빈들의 조합이 달라질 경우, 매번 새로운 컨텍스트를 띄우게 됩니다.

프로젝트의 @WebMvcTest에서 매번 새로운 스프링 컨텍스트를 띄우고 있는 이유였습니다.

 

프로젝트에서 @WebMvcTest에서는 비교적 가벼운(?) 검증들을 하고 있었는데도 불구하고, 매번 새로운 컨텍스트를 띄우고 있기 때문에, 테스트 효율에 안 좋은 영향을 주고 있었습니다.


예시 코드를 보여드리겠습니다.

 

UnauthorizedReviewWebMvcTest.class
BasRequestReviewWebMvcTest.class

BadRequestReviewWebMvcTest 클래스와 UnauthorizedReviewWebMvcTest 클래스는 각각 잘못된 형식으로 요청한 경우(Bad Request)와 사용자 인증과 관련된 예외를 검증하는 @WebMvcTest입니다.

두 클래스에서 생성이 필요한 Bean들과 필드에 선언된 MockBean들이 비슷한 듯 조금씩 다른 빈들을 필요로 하고 있다는 것을 알 수 있습니다.

따라서 밑의 Run 창을 보시면 둘 다 스프링 컨텍스트를 각각 띄우는 것을 확인해볼 수 있습니다.

 

그렇다면 여기서 두 클래스가 필요로 하는 Bean들을 통일시키면 어떨까요 ? (물론 사용되지 않는 빈들이 생성되긴 하지만)

UnauthorizedReviewWebMvcTest 클래스가 먼저 실행되었기 때문에 해당 클래스에서는 스프링 컨테이너가 올라갔지만, BadRequestReviewWebMvcTest에서는 스프링 컨테이너가 띄워지지 않은 것을 확인해 볼 수 있었습니다.

즉, 앞에서 생성한 컨테이너를 재사용하고 있다는 것입니다.

 

따라서 @WebMvcTest 환경을 통일하는 방식으로 개선해 보았습니다.

WebMVCTest라는 추상 클래스를 만들고 @WebMvcTest 환경에서는 해당 클래스를 상속받도록 했습니다.

해당 방식으로 처음 띄워지는 WebMvcTest를 제외하고, 다른 WebMvcTest에서는 새로운 컨텍스트를 띄우지 않고 재사용하는 것을 확인했습니다.

 

테스트 실행 시간이 엄청나게 줄어들지는 않았지만, 그래도 꽤 줄어들었고 새로운 컨텍스트를 띄우지 않아서 좋은 개선이라고 생각합니다.

 


References

- Team 모아모아 

- 이일민, 토비의 스프링 3.1 Vol.2 스프링의 기술과 선택, 2019

- PR: https://github.com/woowacourse-teams/2022-moamoa/pull/224

- https://docs.spring.io/spring-framework/docs/current/reference/html/testing.html#testcontext-ctx-management-caching

- https://bperhaps.tistory.com/entry/%ED%85%8C%EC%8A%A4%ED%8A%B8-%EC%BD%94%EB%93%9C-%EC%B5%9C%EC%A0%81%ED%99%94-%EC%97%AC%ED%96%89%EA%B8%B0-1

- https://suhwan.dev/2019/03/27/spring-test-context-management-and-caching/

- 땡큐 찬