프로그래밍 공부

테스트 코드 시간 줄이기

테스트 코드의 속도

TDD를 이용하여 개발을 진행하다 보면 테스트 속도가 개발 과정에서 영향을 미치는 일이 점점 많아진다. 회고덕 프로젝트를 진행하는 과정에서 테스트 속도를 최적화했던 과정을 공유하고자 한다.

 

 

DirtiesContext 제거이전

이는 굉장히 초반에 제거한 부분으로, 테스트 속도의 개선이 가장 눈에 띄었던 부분이다.

 

DirtiesContext란?

@DirtiesContext는 인수테스트시에 사용하는 메서드인데, 이는 개발자가 정한 기준 (테스트 수행 전/후, 각 테스트 케이스 수행 전/후 등)에 맞춰서 Context를 매번 재생성하여 테스트 격리를 만드어두는 것이다.
스프링 공식문서 링크

 

테스트 결과

과거 코드의 경우에는 테스트코드가 굉장히 적어서, 현재 회고덕 코드에 DirtiesContext를 켜고 테스트를 해본 결과 각 AcceptanceTest의 개수만큼 context가 만들어진 것을 볼 수 있다. (acceptancetest의 메서드 21개 → 21개가 만들어졌다. )
결과적으로 테스트 코드 296개를 실행하는데 실행 시간만 5분 15초, 이를 위해서 준비하는 시간까지 포함하자면 10분 이상의 시간이 들게 된다.

 

 

DirtiesContext 제거

DirtiesContext의 경우에는 context를 매번 새로 띄우는 방식이 아닌 다른 방식으로 테스트 격리를 해결하면 되는데, 회고덕의 경우에는 truncate.sql을 매번 실행하는 것으로 해결하였다.

 

 

 

 

DirtiesContext 제거 이후

기본적으로 Spring으로 작성된 application의 integration test를 돌리기 위해서는 ApplicationContext가 필요하다. Junit 4를 기반으로 작성되었을 경우 다음과 같이 실행된다.

  1. 테스트 클래스의 instance를 생성한다. 이때 instance는 no-args constructor를 통해 생성된다.
  2. 테스트에 필요한 bean으로 ApplicationContext를 구성한다.
  3. 2의 테스트 instance에, 1에서 생성한 ApplicationContext를 활용하여 필요한 bean을 주입한다.

DirtiesContext를 실행하였을 때에는 모든 context를 메서드별로 생성하였다. 그렇다면 DirtiesContext를 제거한 이후에는 Context가 어떻게 생성되었을까?
이를 위해서는 ApplicationContext가 캐싱되는 기준에 대해서 알아보아야 한다.

 

ApplicationContext 캐싱 기준

공식문서 링크 / 공식문서 컨텍스트 캐싱
기본적으로 한번 로드된 Appliacation은 캐싱되어 각 테스트에 재사용된다. 이때 캐싱이 작동되는 방식을 이해하려면 “고유한” “test suite(제품군)”라는 개념을 이해하는 것이 필요하다. 이는 아래 두 가지 조건을 충족하는 것을 의미한다.

  1. 같은 bean의 조합을 필요로 하고
  2. 이전 테스트에서 ApplicationContext가 오염되지 않은 경우

그렇다면 1에서 말하는 같은 bean의 조합을 충족시키는 것은 어떻게 판별할까? 이는 곧 context caching에서 cache key가 무엇으로 이루어지는 지에 대한 질문과도 연결된다.
Spring에서는 테스트 클래스의 여러 configuration으로 이를 파악한다. 자세히는 아래와 같다.

  • 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으로 처리했느냐(Mockito의 @MockBean을 사용했느냐)가 ApplicationContext 재사용 여부에 영향을 미친다는 것이다.
Mockito의 @MockBean을 사용할 경우 contextCustomizers에 MockitoContextCustomizer가 추가되는데, 이 때문에 테스트 클래스에서 @MockBean 처리한 bean의 조합이 달라질 경우 cache key가 달라지게 된다. 따라서 비록 같은 @ContextConfiguration classes attributes를 가졌다고 하더라도 @MockBean의 조합이 달라지면 Spring TestContext는 ApplicationContext를 재사용하지 않는다.

 

 

변경 과정 & 테스트 결과

회고덕의 경우에는 Controller Test에서 해당 문제점을 파악해볼 수 있었다.


기존

각 ControllerTest마다 bean의 조합이 달라서 각 테스트 별로 spring context를 띄워주고 있다.

 


수정

시간 차이는 dirties context를 제거하였을 때보다 훨씬 적지만, spring context를 각 controllerTest별로 띄우지 않는 것을 확인할 수 있었다.
또한, 인텔리제이에서 확인할 수 있는 테스트 시간은 실제로 테스트가 진행된 시간이기 때문에 context가 띄워지는 등의 시간은 포함되지 않아 실제 jenkins에서 확인한 시간은 차이가 다소 나는 편이다.

 


jenkins

Jenkins에서 확인하였을 때에는 기존 1분 30초대가 걸리던 것에 비해서 대략 5초~10초 정도의 시간을 절약했다는 것을 알 수 있었다.

참고자료

https://bperhaps.tistory.com/entry/테스트-코드-최적화-여행기-3
https://suhwan.dev/2019/03/27/spring-test-context-management-and-caching/
https://gocheat.github.io/spring/spring_test-2/

'프로그래밍 공부' 카테고리의 다른 글

InnoDB 스토리지 아키텍처  (7) 2022.10.17
무중단 배포란?  (0) 2022.09.27
VPC란 무엇인가?  (2) 2022.09.20
[TDD] 테스트 코드 작성 순서와 종류  (0) 2022.05.19
도메인이란 무엇인가?  (1) 2022.05.15