Skip to main content

Command Palette

Search for a command to run...

Vitest로 TDD 개발 환경 구축하기

Published
•4 min read•View as Markdown

들어가며

테스트는 이제 “하면 좋은 것”이 아니라
안 하면 불안한 것이 되었습니다.

하지만 여전히 많은 개발자들이 이렇게 느낍니다.

  • TDD는 이상적인데, 현실에서는 너무 느린 것 같다

  • Jest 설정은 점점 복잡해진다

  • Rollup 라이브러리에서는 테스트를 어디까지 해야 할지 모르겠다

이 글에서는 이런 고민을 전제로,

Vitest를 이용해 “현실적으로 돌아가는 TDD 개발 환경”을 구축하는 방법

을 Rollup 기반 TypeScript 라이브러리 기준으로 정리합니다.


1. 요즘에도 TDD는 쓰일까?

결론부터 말하면:

TDD의 “철학”은 여전히 유효하지만,
순수한 형태의 TDD만 고집하는 팀은 줄어들고 있다.

과거의 TDD

  • Red → Green → Refactor를 엄격히 반복

  • 단위 테스트 중심

  • 개발자 개인의 설계 도구

현재의 TDD

  • 테스트를 설계의 기준으로 사용

  • 단위 테스트 + 통합 테스트를 상황에 맞게 조합

  • CI/CD와 결합된 안전망 역할

즉,
TDD는 사라진 것이 아니라 “확장된 테스트 전략의 일부”가 되었습니다.


2. TDD / BDD / ATDD, 무엇이 다른가?

테스트 기반 개발 방식은 하나가 아닙니다.

구분TDDBDDATDD
중심 질문이 코드는 어떻게 동작해야 하는가?사용자는 어떻게 행동하는가?언제 이 기능이 완료되었다고 말할 수 있는가?
관점코드/함수행동/시나리오요구사항/비즈니스
주 사용 주체개발자개발자 + 기획개발자 + 기획 + QA
주 사용 레벨단위 테스트통합/행위 테스트인수/릴리즈 기준

요즘 실무에서 가장 흔한 패턴은 다음과 같습니다.

핵심 로직 → TDD
유스케이스 → BDD 스타일 테스트
릴리즈 기준 → ATDD 일부 적용

이 글에서는 그중에서도
라이브러리 개발에 가장 적합한 TDD에 집중합니다.


3. 테스트 환경을 구축했을 때의 진짜 장점

테스트의 가장 큰 가치는
“버그를 잡는다”가 아닙니다.

1️⃣ 리팩토링이 가능해진다

  • 테스트는 안전망

  • 내부 구현을 바꿔도 외부 동작을 고정할 수 있음

2️⃣ 버그를 조기에 차단한다

  • 로컬/CI 단계에서 실패

  • 운영 장애 비용 급감

3️⃣ 코드가 문서가 된다

it('returns normalized user object', () => {
  expect(parseUser(input)).toEqual({...});
});

테스트는 실행 가능한 스펙입니다.

4️⃣ 협업과 리뷰가 쉬워진다

  • “이 코드의 의도가 무엇인지”를 테스트로 설명 가능

  • 리뷰가 구현이 아닌 설계 중심으로 이동


4. 왜 요즘은 Jest보다 Vitest인가?

Vitest가 선호되는 이유는 단순합니다.

✅ 빠르다

  • Vite 기반

  • watch 모드에서 즉각적인 피드백

✅ ESM/TypeScript 친화적

  • 별도의 복잡한 트랜스폼 설정이 거의 없음

  • Rollup/Vite 프로젝트와 설정 공유 가능

✅ TDD에 최적화된 개발 경험

  • vitest --watch 기본 루프

  • Jest와 거의 동일한 API (expect, mock, spy)

즉,

“요즘 프론트엔드 빌드 환경에 맞게 다시 설계된 테스트 러너”

가 Vitest입니다.


5. Rollup 라이브러리에서의 테스트 원칙

이 글의 가장 중요한 전제입니다.

❌ 잘못된 접근

  • dist 결과물을 테스트

  • Rollup 설정에 테스트 로직을 포함

✅ 올바른 접근

  • 테스트는 항상 src 기준

  • Rollup은 빌드 전용

  • 테스트와 빌드는 책임 분리

라이브러리 테스트의 목적은
“번들이 잘 나오는지”가 아니라
“공개 API가 의도대로 동작하는지”
입니다.


6. Vitest + Rollup 기반 TDD 환경 구축

6-1. 디렉토리 구조

my-lib/
├─ src/
│  ├─ index.ts
│  └─ sum.ts
├─ test/
│  └─ sum.test.ts
├─ rollup.config.mjs
├─ vitest.config.ts
├─ tsconfig.json
├─ tsconfig.build.json
├─ tsconfig.test.json
└─ package.json

6-2. TypeScript 설정 분리

tsconfig.build.json (빌드용)

{
  "extends": "./tsconfig.json",
  "compilerOptions": {
    "rootDir": "src",
    "declaration": true,
    "sourceMap": true
  },
  "include": ["src"]
}

tsconfig.test.json (테스트용)

{
  "extends": "./tsconfig.json",
  "compilerOptions": {
    "noEmit": true,
    "types": ["vitest/globals", "node"]
  },
  "include": ["src", "test"]
}

6-3. Vitest 설정

// vitest.config.ts
import { defineConfig } from "vitest/config";

export default defineConfig({
  test: {
    environment: "node",
    globals: true,
    include: ["test/**/*.test.ts"],
    clearMocks: true,
    restoreMocks: true
  }
});

6-4. Rollup 설정 (빌드 전용)

// rollup.config.mjs
import typescript from "@rollup/plugin-typescript";

export default {
  input: "src/index.ts",
  output: [
    { file: "dist/index.esm.js", format: "esm", sourcemap: true },
    { file: "dist/index.cjs.js", format: "cjs", sourcemap: true }
  ],
  plugins: [
    typescript({ tsconfig: "./tsconfig.build.json" })
  ]
};

6-5. package.json 스크립트

{
  "scripts": {
    "dev:test": "vitest",
    "test": "vitest run",
    "typecheck": "tsc -p tsconfig.test.json",
    "build": "rollup -c",
    "prepublishOnly": "npm run typecheck && npm run test && npm run build"
  }
}

이제 개발 중에는:

npm run dev:test

→ Red / Green / Refactor 루프가 즉시 돌아갑니다.


7. “진짜 TDD답게” 사용하는 운영 팁

  • watch 모드는 항상 켜두기

  • 테스트는 구현이 아닌 행동/계약을 고정

  • 외부 의존(시간, 랜덤, 네트워크)은 mock으로 통제

  • 커버리지는 목표가 아니라 빈 구간을 찾는 도구


마치며

Vitest로 TDD 환경을 구축한다는 것은
단순히 테스트 러너를 바꾸는 문제가 아닙니다.

“코드를 바꿔도 괜찮은 상태”를 만드는 일입니다.

Rollup 기반 라이브러리 개발에서
Vitest + TDD 조합은 다음을 가능하게 합니다.

  • 자신 있는 리팩토링

  • 안정적인 공개 API

  • CI/CD 자동화의 기반

  • 협업 가능한 코드베이스


🧾 작성 참고

이 글은 ChatGPT의 도움을 받아 내용을 정리하였습니다.

More from this blog

LangGraph의 astream_events: 에이전트 실행을 실시간으로 들여다보기

💡 한 줄 요약: astream_events()는 LangGraph 그래프 실행 중 발생하는 모든 이벤트(LLM 토큰, 툴 호출, 노드 시작/종료 등)를 실시간 스트림으로 받을 수 있는 비동기 API입니다. 들어가며 AI 에이전트가 응답을 생성하는 데 25초가 걸린다고 상상해보세요. 사용자는 평균 8초 후에 페이지를 이탈합니다. 하지만 스트리밍을 도입

Jun 20, 20266 min read

LLM은 어떻게 글을 읽고 쓰는가 — 입력부터 출력까지

대화형 AI에게 질문을 던지면, 마치 사람처럼 문장을 이해하고 답을 써 내려가는 것처럼 보인다.하지만 그 안에서 벌어지는 일은 생각보다 단순하고, 또 생각보다 기계적이다.이 글에서는 두 가지 질문에 답해 본다.LLM은 어떻게 글을 만들어내는가,그리고 우리가 입력한 프롬프트는 모델에 어떤 모습으로 들어가는가. 1부. LLM은 어떻게 글을 만들어내는가 핵심은

Jun 10, 20264 min read

useEffect 지옥이란 무엇이며, 어떻게 안전하게 사용하는가

React를 어느 정도 사용하다 보면 한 번쯤은“useEffect 지옥에 빠졌다”는 말을 듣거나 직접 느껴본 적이 있을 것이다. useEffect가 계속 늘어나고 의존성 배열은 점점 길어지고 왜 실행되는지 이해하기 어려워지며 eslint-disable-next-line 이 늘어나는 상태 하지만 많은 사람들이 오해한다.useEffect 지옥은 useEffect가 많아서 생기는 문제가 아니다. 이 글에서는 ‘useEffect 지옥’의 ...

Jan 12, 20263 min read

Zod란 무엇인가: TypeScript에서 런타임 검증과 타입 안정성을 동시에 해결하는 방법

TypeScript 프로젝트를 하다 보면 반드시 마주치는 문제가 있다.바로 “타입은 있는데, 데이터는 믿을 수 없다”는 것이다. TypeScript는 컴파일 타임에는 강력하지만,런타임에 들어오는 데이터에 대해서는 아무런 보장을 해주지 않는다. API 요청 데이터 사용자 입력(Form) 환경 변수 (process.env) 외부 JSON / 설정 파일 이 모든 것은 타입 시스템 바깥에서 들어온다. 이 문제를 해결하기 위해 등장한 라이브러...

Jan 10, 20263 min read

Flatpak을 이해하기 위한 배경 지식

배포판, 라이브러리 충돌, 그리고 OSTree까지 리눅스 데스크톱에서 Flatpak이 표준처럼 자리 잡은 데에는 분명한 이유가 있다.그 이유를 이해하려면 단순히 “Flatpak은 패키징 시스템이다”를 넘어서,리눅스 배포판 구조, 라이브러리 버전 충돌, OSTree라는 기술까지 함께 이해해야 한다. 이 글에서는 다음 질문에 답해본다. 리눅스에서 말하는 배포판이란 무엇인가? 라이브러리 버전 충돌은 왜 발생하는가? Flatpak은 이 문제를 어...

Jan 8, 20265 min read

Dev note

32 posts