자바스크립트 교재 · 26편 / 26편 ★완결

테스트 작성 — 첫 단위 테스트

시리즈 마지막. 코드가 안전하게 자라는 방법.

고급읽는 시간 8분2026-05-17
JS 테스트가 코드의 안전망 역할을 하는 일러스트

26편의 마지막. 테스트는 "코드가 의도대로 동작하는지 자동으로 확인하는 코드". 처음에는 귀찮지만 코드가 커지는 순간부터 테스트 없는 게 더 귀찮아집니다. 26편은 vitest/jest 의 첫 단위 테스트와 mock·CI 연결까지.

왜 테스트 — 5가지 이유

  • 회귀 방지 — 한 번 잡은 버그가 다시 안 나는 약속.
  • 리팩터링 안전망 — 코드 구조 바꿔도 동작 동일.
  • 문서 — "이 함수가 어떻게 동작하나" 의 실행 가능한 설명.
  • 설계 압박 — 테스트하기 좋은 코드 = 보통 좋은 설계.
  • 마음 평화 — 배포 후 잠 자기.

vitest vs jest — 2026 선택지

vitestjest
속도매우 빠름 (esbuild 기반)보통
ESM기본 지원설정 필요
TS기본 지원ts-jest·@swc/jest 필요
APIjest 호환원조
생태계신생 (성장 중)광범위

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 ✅

© 2026 주나이테크(주) @JUNAITECH