메인 μ½˜ν…μΈ λ‘œ κ±΄λ„ˆλ›°κΈ°
Vitest둜 μž‘μ„±λœ ν…ŒμŠ€νŠΈλ₯Ό 효과적으둜 λ””λ²„κΉ…ν•˜λŠ” 방법을 λ‹€λ£Ήλ‹ˆλ‹€.

Vitest 디버깅 방법

SonamuλŠ” Vitestλ₯Ό ν…ŒμŠ€νŠΈ ν”„λ ˆμž„μ›Œν¬λ‘œ μ‚¬μš©ν•©λ‹ˆλ‹€. λ‹€μ–‘ν•œ 디버깅 방법을 μ œκ³΅ν•©λ‹ˆλ‹€.

VSCode 디버거 μ‚¬μš©

1. launch.json μ„€μ •

.vscode/launch.json:
{
  "version": "0.2.0",
  "configurations": [
    {
      "type": "node",
      "request": "launch",
      "name": "Debug Current Test File",
      "runtimeExecutable": "pnpm",
      "runtimeArgs": ["vitest", "run", "${file}"],
      "cwd": "${workspaceFolder}",
      "console": "integratedTerminal",
      "sourceMaps": true,
      "resolveSourceMapLocations": ["${workspaceFolder}/**", "!**/node_modules/**"]
    },
    {
      "type": "node",
      "request": "launch",
      "name": "Debug All Tests",
      "runtimeExecutable": "pnpm",
      "runtimeArgs": ["vitest", "run"],
      "cwd": "${workspaceFolder}",
      "console": "integratedTerminal",
      "sourceMaps": true
    }
  ]
}

2. 디버깅 μ‹€ν–‰

  1. ν…ŒμŠ€νŠΈ 파일 μ—΄κΈ°
    • λ””λ²„κΉ…ν•˜λ €λŠ” .test.ts 파일 μ—΄κΈ°
  2. 브레이크포인트 μ„€μ •
    • 쀄 번호 μ™Όμͺ½ ν΄λ¦­ν•˜μ—¬ λΉ¨κ°„ 점 ν‘œμ‹œ
  3. 디버거 μ‹œμž‘
    • F5 ν‚€ λ˜λŠ” β€œRun and Debug” νŒ¨λ„μ—μ„œ β€œDebug Current Test File” 선택
  4. 디버깅
    • λΈŒλ ˆμ΄ν¬ν¬μΈνŠΈμ—μ„œ λ©ˆμΆ”λ©΄ λ³€μˆ˜ 검사
    • F10 (Step Over), F11 (Step Into) λ“± μ‚¬μš©

μ½˜μ†” λ‘œκΉ…

console.log μ‚¬μš©

κ°€μž₯ κ°„λ‹¨ν•œ 디버깅 방법:
test("μ‚¬μš©μž 생성", async () => {
  const email = "test@example.com";

  console.log("ν…ŒμŠ€νŠΈ μ‹œμž‘:", email);

  const user = await UserModel.create({ email });
  console.log("μƒμ„±λœ μ‚¬μš©μž:", user);

  expect(user.email).toBe(email);
});

κ΅¬μ‘°ν™”λœ λ‘œκΉ…

test("λ³΅μž‘ν•œ 객체 λ‘œκΉ…", async () => {
  const data = {
    user: { id: 1, email: "test@example.com" },
    items: [1, 2, 3],
  };

  // 예쁘게 좜λ ₯
  console.log(JSON.stringify(data, null, 2));

  // λ˜λŠ”
  console.dir(data, { depth: null });
});

νŠΉμ • ν…ŒμŠ€νŠΈλ§Œ μ‹€ν–‰

test.only

// βœ… 이 ν…ŒμŠ€νŠΈλ§Œ μ‹€ν–‰
test.only("디버깅할 ν…ŒμŠ€νŠΈ", async () => {
  const result = await complexOperation();
  expect(result).toBeDefined();
});

// ❌ λ‹€λ₯Έ ν…ŒμŠ€νŠΈλŠ” κ±΄λ„ˆλœ€
test("κ±΄λ„ˆλ›Έ ν…ŒμŠ€νŠΈ", async () => {
  // ...
});

describe.only

// βœ… 이 λΈ”λ‘μ˜ ν…ŒμŠ€νŠΈλ“€λ§Œ μ‹€ν–‰
describe.only("User API", () => {
  test("μ‚¬μš©μž 생성", async () => {
    // ...
  });

  test("μ‚¬μš©μž 쑰회", async () => {
    // ...
  });
});

// ❌ λ‹€λ₯Έ describe 블둝은 κ±΄λ„ˆλœ€
describe("Order API", () => {
  test("μ£Όλ¬Έ 생성", async () => {
    // ...
  });
});

CLI둜 νŠΉμ • ν…ŒμŠ€νŠΈ μ‹€ν–‰

# νŠΉμ • 파일만
pnpm vitest run src/models/user.model.test.ts

# νŠΉμ • νŒ¨ν„΄
pnpm vitest run user

# νŠΉμ • ν…ŒμŠ€νŠΈ 이름
pnpm vitest run -t "μ‚¬μš©μž 생성"

Watch λͺ¨λ“œ

μžλ™ μž¬μ‹€ν–‰

# Watch λͺ¨λ“œλ‘œ μ‹€ν–‰
pnpm vitest watch

# λ˜λŠ”
pnpm test --watch
νŒŒμΌμ„ μˆ˜μ •ν•˜λ©΄ κ΄€λ ¨ ν…ŒμŠ€νŠΈκ°€ μžλ™μœΌλ‘œ μž¬μ‹€ν–‰λ©λ‹ˆλ‹€.

Watch λͺ¨λ“œ 단좕킀

ν…ŒμŠ€νŠΈ μ‹€ν–‰ 쀑:
  • a: λͺ¨λ“  ν…ŒμŠ€νŠΈ μž¬μ‹€ν–‰
  • f: μ‹€νŒ¨ν•œ ν…ŒμŠ€νŠΈλ§Œ μž¬μ‹€ν–‰
  • t: νŠΉμ • ν…ŒμŠ€νŠΈ ν•„ν„°
  • q: μ’…λ£Œ

UI λͺ¨λ“œ (vitest β€”ui)

μ‹€ν–‰

pnpm vitest --ui
λΈŒλΌμš°μ €μ—μ„œ http://localhost:51204/__vitest__/ μ—΄λ¦Ό κΈ°λŠ₯:
  • ν…ŒμŠ€νŠΈ 계측 ꡬ쑰 μ‹œκ°ν™”
  • μ‹€νŒ¨ν•œ ν…ŒμŠ€νŠΈ 필터링
  • κ°œλ³„ ν…ŒμŠ€νŠΈ μž¬μ‹€ν–‰
  • μ½”λ“œ 컀버리지 확인
  • μ½˜μ†” 좜λ ₯ 확인

μ—λŸ¬ 디버깅

μƒμ„Έν•œ μ—λŸ¬ λ©”μ‹œμ§€

test("μ—λŸ¬ λ©”μ‹œμ§€ κ°œμ„ ", async () => {
  const userId = 999;

  // ❌ κΈ°λ³Έ μ—λŸ¬
  expect(await UserModel.findById(userId)).toBeDefined();

  // βœ… μƒμ„Έν•œ μ—λŸ¬ λ©”μ‹œμ§€
  const user = await UserModel.findById(userId);
  expect(user, `μ‚¬μš©μž ID ${userId}λ₯Ό 찾을 수 μ—†μŒ`).toBeDefined();
});

try-catch둜 μ—λŸ¬ 확인

test("μ—λŸ¬ 상세 뢄석", async () => {
  try {
    await riskyOperation();
    expect.fail("μ—λŸ¬κ°€ λ°œμƒν•΄μ•Ό 함");
  } catch (error) {
    // μ—λŸ¬ 상세 확인
    console.log("μ—λŸ¬ νƒ€μž…:", error.constructor.name);
    console.log("μ—λŸ¬ λ©”μ‹œμ§€:", error.message);
    console.log("μŠ€νƒ 트레이슀:", error.stack);

    expect(error).toBeInstanceOf(BadRequestException);
  }
});

비동기 μ½”λ“œ 디버깅

async/await 확인

test("비동기 μž‘μ—… 디버깅", async () => {
  console.log("1. μ‹œμž‘");

  const promise1 = fetchUser(1);
  console.log("2. fetchUser 호좜");

  const promise2 = fetchOrders(1);
  console.log("3. fetchOrders 호좜");

  const [user, orders] = await Promise.all([promise1, promise2]);
  console.log("4. λͺ¨λ‘ μ™„λ£Œ:", { user, orders });

  expect(user).toBeDefined();
});

νƒ€μž„μ•„μ›ƒ 증가

test("느린 μž‘μ—…", async () => {
  // κΈ°λ³Έ 5μ΄ˆμ—μ„œ 30초둜 증가
  const result = await slowOperation();
  expect(result).toBeDefined();
}, 30000); // 30초 νƒ€μž„μ•„μ›ƒ

λͺ¨ν‚Ή 디버깅

Mock 호좜 확인

import { vi } from "vitest";

test("Mock 호좜 디버깅", async () => {
  const mockFn = vi.fn().mockResolvedValue({ id: 1 });

  await mockFn("test");
  await mockFn("test2");

  // Mock 호좜 확인
  console.log("호좜 횟수:", mockFn.mock.calls.length);
  console.log("첫 번째 호좜:", mockFn.mock.calls[0]);
  console.log("λͺ¨λ“  호좜:", mockFn.mock.calls);

  expect(mockFn).toHaveBeenCalledTimes(2);
  expect(mockFn).toHaveBeenCalledWith("test");
});

Mock 리셋 확인

beforeEach(() => {
  // 각 ν…ŒμŠ€νŠΈ 전에 Mock 리셋
  vi.clearAllMocks();
});

test("Mock μƒνƒœ 확인", () => {
  const mockFn = vi.fn();

  // 호좜 μ „
  console.log("호좜 μ „:", mockFn.mock.calls.length); // 0

  mockFn("test");

  // 호좜 ν›„
  console.log("호좜 ν›„:", mockFn.mock.calls.length); // 1
});

λ°μ΄ν„°λ² μ΄μŠ€ μƒνƒœ 확인

쿼리 λ‘œκΉ…

test("DB 쿼리 확인", async () => {
  // 쿼리 λ‘œκΉ… ν™œμ„±ν™” (개발 쀑)
  const result = await UserModel.getPuri("r")
    .table("users")
    .select("*")
    .where({ email: "test@example.com" })
    .debug() // 쿼리 좜λ ₯
    .getOne();

  console.log("κ²°κ³Ό:", result);
});

ν…ŒμŠ€νŠΈ ν›„ 데이터 확인

test("데이터 생성 확인", async () => {
  const email = "test@example.com";

  await UserModel.create({ email });

  // DBμ—μ„œ 직접 확인
  const users = await UserModel.getPuri("r").table("users").select("*").getMany();

  console.log("전체 μ‚¬μš©μž:", users);

  const created = users.find((u) => u.email === email);
  console.log("μƒμ„±λœ μ‚¬μš©μž:", created);

  expect(created).toBeDefined();
});

Coverage 디버깅

컀버리지 확인

# 컀버리지 생성
pnpm test --coverage

# HTML 리포트 μ—΄κΈ°
open coverage/index.html

μ»€λ²„λ˜μ§€ μ•Šμ€ μ½”λ“œ μ°ΎκΈ°

// src/models/user.model.ts
class UserModelClass extends BaseModelClass {
  async createUser(email: string) {
    if (!email) {
      // ❌ 이 λΆ„κΈ°κ°€ ν…ŒμŠ€νŠΈλ˜μ§€ μ•ŠμŒ
      throw new Error("Email required");
    }

    return this.save({ email });
  }
}

// ν…ŒμŠ€νŠΈ μΆ”κ°€ ν•„μš”
test("이메일 없이 생성 μ‹œ μ—λŸ¬", async () => {
  await expect(UserModel.createUser("")).rejects.toThrow("Email required");
});

Vitest μ„€μ • 디버깅

μ„€μ • 확인

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

export default defineConfig({
  test: {
    globals: true,
    include: ["src/**/*.test.ts"],

    // 디버깅 μ˜΅μ…˜
    bail: 1, // 첫 μ‹€νŒ¨ μ‹œ 쀑단
    reporters: ["verbose"], // 상세 좜λ ₯
    logHeapUsage: true, // λ©”λͺ¨λ¦¬ μ‚¬μš©λŸ‰ 좜λ ₯

    // νƒ€μž„μ•„μ›ƒ 증가
    testTimeout: 10000,
    hookTimeout: 10000,
  },
});

ν™˜κ²½ λ³€μˆ˜ 확인

test("ν™˜κ²½ λ³€μˆ˜ 디버깅", () => {
  console.log("NODE_ENV:", process.env.NODE_ENV);
  console.log("DB_HOST:", process.env.DB_HOST);
  console.log("λͺ¨λ“  ν™˜κ²½ λ³€μˆ˜:", process.env);
});

병렬 μ‹€ν–‰ 디버깅

격리 λͺ¨λ“œ λΉ„ν™œμ„±ν™”

일뢀 ν…ŒμŠ€νŠΈλŠ” 병렬 μ‹€ν–‰ μ‹œ λ¬Έμ œκ°€ λ°œμƒν•  수 μžˆμŠ΅λ‹ˆλ‹€:
// vitest.config.ts
export default defineConfig({
  test: {
    pool: "forks",
    maxWorkers: 1, // 단일 μ›Œμ»€
    isolate: false, // 격리 λΉ„ν™œμ„±ν™”
  },
});

순차 μ‹€ν–‰

# 순차 μ‹€ν–‰
pnpm vitest run --no-threads

μŠ€λƒ…μƒ· 디버깅

μŠ€λƒ…μƒ· μ—…λ°μ΄νŠΈ

# μŠ€λƒ…μƒ· μ—…λ°μ΄νŠΈ
pnpm vitest run -u

# νŠΉμ • 파일만
pnpm vitest run -u src/models/user.model.test.ts

μŠ€λƒ…μƒ· 확인

test("μŠ€λƒ…μƒ· 디버깅", () => {
  const data = {
    id: 1,
    name: "Test",
    timestamp: new Date().toISOString(),
  };

  // ❌ timestampκ°€ 계속 변경됨
  expect(data).toMatchSnapshot();

  // βœ… 동적 κ°’ μ œμ™Έ
  expect({
    ...data,
    timestamp: expect.any(String),
  }).toMatchSnapshot();
});

νƒ€μž… 체크 디버깅

νƒ€μž… μ—λŸ¬ 확인

# νƒ€μž… 체크만 μ‹€ν–‰
pnpm vitest typecheck

# λ˜λŠ”
pnpm tsc --noEmit

νƒ€μž… ν…ŒμŠ€νŠΈ

// src/**/*type-safety.test.ts
import { expectTypeOf } from "vitest";

test("νƒ€μž… μ•ˆμ „μ„± 확인", () => {
  const user = { id: 1, email: "test@example.com" };

  expectTypeOf(user.id).toEqualTypeOf<number>();
  expectTypeOf(user.email).toEqualTypeOf<string>();
});

ν…ŒμŠ€νŠΈ 격리 문제

beforeEach/afterEach 확인

describe("User Tests", () => {
  let testUser: User;

  beforeEach(async () => {
    console.log("Setup μ‹œμž‘");
    testUser = await UserModel.create({
      email: "test@example.com",
    });
    console.log("Setup μ™„λ£Œ:", testUser.id);
  });

  afterEach(async () => {
    console.log("Cleanup μ‹œμž‘");
    if (testUser) {
      await UserModel.del(testUser.id);
    }
    console.log("Cleanup μ™„λ£Œ");
  });

  test("ν…ŒμŠ€νŠΈ 1", async () => {
    expect(testUser).toBeDefined();
  });
});

λ©”λͺ¨λ¦¬ λˆ„μˆ˜ 디버깅

λ©”λͺ¨λ¦¬ μ‚¬μš©λŸ‰ 확인

// vitest.config.ts
export default defineConfig({
  test: {
    logHeapUsage: true, // λ©”λͺ¨λ¦¬ μ‚¬μš©λŸ‰ 좜λ ₯
  },
});

λ¦¬μ†ŒμŠ€ 정리 확인

test("λ¦¬μ†ŒμŠ€ 정리 확인", async () => {
  const db = createConnection();

  try {
    await db.query("SELECT 1");
  } finally {
    await db.destroy(); // λ°˜λ“œμ‹œ 정리
    console.log("DB μ—°κ²° 정리 μ™„λ£Œ");
  }
});

Best Practices

1. μž‘μ€ λ‹¨μœ„λ‘œ ν…ŒμŠ€νŠΈ

// ❌ λ„ˆλ¬΄ 큰 ν…ŒμŠ€νŠΈ
test("전체 μ‚¬μš©μž ν”Œλ‘œμš°", async () => {
  // 생성, 쑰회, μˆ˜μ •, μ‚­μ œ λͺ¨λ‘ 포함
});

// βœ… μž‘μ€ λ‹¨μœ„λ‘œ 뢄리
test("μ‚¬μš©μž 생성", async () => {});
test("μ‚¬μš©μž 쑰회", async () => {});
test("μ‚¬μš©μž μˆ˜μ •", async () => {});
test("μ‚¬μš©μž μ‚­μ œ", async () => {});

2. 의미 μžˆλŠ” ν…ŒμŠ€νŠΈ 이름

// ❌ 뢈λͺ…ν™•ν•œ 이름
test("test1", async () => {});

// βœ… λͺ…ν™•ν•œ 이름
test("이메일 없이 μ‚¬μš©μž 생성 μ‹œ BadRequestException λ°œμƒ", async () => {
  await expect(UserModel.create({ email: "" })).rejects.toThrow(BadRequestException);
});

3. AAA νŒ¨ν„΄ (Arrange-Act-Assert)

test("μ‚¬μš©μž 생성", async () => {
  // Arrange (μ€€λΉ„)
  const email = "test@example.com";
  const name = "Test User";

  // Act (μ‹€ν–‰)
  const user = await UserModel.create({ email, name });

  // Assert (검증)
  expect(user.email).toBe(email);
  expect(user.name).toBe(name);
});

4. μ‹€νŒ¨ μ‹œ μΆ©λΆ„ν•œ 정보

test("λ³΅μž‘ν•œ 쑰건 검증", async () => {
  const result = await complexCalculation();

  // βœ… μ‹€νŒ¨ μ‹œ 무엇이 잘λͺ»λ˜μ—ˆλŠ”μ§€ λͺ…ν™•νžˆ
  expect(result.total, `total이 ${result.expected}μ—¬μ•Ό ν•˜λŠ”λ° ${result.total}μž„`).toBe(
    result.expected,
  );
});

κ΄€λ ¨ λ¬Έμ„œ