위키
Jest 실행 속도 개선

2024.06.26

Jest 실행 속도 개선

진행 배경

현재 재직중인 회사는 GitHub Acitons의 기업용 무료 플랜을 사용하고 있습니다. GitHub이 제공하는 가상머신을 매월 최대 2,000분까지 무료로 사용할 수 있는데요.(참고 (opens in a new tab)) 대체로 주어진 할당량 내에서 큰 불편함 없이 사용해오고 있지만, 한때 제공받은 시간을 모두 사용하여 CI를 실행하지 못하는 경우가 있었습니다.

우선 제가 현재 담당한 프로젝트의 GitHub Actions 사용 실태를 확인해 보았습니다. CI 진행시 Test job에서 가장 많은 시간을 소비하고 있네요. (CI 1회 수행시 전체 사용시간의 약 66% 차지) 따라서 테스트 실행 속도를 개선해서 GitHub Actions 사용 시간을 줄여보기로 했습니다. 테스트 실행 시간이 줄어들면 피드백 주기가 짧아지니 업무 생산성을 높이는 데에도 도움이 될 것으로 보았습니다.

CI screenshot

테스트에서 가장 많은 시간을 소비하고 있습니다


성능 개선을 위한 벤치마킹 준비

Jest의 테스트 실행 속도를 개선하는 방법은 구글링 한 번 만으로도 여러가지가 나옵니다. 하지만 어떤 방법을 적용하는것이 좋을지, 설정값은 얼마가 적당한지는 프로젝트의 상황에 따라 다릅니다. 테스트 실행 환경, 테스트 갯수, 스냅샷 테스트의 유무 등 여러 요인이 작용하기 때문입니다.

테스트 실행 속도를 개선하는 방법은 소요시간이 긴 테스트 코드를 직접 개선하거나 서드파티 라이브러리를 사용하는 방법 등 다양하게 있지만, 이번에는 이미 사용중인 Jest와 관련된 패키지들의 옵션만을 활용하여 실행 속도를 개선해 보기로 했습니다. 3가지 정도의 성능 개선 옵션을 현재 담당한 프로젝트에 적용해 보고, 실제로 유의미한 개선 효과가 있는지 벤치마킹을 해보았습니다.

테스트 구성

작성된 Jest 테스트 코드는 다음과 같이 구성되어 있습니다.

  • Test Suites: 35 total
  • Tests: 197 total
  • Snapshots: 0 total

벤치마킹 환경

벤치마킹을 실행한 환경정보는 다음과 같습니다.

Apple M1 Pro / 10 Cores (8 performance and 2 efficiency)

벤치마킹 설계

테스트 세트별로 이전 테스트 세트로부터의 영향을 없애기 위해 Jest 캐시를 삭제하여 초기화하는 작업을 수행하고 총 6번의 실행을 통해 평균, 최소, 최대 소요시간을 측정했습니다. 첫번째 실행시에는 캐시가 없는 상태이기 때문에 테스트 실행 소요시간이 가장 깁니다. (Max값에 해당)

테스트 설계

테스트 1세트당 테스트 설계

추가로, 벤치마킹을 효율적으로 진행하고자 hyperfine (opens in a new tab)을 사용했습니다.


성능 개선 옵션별 벤치마킹

성능 개선 방법을 적용하지 않은 초기 상태에서는 테스트를 모두 실행하는데 평균 25.4초가 소요됐습니다.

hyperfine --setup 'npx jest --clearCache' 'npm run test:local' --runs 6

성능 개선 전


--runInBand

Jest는 기본적으로 Node.js 런타임에서 동작하기 때문에 worker_thread 모듈을 사용해서 테스트를 병렬로 처리할 수 있습니다. 하지만 여러 개의 worker를 사용하는 것이 모든 경우에 적절한 것은 아닙니다. 테스트 코드의 양이 적다면 테스트를 실행하는데 걸리는 시간보다 여러개의 worker를 시작하는데 걸리는 시간이 더 오래 걸리기 때문입니다. 이 경우 하나의 worker 프로세스만 사용하는것이 더 빠른데, 이때 사용할 수 있는 옵션이 바로 --runInBand (opens in a new tab) 입니다. --runInBand 옵션을 활성화 시키면 하나의 worker 프로세스에서 모든 테스트를 순차적으로 실행하게 됩니다.

hyperfine --setup 'npx jest --clearCache' 'npm run test:local -- --runInBand' --runs 6

--runInBand 테스트 결과

테스트 갯수가 적지 않아서 그런지 소요 시간이 2배 이상 증가했습니다. 테스트를 순차적으로 실행한다는 특성이 있으니 문서에서 언급하는대로 디버깅시 사용해 볼 수 있겠네요.


isolatedModules

두번째로는 ts-jest의 isolatedModules (opens in a new tab) 옵션을 적용해 보았습니다. 이 옵션은 테스트 실행시 타입스크립트 타입 체킹을 skip 함으로써 속도 개선의 효과를 얻을 수 있는 방법입니다.

isolatedModules 테스트 결과

테스트 소요 시간이 평균 17초로, 약 32.9% 개선되는 결과가 나왔습니다.


--maxWorkers

마지막으로 최적의 worker 갯수를 찾아보았습니다. 가용 worker의 10%부터 100%까지 10% 단위로 벤치마킹을 해보았는데요. 가용 worker의 60%를 사용했을때 테스트 소요 시간이 가장 짧았습니다.

hyperfine --setup 'npx jest --clearCache' --parameter-scan max_workers 10 100 -D 10 'npm run test:local -- --maxWorkers={max_workers}%' --runs 6

--maxWorkers 테스트 결과

참고로 --maxWorkers (opens in a new tab) 옵션은 watch mode(--watch 또는 --watchAll)를 사용하지 않는 경우에만 적용됩니다.(링크 (opens in a new tab)) 테스트를 진행한 프로젝트의 경우 개발 환경에서 watch mode를 사용하기 때문에 이 옵션은 CI 환경에만 적용하는것이 적절하다고 판단했습니다.


결과

최종적으로 다음과 같이 테스트 실행 환경별로 적합한 옵션과 최적의 설정값을 도출할 수 있었는데요.

로컬 개발 환경 (watch mode 사용)CI환경 (watch mode 미사용)
--runInBand△ 디버깅시 사용 고려△ 디버깅시 사용 고려
isolatedModulesOO
--maxWorkers=60%XO

실제로 적용해보니 테스트 소요 시간이 30% 이상 개선되었습니다.

  • 로컬 개발 환경: 25.444 s17.075 s (32.9% 개선)
  • CI환경 : 240.293 s138.399 s (42.4% 개선)

CI환경에서의 가상머신 사용 시간은 6분 4초에서 4분 7초로 32% 가량 감소했습니다.

CI 소요시간 - before

성능 개선 전


CI 소요시간 - after

성능 개선 후