Test Code

What is Vitest? Vite와 함께 알아보자 + 코드 커버리지 확인

남희정 2023. 8. 9. 19:43

2023.08.05 - [Test Code] - New 새 맥북에서 pnpm + Vite 환경 + Vitest 구축하기

Vitest Blazing Fast Unit Test Framework

엄청나게 빠른 단위 테스트 프레임워크

 

Vite에 대해 들어본 적이 있다면 Vite에서 빌드된 빠른 단위 테스트 프레임 워크인 Vitest에 대해 들어봤을 것이다. Vitest가 무엇인지, 어떻게 사용하는 건지, 왜 나의 앱을 위한 다음 테스트 프레임워크가 될 수 있는지 알아보자!

 

Vite

Vitest로 들어가기 전에, Vite의 출현을 먼저 얘기해보겠다. 

브라우저에서 ESM(ES Modules)을 지원하기 전까지, JavaScript 모듈화를 네이티브 레벨에서 진행할 수 없었다. 그래서 소스 모듈을 브라우저에서 실행할 수 있는 파일로 Crawling 크롤링, 처리 및 연결하는 Bundling 번들링이라는 해결 방법을 사용해야 했었다.

 

크롤링 Crawling

  • 웹사이트에서 데이터를 수집하는 과정을 말한다.
  • HTML, CSS, JavaScript 등의 내용을 분석하여 원하는 정보를 추출하는 작업

 

번들링 Bundling

  • 모듈화된 소스 코드를 브라우저에서 실행할 수 있는 파일로 한데 묶어주는 작업
  • 모듈 시스템이 없을 때 HTML 파일에 스크립트 파일을 불러와서 사용할 경우 전역 공간에 모든 변수가 노출될 수 있었고 여러 스크립트를 불러왔을 때 변수의 이름이 겹쳐 충돌이 발생하는 문제점이 생김.

 

<html>
  <script src="/src/foo.js"></script>
  <script src="/src/bar.js"></script>
  <script src="/src/baz.js"></script>
</html>
// foo.js, bar.js, baz.js 중 하나라도 변수 이름이 겹치는 선언이 전역에 있다면 충돌이 발생한다.

 

Module Bundling

Javascript 모듈화 이전에는 CommonJS, AMD(Asynchronous Module Definition), UMD(Universal Module Definition) 등의 모듈 시스템이 있었지만, 이들은 언어 자체에 내장되어 있지 않아서 브라우저에서 바로 사용할 수 없었다. 2008년에 구글에서 브라우저 외부에서도 JavaScript를 실행시킬 수 있는 V8엔진을 소개하면서 모듈화에 대한 필요성이 더욱 부각됐다. 브라우저 외부 환경에서 JS를 실행할 수 있게 되면서, 특히 Node.js를 통해 JavaScript로 서버를 개발하는 것이 큰 인기를 얻게 됐다. 더불어 모듈 표준화를 위한 움직임이 본격적으로 시작됐다.

Native ESM(ES Modules)

EcmaScript 6 (ES6)에서 도입된 기능. 브라우저와 Node.js에서 네이티브로 JavaScript 모듈 시스템을 지원한다.

  • import 및 export 키워드를 사용하여 네이티브로 간단하고 직관적인 모듈화 구현.
  • 모듈 표준화
  • 동기/비동기 로드를 지원, 편리한 순환 참조 관리, 쉬운 트리 쉐이킹
  • IE 같은 구형 브라우저에서는 제대로 동작하지 않는다는 문제점이 있었음
<!-- 모듈을 포함하는 HTML 파일 -->
<script type="module">
  // 모듈 로드
  import { greet } from './greeting.js';

  // 모듈의 함수 사용
  greet('John');
</script>
// greeting.js 파일
export function greet(name) {
  console.log(`Hello, ${name}!`);
}

기존 Bundler

모든 브라우저에서 모듈 시스템을 완전하게 지원하지 않았기 때문에 개발자들은 '번들링'이라는 우회적인 방법을 사용해야 했다. 따라서 브라우저와 무관하게 모듈 시스템을 지원하기 위해 Webpack, Rollup 그리고 Parcel 같은 번들링 도구, 번들러가 탄생했다. 이런 번들러 도구는 프런트엔드 개발자의 생산성을 크게 향상시켜줬다.

 

기존 번들러들은 엔트리 JavaScript 파일부터 시작해서 소스 코드와 `node_modules` 폴더 전체 코드 베이스를 묶고, 필드 프로세스를 통해 실행한 다음 번들된 코드를 브라우저에 제공한다.

 

콜드 스타트 방식(최초로 실행되어 이전에 캐싱한 데이터가 없는 경우를 의미)으로 개발 서버를 구동할 때, 번들러 기반의 도구의 경우 애플리케이션 내 모든 소스 코드에 대해 크롤링 및 빌드 작업을 마쳐야지만이 실제 페이지를 제공할 수 있다. 웹 애플리케이션이 커지면 커질수록 애플리케이션을 시작하는데 오래 걸리게 되었고, 또한 대부분의 번들러는 Node.js 기반으로 돌아가기 때문에 싱글 스레드로 인한 처리 한계를 가졌다.

차세대 모듈 번들러 등장

위에서 나타난 문제점을 해결하기 위해 snowpack, vite 같은 차세대 번들러들이 등장했다.

더욱 빠른 서버 구동

Vite는 애플리케이션의 모듈을 Dependencies와 Source code 두 가지 카테고리로 나누어 개발 서버의 시작 시간을 개선했다.

Dependencies 의존성

  • 개발 시 그 내용이 바뀌지 않을 일반적인(Plain) JavaScript 소스 코드
  • Vite는 사전 번들링을 네이티브 영역에서 돌려 성능을 높였다.
  • Go 기반의 Esbuild를 사용, 기존 번들러 대비 10-100배 빠른 속도를 가진다.

더욱 빠른 소스 코드 갱신

Source code 소스 코드

  • JSX, CSS 또는 Vue/Svelte 컴포넌트와 같이 컴파일링이 필요하고, 수정 또한 매우 잦은 Non-plain JavaScript 소스 코드
  • Native ESM을 이용
  • 소스코드는 Native ESM을 사용하고, NPM 패키지들은 Esbuild로 사전 번들링을 수행한다.

 

 

초록색 상자로 표시된 route를 어떤 페이지라고 해보자. 이 페이지를 요청하면 브라우저는 route에 해당하는 브랜치의 정보만 필요하다. 따라서 브라우저는 ESM을 통해 이 페이지의 모듈에 대한 소스 코드만을 번들러에게 요청하고, 번들러는 해당하는 소스 코드만을 전달한다.

 

어떤 모듈이 수정됐을 때도 번들러는 수정된 모듈과 관련된 부분만 교체하고, 브라우저에서 해당 모듈을 요청하면 교체된 모듈을 전달하면 되기 때문에 전체를 다시 번들할 필요가 없다. 전 과정에서 완벽하게 ESM을 이용하기에, 앱 사이즈가 커져도 HMR을 포함한 갱신 시간에는 영향을 끼치지 않는다.

 

여기서 더 나아가 Vite의 경우 HTTP 헤더를 이용해 요청한 소스 코드가 `304 Not Modified`, 디펜던시는 `Cache-Control: max-age=31536000`, `immutable`를 이용해 캐시되도록 함으로써 요청을 줄여 성능을 더 높였다.

 

So Vitest?

Vitest는 Vite를 기반으로 구축된 단위 테스트 프레임워크이다. 

 

🔸 Vue, React, Svelte, Lit 등을 위한 컴포넌트 테스트

🔸 별도의 설치나 구성이 필요 없이 바로 사용할 수 있는 TypeScript / JSX 지원

🔸 ESM 우선, 최상위 레벨 await

JavaScript 모듈의 최상위 레벨에서도 비동기 작업을 수행할 수 있는 기능

🔸  멀티 스레딩 workers

🔸  Test Suite 및 Test용 필터링, 타임아웃, 동시성 관리

- 테스트 스위트나 개별 테스트를 원하는 기준에 따라 필터링하여 실행

- 타임아웃 : 각 테스트가 일정 시간 내에 완료되지 않을 경우 자동으로 실패 처리되는 기능

- 동시성 : 여러 테스트가 동시에 실행되는 환경에서의 효율적인 테스트 실행을 지원

🔸 Jest 호환 스냅샷

- UI 컴포넌트나 렌더링 결과를 포함하여 예상한 출력과 실제 출력을 비교할 때 유용하게 사용

🔸  표명 Assertion을 위한 내장된 Chai 및 Jest expect 호환 API

- 테스트 코드에서 특정 조건이나 예상 결과가 실제 결과와 일치하는지 확인하는 기능

- Chai와 Jest 프레임워크에서 사용하는 assertion 문법과 비슷한 문법을 사용하여 테스트 코드에서 예상 결과를 검증할 수 있는 API를 제공

- 개발자는 익숙한 assertion 스타일을 사용하여 테스트 코드를 작성할 수 있다.

- Chai는 다양한 assertion 스타일을 지원하는 라이브러리로, 테스트 코드에서 예상 결과와 실제 결과를 비교하기 위한 다양한 방법을 제공한다.

- Jest는 JavaScript 프레임워크로, 테스트 코드 작성을 위한 도구와 assertion 기능을 내장하고 있다.

🔸  Jest 호환 API로 디자인

 

Vitest와 다른 테스트 프레임 워크 비교하기

Vitest는 인기 있는 테스트 프레임 워크인 Jest와 비교가 자주 된다. Vitest가 Jest 위에 구축되어서 더 현대적이고 개선된 버전이기 때문이다. 또한 대부분의 Jest API 및 생태계 라이브러리와 호환되고, 공식 가이드에 따라 마이그레이션하기 간단하다.

 

마이그레이션

기존의 시스템, 소프트웨어, 데이터 등을 새로운 버전이나 환경으로 이전하는 과정

 

Mocha나 Jasmine과 같은 널리 사용되는 다른 테스트 프레임워크와 마찬가지로, Vitest도 간단한 `describe-it-assert` 또는`describe-it-expect` 패턴을 따른다. 하지만 Vitest를 사용하는 장점은 이것을 빠르게 설정할 수 있으며 별도의 assertion library 설치가 필요하지 않다는 점이다.

 

특히 Vitest를 사용하는 가장 편리한 장점 중 하나는 Jest + Babel 기반 프로젝트와 비교했을 때, 최소한의 구성만 필요하기 때문이다. dev, build 및 test 환경의 구성을 vite.config.js 파일 하나에 단일 파이프라인으로 정의할 수 있다.

 

간단한 예시로 React 앱에 Jest와 Babel 환경을 설정하는 경우, 보통 CRA(Create React App)와 함께 제공되는 패키지 외에 추가 패키지를 설치해야 된다.

 

  • babel-jest
  • @babel/core
  • @babel/preset-env
  • @babel/preset-react
  • @babel/preset-typescript
  • @types/jest

 

그 후에 `jest.config.js``babel.config.js` 파일을 작성하여 구성을 완료해야 한다. 그러나 Vitest를 사용하면 이러한 추가 종속성을 모두 설치할 필요가 없으며, `vite.config.js` 또는 `vitest.config.js` 파일만 있으면 된다. 비-Vite 프로젝트의 경우에도 구성을 단일 파일로 설정할 수 있다.

import { defineConfig } from 'vitest/config';

export default defineConfig({
    test: {
        environment: 'jsdom',
    },
});

 

  장점 단점
Jest - React, Vue, Babel 기반 프로젝트 호환 가능
- 성능이 좋음
- 비동기 코드 및 다양한 다양한 테스트 유형 지원
- 확장된 API, 필요한 경우 선택적으로 포함 가능

- 쉬운 테스트를 위해 가져온 모든 라이브러리 자동 모킹
- 가장 인기 있으며 활발한 커뮤니티 지원
- 자동 모킹은 성능 저하
- 제3자 라이브러리나 도구와 함께 사용할 경우 지원되지 않을 수도 있음
- 설정이 복잡할 수 있음

- 많은 종속성이 필요할 수 있음 (예: Babel 등)
Mocha - 간단하고 가벼우며 시작하기 쉬움
- 유연하여 구성하고 다양한 라이브러리를 포함하기 쉬움
- 활발한 커뮤니티와 지원이 있음
- 다양하지 않은 내장 기능
- 다른 라이브러리 별도 설치 필요
- 서로 다른 종속성이 다른 요구 사항을 가질 수 있어 구성이 어려울 수 있음
- 자동 모킹이나 스냅샷 테스팅을 포함하기 복잡함
Jasmine - 매우 유연하며 대부분의 프레임워크와 라이브러리와 호환됨
- 내장된 assertion 라이브러리
- 추가 라이브러리가 필요하지 않음

- 읽기 쉬운 깔끔한 구문
- 비동기 코드 테스트가 어려울 수 있음
- 최신 테스트 프레임워크보다는 사용 빈도가 낮음
Vitest ⭐️ - 원시 ESM(ES Module) 및 기본 TypeScript 지원
- 멀티스레드 지원 및 가벼움
- 최소한의 구성 및 설정이 필요
- JEST API 기반으로 구축되어 모든 Jest 테스트 기능 포함
- 성능이 빠름. 라이브 리로드가 Jest보다 빠름
- Vite 프로젝트와 높은 호환성을 가짐
- 아직 초기 단계이며, 활발한 커뮤니티 지원이 없을 수 있음
- Jest와 달리 전역 모듈을 사용하지 않고 각 테스트를 격리하여 실행하므로 확장 시 성능에 영향을 미칠 수 있음
 

 

Vitest 용 VS Code 확장

테스트 속도를 높이고 디버깅을 더 쉽게 하기 위해 VS Code에서 확장할 수 있다! 추후에 적용해봐야겠다.

  • 상태 별로 테스트 필터링
  • 쉬운 디버깅
  • 콘솔 출력 검사
  • 빠른 테스트 재실행

 

Test Coverage Report 테스트 커버리지 보고서

2월에 나온 Vitest 기사 중에 테스트 커버리지 명령어를 `c8`로 쓰는 걸 확인할 수 있다. 그걸 보고 따라하던 찰나, warning message가 쏟아지기 시작함 🧐 오류를 피하고 싶다면 나를 따라하길 바란다..!

 

 

Coverage Setup

당연히 테스트할 수 있는 테스트 코드는 선행되어야한다! 그리고 `package.json`에 coverage 세팅을 해준다.

 

커버리지 명령어를 통해 모든 프로젝트 코드가 테스트 되었는지 확인할 수 있다.

 

2023.08월 기준) 기본적으로 Vitest는 v8패키지를 사용하여 적용 범위 보고서를 실행한다. 02월 아티클에는 c8을 쓴다. c8을 선언하면 더이상 권장되지 않는다고 뜬다. 추가적으로 ` istanbul`의 경우`test.coverage.provider`로 설정하여 커버리지 도구를 선택할 수 있다고 한다.

자세한 것은 [Vitest 공식 홈페이지]를 보면 된다.

# For v8
pnpm i -D @vitest/coverage-v8

v8, istanbul 둘다수동으로 설치 가능하다.

첫 시도때엔 c8로 선언했더니 오류가 떴다. 자동으로 v8로 안내하는 메세지가 뜬다.

 

"기본 코드 커버리지 제공자가 "c8"에서 "v8"로 변경되었습니다. 새로운 패키지를 설치해야 합니다. 이전에 사용되던 더 이상 권장되지 않는 코드 커버리지 제공자를 사용하려면 "--coverage.provider c8" 옵션을 사용하십시오."

 

커버리지가 잘 뜨는 것을 확인할 수 있다.

 

결론

Vitest는 매우 빠르게 변한다.

다른 테스트 프레임워크보다 훨씬 좋은 것은 알겠으나 커뮤니티를 통해 얻을 수 있는 정보가 적은 게 큰 단점이다. 하지만 그만큼 Vitest에서 친절하게 안내해준다. Jest를 쓰던 사람도 충분히 빠르게 마이그레이션 할 수 있을 것이다. Vitest의 정보는 적기때문에(특히 국내) 관련한 포스팅은 계속 쓸 예정!

 


[Vitest: Blazing Fast Unit Test Framework]

[Vitest 공식홈페이지]

[Jest에서 Vitest로]

[JavaScript class 예제 + 퀴즈로 Vitest 테스트 코드 적용하기]