Logo

Vitest로 테스트 그룹화 및 실행 제어하기

길거나 복잡한 코드에 대한 테스트를 작성하다보면 테스트 코드의 양도 비례해서 늘어나게 됩니다. 하나의 파일 안에서 많은 수의 테스트가 있으면 도대체 어느 테스트가 어떤 코드를 위한 것인지 빠르게 파악하기가 어렵습니다. 게다가 디버깅을 하다보면 특정 테스트만 실행하거나 특정 테스트만 제외하고 테스트를 해야하는 경우도 생기죠.

이번 포스팅에서는 이러한 상황을 효과적으로 대처하기 위해서 Vitest로 어떻게 테스트를 그룹화하고 실행 제어를 할 수 있는지 알아보려고 합니다.

테스트 케이스와 스위트

테스트를 그룹화할 때 자주 사용되는 용어가 있어서 정리를 하고 넘어가겠습니다. 보통 각각의 테스트 함수를 테스트 케이스(test case)라고 하고, 서로 관련이 있는 여러 개의 테스트를 묶어주는 논리적인 단위를 테스트 스위트(test suite)라고 합니다.

테스트를 담고 있는 하나의 파일이 테스트 스위트가 될 수 있으며, 하나의 파일도 아래에서 배울 describe() 함수를 통해서 여러 개의 테스트 스위트로 분류할 수 있습니다. Vitest에서는 개별 테스트 케이스를 test() 함수 또는 이 함수의 별칭인 it() 함수로 표시합니다.

테스트 대상 코드

간단한 실습을 위해서, utils.ts 파일 안에 퍼센트, 날짜, 통화를 포멧팅(formatting)할 때 사용하는 유틸리티 함수를 작성해보겠습니다.

utils.ts
export function formatPercent(value: number) {
  return new Intl.NumberFormat('en', {
    style: 'percent',
  }).format(value);
}

export function formatDate(date: Date, locale: string = 'en') {
  return new Intl.DateTimeFormat(locale, {
    dateStyle: 'long',
  }).format(date);
}

export function formatCurrency(value: number, currency: string = 'USD') {
  return new Intl.NumberFormat('ko', {
    style: 'currency',
    currency,
  }).format(value);
}

위 코드에서는 자바스크립트의 Intl API가 사용되고 있습니다. 이 부분에 대해서는 별도 포스팅에서 자세히 다루고 있으니 참고 바랍니다.

테스트 작성 하기

이제 위에서 작성한 3개의 유틸리티 함수에 대한 테스트를 작성해보겠습니다. 3개의 함수 모두 문자열을 반환하므로 toBe() 매처(Matcher) 함수만 있으면 쉽게 반환값을 검증할 수 있을 것입니다.

utils.test.ts
import { expect, test } from 'vitest';
import { formatPercent, formatDate, formatCurrency } from './utils';

test('formatPercent()', () => {
  expect(formatPercent(0.7)).toBe('70%');
  expect(formatPercent(1 / 4)).toBe('25%');
});

test('formatDate() with en', () => {
  expect(formatDate(new Date('1995-12-17T03:24:00'))).toBe('December 17, 1995');
});

test('formatDate() with ko', () => {
  expect(formatDate(new Date('1995-12-17T03:24:00'), 'ko')).toBe(
    '1995년 12월 17일'
  );
});

test('formatDate() with zh', () => {
  expect(formatDate(new Date('1995-12-17T03:24:00'), 'zh')).toBe(
    '1995年12月17日'
  );
});

test('formatCurrency() with USD', () => {
  expect(formatCurrency(40.56)).toBe('US$40.56');
});

test('formatCurrency() with KRW', () => {
  expect(formatCurrency(50000, 'KRW')).toBe('₩50,000');
});

Vitest로 테스트를 실행해보면 6개의 테스트가 모두 통과하는 것을 볼 수 있습니다.

$ npx vitest run utils

 RUN  v1.4.0 /home/projects/vitest-describe

 ✓ src/utils.test.ts (5)
   ✓ formatPercent()
   ✓ formatDate() with en
   ✓ formatDate() with ko
   ✓ formatDate() with zh
   ✓ formatCurrency() with USD
   ✓ formatCurrency() with KRW

 Test Files  1 passed (1)
      Tests  6 passed (6)
   Start at  15:15:49
   Duration  1.50s (transform 89ms, setup 0ms, collect 58ms, tests 1ms, environment 0ms, prepare 236ms)

Vitest의 자주 쓰이는 매처 함수에 대해서는 별도 포스팅에 정리해놓았으니 참고 바랍니다.

describe(): 테스트 그룹화

utils.ts 파일에 지금의 3개의 함수 밖에 없지만 시간이 지나면 점점 더 많은 유틸리티 함수가 추가될 것입니다. 자연스럽게 utils.test.ts 파일 안에 테스트 케이스의 개수도 점점 늘어나겠지요.

Vitest에서 제공하는 describe() 함수와 it() 함수를 활용하면 서로 관련있는 테스트끼리 묶어 놓을 수 있습니다.

utils.test.ts
import { expect, describe, it } from "vitest";
import { formatPercent, formatDate, formatCurrency } from "./utils";

it("formatPercent()", () => {
  expect(formatPercent(0.7)).toBe("70%");
  expect(formatPercent(1 / 4)).toBe("25%");
});

describe("formatDate()", () => {
  it("en", () => {
    expect(formatDate(new Date("1995-12-17T03:24:00"))).toBe(
      "December 17, 1995"
    );
  });

  it("ko", () => {
    expect(formatDate(new Date("1995-12-17T03:24:00"), "ko")).toBe(
      "1995년 12월 17일"
    );
  });

  it("zh", () => {
    expect(formatDate(new Date("1995-12-17T03:24:00"), "zh")).toBe(
      "1995年12月17日"
    );
  });
});

describe("formatCurrency()", () => {
  it("USD", () => {
    expect(formatCurrency(40.56)).toBe("US$40.56");
  });

  it("KRW", () => {
    expect(formatCurrency(50000, "KRW")).toBe("₩50,000");
  });
});

테스트를 실행해보면 터미널에 출력되는 결과도 함수 별로 정리가 되어 있어서 보기가 좋다고 느끼실 것입니다.

$ npx vitest run utils

 RUN  v1.4.0 /home/projects/vitest-describe

 ✓ src/utils.test.ts (5)
   ✓ formatPercent()
   ✓ formatDate() (2)
     ✓ en
     ✓ ko
     ✓ zh
   ✓ formatCurrency() (2)
     ✓ USD
     ✓ KRW

 Test Files  1 passed (1)
      Tests  6 passed (6)
   Start at  15:17:23
   Duration  1.58s (transform 81ms, setup 0ms, collect 63ms, tests 4ms, environment 0ms, prepare 347ms)

여기서 it() 함수는 test() 함수와 완전히 동일한 역할, 즉 테스트 케이스를 나타냅니다. 기존 많이 사용되었던 테스트라이브러리인 Jest나 Mocha, Jasmin에서 describe()it()을 쌍으로 함께 사용하였기 때문에, Vitest에서도 익숙한 사용자 경험을 위해서 it()test() 함수의 별칭으로 제공해주고 있습니다.

todo(): 테스트 설명만 미리 작성

test.todo() 또는 it.todo() 함수를 사용하면 필요한 테스트 코드의 설명만 미리 써놓을 수 있습니다. 애플리케이션 코드보다 테스트 코드를 먼저 작성하는 테스트 주도 개발(Test-driven Development) 시 특히 유용하게 사용되는 기능인데요.

이렇게 todo() 함수로 미리 테스트가 필요한 부분을 적어두면, 나중에 기능 구현을 할 때 어떤 부분이 테스트되야하는지 상기할 수 있어서 좋습니다.

todo.test.ts
import { expect, describe, it, test } from "vitest";

describe("Example Test Suite", () => {
  it("one plus two is three", () => {
    expect(1 + 2).toBe(3);
  });

  it.todo("this test won't run", () => {
    expect(1 - 2).toBe(-100);
  });
});

test.todo("write test code later");

위 테스트 파일을 실행해보면, it.todo()test.todo()로 표시한 테스트 케이스가 실행이 되는지 않아도 터미널에 출력이 되는 것을 볼 수 있습니다.

$ npx vitest run todo

 RUN  v1.4.0 /home/projects/vitest-describe

 ✓ src/todo.test.ts (3)
   ✓ Example Test Suite (2)
     ✓ one plus two is three
     ↓ this test won't run [skipped]write test code later [skipped]

 Test Files  1 passed (1)
      Tests  1 passed | 2 todo (3)
   Start at  18:42:35
   Duration  1.56s (transform 38ms, setup 0ms, collect 28ms, tests 1ms, environment 0ms, prepare 111ms)

skip(): 테스트 제외하고 실행

skip()이 달려있는 테스트 케이스나 스위트는 Vitest가 테스트를 실행할 때 제외되게 됩니다. 특정 테스트 케이스에 문제가 생겼는데 바로 고칠 수 있는 여력이 없어서 일시적으로 예외시켜 놔야할 때 유용하게 활용할 수 있습니다. 대다수의 프로젝트에서 실패하는 테스트가 있는 경우 배포가 금지되기 때문입니다.

skip.test.ts
import { expect, test } from "vitest";

test("this test will be executed", () => {
  expect(1 + 1).toBe(2);
});

test.skip("this test will be skipped", () => {
  expect(1 + 1).toBe(3);
});
$ npx vitest run skip

 RUN  v1.4.0 /home/projects/vitest-describe

 ✓ src/skip.test.ts (2)
   ✓ this test will be executed
   ↓ this test will be skipped [skipped]

 Test Files  1 passed (1)
      Tests  1 passed | 1 skipped (2)
   Start at  18:45:56
   Duration  1.38s (transform 36ms, setup 0ms, collect 22ms, tests 1ms, environment 0ms, prepare 123ms)

only(): 특정 테스트만 실행

테스트 코드 작성 중에 특정 테스트 케이스에 문제가 생겨서 해당 테스트 케이스에만 실행하면서 추가 디버깅(debugging)을 하고 싶다면, 해당 스위트나 케이스를 only()로 표시해주면 됩니다.

only.test.ts
import { describe, expect, it } from "vitest";

describe("Example Test Suite", () => {
  it("this test will not be executed", () => {
    expect(1 + 1).toBe(2);
  });

  it.only("this test will be executed only", () => {
    expect(1 + 1).toBe(2);
  });
});
$ vitest run only

 RUN  v1.4.0 /home/projects/vitest-describe

 ✓ src/only.test.ts (2)
   ✓ Example Test Suite (2)
     ↓ this test will not be executed [skipped]
     ✓ this test will be executed only

 Test Files  1 passed (1)
      Tests  1 passed | 1 skipped (2)
   Start at  18:49:44
   Duration  1.56s (transform 33ms, setup 0ms, collect 22ms, tests 1ms, environment 0ms, prepare 117ms)

전체 코드

실습 프로젝트에서 작성한 테스트 코드는 아래에서 직접 확인하고 실행해볼 수 있습니다.

마치면서

지금까지 Vitest에서 제공하는 describe() 함수를 사용하여 관련있는 테스트를 효과적으로 그룹화해보았습니다. 또한 todo(), skip(), only() 함수를 사용하여 실행 여부를 유연하게 제어해보았습니다.

Vitest에 연관된 포스팅은 Vitest 태그를 통해서 쉽게 만나보세요!