4.Node.js/Vitest&TypeBox

[Vitest] 8편. Vitest + V8 기반 테스트 커버리지 : @vitest/coverage-v8

쿼드큐브 2026. 2. 11. 08:53
반응형
반응형

 

8편. Vitest + V8 기반 테스트 커버리지 : @vitest/coverage-v8

 

📚 목차
1. 테스트 커버리지는 왜 중요한가?
2. Vitest에서 커버리지 활성화하기: @vitest/coverage-v8 설정
3. 커버리지 리포트 실습 및 예제
4. 커버리지 리포트 확인

 

📂 [GitHub 예시 코드 보러가기] (https://github.com/cericube/nodejs-tutorials) /vitest

 

1. 테스트 커버리지는 왜 중요한가?

1. 테스트 커버리지란?

테스트 커버리지(Test Coverage)는 전체 애플리케이션 코드 중 테스트로 실행된 코드의 비율을 의미합니다.

즉, 작성된 테스트 코드가 실제 애플리케이션의 어느 부분까지 영향을 미치고 있는지를 정량적으로 측정하는 지표입니다.

 

2. 주요 커버리지 지표 및 필요한 이유

지표 설명
Statements 전체 코드 중 실행된 명령문(statement)의 비율
Branches if, else, switch, ? : 등의 조건 분기문 중 테스트된 경로 비율
Functions 정의된 함수 또는 메서드가 호출된 비율
Lines 전체 코드 라인 중 테스트를 거친 물리적 코드 줄 수의 비율

▸ QA 및 유지보수: 테스트되지 않은 '사각지대'를 찾아내어 잠재적인 버그를 예방합니다.
▸ 리팩토링 자신감: 높은 커버리지는 코드 구조를 변경할 때 기존 로직이 망가지지 않았음을 보장하는 안전장치가 됩니다.
▸ 협업 기준: 팀 내 최소 커버리지 기준(Threshold)을 정해 코드 품질을 상향 평준화할 수 있습니다.

 

📌 실무 포인트: 커버리지는 100%가 목표가 아닙니다

커버리지는 100%일 필요는 없습니다.
하지만 핵심 로직은 높은 커버리지를 유지해야 안정적인 서비스 운영이 가능합니다.

테스트 커버리지를 무조건 100%로 맞추는 것은 비효율적일 수 있습니다.
테스트하기 어려운 코드 (예: 로깅, 예외 메시지 등)까지 억지로 테스트하려 하면 오히려 코드 품질이 낮아지거나 테스트 유지 비용이 높아집니다.

 

2. Vitest에서 커버리지 활성화하기: @vitest/coverage-v8 설정

📌V8 기반 커버리지란?

Vitest는 내부적으로 V8 JavaScript 엔진의 native coverage API 를 사용하여 빠르고 정확한 커버리지를 수집할 수 있습니다.
이를 위해 별도 패키지인 @vitest/coverage-v8이 필요합니다.

 

1. @vitest/coverage-v8 패키지 설치

cd nodejs-tutorials\vitest
npm install -D @vitest/coverage-v8

== 설치 결과 ==
PS D:\NodejsDevelope\workspace\nodejs-tutorials\vitest> npm list
nodejs-tutorials@1.0.0 D:\NodejsDevelope\workspace\nodejs-tutorials
└─┬ vitest-study@1.0.0 -> .\vitest
  ├── @vitest/coverage-v8@4.0.17
  ├── @vitest/ui@4.0.16
  ├── axios@1.13.2
  ├── fastify@5.6.2
  ├── msw@2.12.7
  ├── undici@7.18.2
  └── vitest@4.0.16

 

2. vitest.config.ts 설정

프로젝트 루트의 설정 파일에서 커버리지 옵션을 활성화합니다.

coverage: {
  enabled: true, // 테스트 실행 시 커버리지 수집을 활성화합니다.
  provider: 'v8', // V8 엔진의 native coverage API를 사용하는 공급자 (빠르고 정확함)
  reporter: ['text', 'json', 'html'], // 생성할 커버리지 리포트 형식
  reportsDirectory: './coverage', // 커버리지 리포트가 저장될 디렉터리 경로

  // 커버리지 측정 대상 파일을 지정합니다.
  // 보통 테스트 대상 소스코드를 포함하는 경로를 지정합니다.
  include: ['src/**/*.ts'],

  // 커버리지에서 제외할 파일 목록입니다.
  // 진입점(main.ts), 타입 정의(d.ts), 테스트 파일 등은 일반적으로 제외합니다.
  exclude: [
    'src/main.ts',       // 앱의 엔트리포인트 (Fastify/Express 부트스트랩 코드 등)
    '**/*.d.ts',         // 타입 선언 파일은 실행 코드가 아니므로 제외
    'src/types/**',      // 공용 타입 모음 디렉토리
    '**/*.test.ts',      // 테스트 자체는 커버리지 측정 대상이 아님
  ],

  // 테스트 실패 조건으로 사용할 최소 커버리지 기준을 설정합니다.
  // 기준 미달 시 CI/CD 파이프라인에서 실패로 처리할 수 있습니다.
  thresholds: {
    lines: 80,        // 전체 코드 줄 수 기준 80% 이상 실행되어야 통과
    functions: 80,    // 함수 기준 80% 이상 호출
    branches: 70,     // 조건 분기 기준 70% 이상 테스트 커버
    statements: 80,   // 실행 가능한 명령문 기준 80% 이상 커버
  },
},
반응형

 

3. 커버리지 리포트 실습 및 예제

1. 커버리지 실행 명령어

npx vitest --coverage

▸--coverage: 커버리지 수집 기능을 활성화합니다. vitest.config.ts의 coverage 설정을 기반으로 동작합니다.

옵션 설명
--coverage 커버리지를 수집합니다. (필수)
--run watch 모드를 비활성화하고 테스트를 한 번만 실행합니다. 커버리지 사용 시 필요
--dir [경로] 테스트 실행의 루트 디렉터리를 지정합니다. (기본: 현재 디렉터리)
--reporter [형식] 테스트 결과 출력 형식을 지정합니다 (default, verbose, dot, json 등)
--config [파일명] 커스텀 vitest 설정 파일을 사용할 때 사용 (vitest.config.custom.ts 등)
--testNamePattern [정규식] 특정 테스트 이름에 해당하는 케이스만 실행합니다
--passWithNoTests 실행할 테스트가 없더라도 오류 없이 통과시킵니다 (CI에서 유용)

 

예시)

1. 특정 테스트 파일만 실행하면서 커버리지 수집
npx vitest run src/services/user.service.test.ts --coverage

2. 특정 테스트 이름만 실행
npx vitest run --coverage --testNamePattern="should create user"

3. 커스텀 설정 파일 사용
npx vitest run --coverage --config=vitest.coverage.config.ts

 

2. 실습용 예시 코드

// src/ch08/user.service.ts
export interface User {
  id: number;
  name: string;
  age: number;
}

// 사용자의 나이를 검증하는 함수입니다.
export const validateUserAge = (user: User): boolean => {
  if (user.age < 0) {
    // 음수 나이는 유효하지 않으므로 예외 처리합니다.
    throw new Error("Age cannot be negative");
  }
  // 만 19세 이상이면 true, 그렇지 않으면 false를 반환합니다.
  return user.age >= 19;
}

✔️ Statements (총 4개)
▸ 함수 선언 → const validateUserAge = (user) => {...}

▸ 조건문 평가 → if (user.age < 0)

▸ 예외 처리 → throw new Error(...)

▸ 반환 → return user.age >= 19

 

✔️ Branches (총 2개)
▸ 조건문: if (user.age < 0)
    → 이 조건은 true일 수도 있고 false일 수도 있음
    → 각각 하나의 분기로 계산됨

 

✔️ Functions (총 1개)
▸ validateUserAge() 함수 하나만 존재

 

✔️ Lines (총 4줄)

▸ export const validateUserAge = ... → 함수 정의
▸ if (user.age < 0)
▸ throw new Error(...)
▸ return user.age >= 19

// tests/ch08/user.service.test.ts
import { describe, it, expect } from 'vitest';
import { validateUserAge } from '../../src/ch08/user.service';

describe('validateUserAge', () => {
  it('성인인 경우 true를 반환해야 한다', () => {
    const user = { id: 1, name: 'Alice', age: 20 };
    expect(validateUserAge(user)).toBe(true);
  });

  // 누락된 케이스: 미성년자(false 반환), 음수 나이(에러 발생)
});

 

3. 테스트 범위(커버리지 대상) 제한하기

실습 단위로 커버리지를 확인할 때는 전체 프로젝트를 대상으로 하기보다 특정 폴더만 포함하는 것이 좋습니다.

// vitest.config.ts
coverage: {
  provider: 'v8',
  include: ['src/ch08/**/*.ts'], // ✅ ch08 하위 소스만 커버리지 대상으로 설정
  exclude: ['**/*.test.ts', '**/*.d.ts'],
  reporter: ['text', 'html'],
}

 

4. 커버리지 결과 해석

npx vitest .\tests\ch08/user.service.test.ts --coverage

커버리지 측정 결과 화면
커버리지 측정 결과 화면

테스트를 실행하면 Branch 커버리지가 100%가 되지 않습니다.

if (user.age < 0) 조건의 참인 경우와 성인이 아닌 경우를 테스트하지 않았기 때문입니다.

if (user.age < 0) { ... }
return user.age >= 19;

> user.age < 0 인 경우 (예외 발생)
> user.age >= 19 인 경우 (true)
> user.age < 19 인 경우 (false)

총 3개의 논리 분기 중 현재 테스트는 true 분기만 실행하고 있기 때문입니다.
즉, 커버리지는 단순히 “라인이 실행됐는지”가 아니라 모든 조건 분기가 실행됐는지를 기준으로 측정됩니다.

 

5. 누락된 테스트 보완 → Branch 100% 달성

it('미성년자인 경우 false를 반환해야 한다', () => {
  const user = { id: 2, name: 'Bob', age: 15 };
  expect(validateUserAge(user)).toBe(false);
});

it('나이가 음수면 에러를 던져야 한다', () => {
  const user = { id: 3, name: 'Charlie', age: -1 };
  expect(() => validateUserAge(user)).toThrow("Age cannot be negative");
});

 

이제 모든 분기(Branch)가 실행되어 100% 커버리지를 달성할 수 있습니다.

보완된 테스트 커버리지 결과
보완된 테스트 커버리지 결과

 

4. 커버리지 리포트 확인

Vitest에서는 커버리지 리포트를 여러 형식으로 출력할 수 있으며, 결과 파일이 어디에 저장될지도 직접 지정할 수 있습니다.

이 기능은 특히 로컬 디버깅, CI 리포트 확인, 시각적 분석 등 다양한 상황에서 유용하게 활용됩니다.

// vitest.config.ts
coverage: {
  reporter: ['text', 'json', 'html'],     // 리포트 출력 형식 지정
  reportsDirectory: './coverage',         // 리포트 저장 경로 지정
}
옵션 설명
'text' 터미널에 요약 형태의 커버리지 결과를 출력합니다. 빠르게 수치를 확인할 때 유용합니다.
'json' coverage-final.json 형태로 리포트를 생성하며, Codecov나 Coveralls 같은 외부 리포팅 툴에 활용할 수 있습니다.
'html' 시각적으로 분석 가능한 HTML 리포트를 생성합니다. 브라우저에서 열어 누락된 테스트 라인을 한눈에 확인할 수 있습니다.

 

💡html 리포트 화면 예시

html 리포트 화면
html 리포트 화면
html 리포트 화면
html 리포트 화면


※ 게시된 글 및 이미지 중 일부는 AI 도구의 도움을 받아 생성되거나 다듬어졌습니다.

반응형

 

반응형