테스트 작성 — 첫 단위 테스트
시리즈 마지막. 코드가 안전하게 자라는 방법.
26편의 마지막. 테스트는 "코드가 의도대로 동작하는지 자동으로 확인하는 코드". 처음에는 귀찮지만 코드가 커지는 순간부터 테스트 없는 게 더 귀찮아집니다. 26편은 vitest/jest 의 첫 단위 테스트와 mock·CI 연결까지.
왜 테스트 — 5가지 이유
- 회귀 방지 — 한 번 잡은 버그가 다시 안 나는 약속.
- 리팩터링 안전망 — 코드 구조 바꿔도 동작 동일.
- 문서 — "이 함수가 어떻게 동작하나" 의 실행 가능한 설명.
- 설계 압박 — 테스트하기 좋은 코드 = 보통 좋은 설계.
- 마음 평화 — 배포 후 잠 자기.
vitest vs jest — 2026 선택지
| vitest | jest | |
|---|---|---|
| 속도 | 매우 빠름 (esbuild 기반) | 보통 |
| ESM | 기본 지원 | 설정 필요 |
| TS | 기본 지원 | ts-jest·@swc/jest 필요 |
| API | jest 호환 | 원조 |
| 생태계 | 신생 (성장 중) | 광범위 |
2026 추천. 새 프로젝트는 vitest — 빠르고 ESM/TS 기본. 기존 jest 프로젝트는 그대로 유지. API 가 거의 호환이라 학습은 한 번이면 둘 다.
설치 — vitest
npm install -D vitest
# package.json
{
"scripts": {
"test": "vitest",
"test:run": "vitest run",
"test:cov": "vitest run --coverage"
}
}
첫 테스트 — describe / it / expect
// src/math.js
export function add(a, b) {
return a + b;
}
// src/math.test.js
import { describe, it, expect } from "vitest";
import { add } from "./math.js";
describe("add()", () => {
it("두 양수의 합", () => {
expect(add(1, 2)).toBe(3);
});
it("음수 포함", () => {
expect(add(-5, 3)).toBe(-2);
});
it("0 더하기", () => {
expect(add(0, 7)).toBe(7);
});
});
npx vitest
# 출력
# ✓ add() 두 양수의 합
# ✓ add() 음수 포함
# ✓ add() 0 더하기
# 3 passed
자주 쓰는 매처(matcher)
// 동등성
expect(x).toBe(y); // === (primitive)
expect(obj).toEqual({ a: 1 }); // 깊은 비교 (객체·배열)
expect(obj).toStrictEqual({ a: 1 }); // undefined 차이도 검사
// 진실성
expect(x).toBeTruthy();
expect(x).toBeFalsy();
expect(x).toBeNull();
expect(x).toBeUndefined();
expect(x).toBeDefined();
// 숫자
expect(x).toBeGreaterThan(5);
expect(x).toBeLessThanOrEqual(10);
expect(0.1 + 0.2).toBeCloseTo(0.3); // 부동소수점 비교
// 문자열·배열
expect("hello").toContain("ll");
expect([1, 2, 3]).toContain(2);
expect("hello").toMatch(/^h/);
// 함수 호출 결과
expect(() => doStuff()).toThrow();
expect(() => divide(1, 0)).toThrow("0 으로");
// 비동기
await expect(fetchUser(1)).resolves.toMatchObject({ id: 1 });
await expect(fetchUser(999)).rejects.toThrow("not found");
setup/teardown — 각 테스트 전후
import { beforeEach, afterEach } from "vitest";
let db;
beforeEach(() => {
db = new Database();
db.seed();
});
afterEach(() => {
db.close();
});
it("...", () => { ... });
mock — 외부 의존 격리
import { vi, it, expect } from "vitest";
// 함수 mock
const fn = vi.fn();
fn("a", 1);
expect(fn).toHaveBeenCalledWith("a", 1);
expect(fn).toHaveBeenCalledTimes(1);
// 반환값 설정
fn.mockReturnValue(42);
fn(); // 42
// 모듈 mock
vi.mock("./api.js", () => ({
fetchUser: vi.fn(async (id) => ({ id, name: "테스트" })),
}));
// 부분 mock (실제 함수 일부만)
vi.mock("./api.js", async () => {
const actual = await vi.importActual("./api.js");
return {
...actual,
fetchUser: vi.fn(),
};
});
비동기 테스트
it("loadUser — 성공", async () => {
const user = await loadUser(1);
expect(user.name).toBe("준성");
});
it("loadUser — 404", async () => {
await expect(loadUser(999)).rejects.toThrow("not found");
});
// 타이머 mock
import { vi } from "vitest";
it("debounce", () => {
vi.useFakeTimers();
const fn = vi.fn();
const debounced = debounce(fn, 500);
debounced();
debounced();
vi.advanceTimersByTime(600);
expect(fn).toHaveBeenCalledTimes(1);
});
커버리지
npm install -D @vitest/coverage-v8
# vitest.config.js
export default {
test: {
coverage: {
reporter: ["text", "html"],
exclude: ["node_modules", "dist", "**/*.test.js"],
thresholds: { lines: 80, functions: 80 },
}
}
};
npx vitest run --coverage
# 결과 — 라인·브랜치·함수별 % 와 미커버 영역
커버리지 함정. 100% 가 항상 좋은 건 아님. 무의미한 테스트로 라인만 채울 수 있음. 커버리지보다 시나리오 다양성(엣지 케이스·실패 경로) 이 중요. 70-80% 가 보통 좋은 균형.
CI 연결 — GitHub Actions
# .github/workflows/test.yml
name: Test
on: [push, pull_request]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 22
cache: npm
- run: npm ci
- run: npm run test:run
- run: npm run typecheck # TS 라면 (19편)
테스트 종류 — 한 표
| 레벨 | 대상 | 비율 |
|---|---|---|
| unit | 함수·클래스 하나 | 가장 많이 (70%) |
| integration | 여러 모듈 같이 (DB·API) | 중간 (20%) |
| e2e | 실제 브라우저·전체 흐름 | 적음 (10%) |
"테스트 피라미드" — 빠른 unit 이 많고, 느리고 비싼 e2e 는 적게.
📚 26편 시리즈 회고 — 무엇을 익혔나
| 파트 | 핵심 |
|---|---|
| Part 1 입문(1-5) | 설치·console.log·변수·자료형·연산자 |
| Part 2 기초(6-13) | 조건·반복·함수·배열·객체·문자열·스코프·에러 |
| Part 3 중급(14-20) | DOM·비동기·Promise·fetch·class·모듈·정규식 |
| Part 4 고급(21-26) | 클로저·this·프로토타입·이터레이터·패턴·테스트 |
다음은 어디로? 실제 프로젝트. ① 작은 토이 — TODO·메모·블로그 만들면서 1~13편 복습. ② 프레임워크 — React/Svelte/Vue 로 14~20편 실전. ③ 본격 — TS(20편 시리즈 별도)·테스트·CI 까지 완비된 코드베이스. JS 는 거기서 끝없이 깊어집니다 — 평생 학습.
마지막 한 줄
"코드는 글이다." 처음 익힐 때는 문법이 전부지만, 진짜 실력은 "다음 사람이 6개월 뒤 읽기 좋게 쓰는 것". 26편의 모든 도구는 결국 그 목적을 위한 수단입니다.
🎓 자바스크립트 26편 시리즈 완결
console.log 한 줄로 시작해 테스트·CI 까지. 다음은 진짜 프로젝트로 손에 익히기. 6개월 뒤 1편을 다시 보면 — 그때 이해 못한 모든 게 명확할 거예요.
이전: 25편 디자인 패턴 · 현재: 26편 (졸업작 ★) · 진행: 26/26 ✅