Use Cases (Diperbarui: 2/6/2026)

Panduan TDD dengan Claude Code: Test-Driven Development memakai Vitest dan node:test

Praktik TDD dengan Claude Code lewat contoh Vitest, node:test, CI, hooks, dan template prompt yang siap dipakai.

Panduan TDD dengan Claude Code: Test-Driven Development memakai Vitest dan node:test

Claude Code bisa menulis kode dengan cepat, tetapi cepat belum tentu aman. Masalah biasanya muncul belakangan: edge case terlewat, perilaku lama rusak, diskon salah hitung, atau test baru gagal ketika masuk CI.

TDD, atau test-driven development, membantu mengendalikan kecepatan itu. Alurnya sederhana: tulis test yang gagal terlebih dahulu, buat implementasi paling kecil agar test lulus, lalu refactor tanpa mengubah perilaku. Siklus ini disebut Red-Green-Refactor. Red berarti gagal dengan alasan yang benar, Green berarti perilaku sudah lolos, Refactor berarti merapikan kode.

Claude Code cocok untuk workflow ini karena bisa menyusun kasus test, membaca output kegagalan, menulis implementasi kecil, dan menambahkan CI. Yang perlu dihindari adalah prompt “buat fiturnya” tanpa bukti test. Minta Claude Code menunjukkan Red dulu, baru lanjut ke implementasi.

Untuk pembaruan ini, dokumentasi resmi yang dicek adalah Claude Code hooks reference, Claude Code memory, Claude Code settings, Vitest Getting Started, Vitest CLI, dan Node.js test runner. Contoh hook di bawah membaca JSON dari stdin dan memakai tool_input.file_path, sesuai bentuk dokumentasi Claude Code saat ini.

Peran Claude Code dalam TDD

Claude Code dapat menangani daftar kasus test, interpretasi failure, implementasi minimal, CI, dan ringkasan risiko. Manusia tetap menentukan aturan bisnis, batas keamanan, kontrak API publik, dan keputusan rilis.

TahapTugas Claude CodeReview manusia
RedMenulis test yang gagal dari spesifikasiApakah ada requirement yang dikarang?
GreenImplementasi paling kecilApakah ada abstraksi berlebihan?
RefactorMerapikan nama dan duplikasiApakah perilaku tetap sama?
CIMenjalankan test di setiap PRApakah versi Node realistis?
OperasiMemakai hooks dan CLAUDE.mdApakah otomatisasi terlalu lambat?
flowchart LR
  A["Potong spesifikasi"] --> B["Red: test gagal"]
  B --> C["Green: implementasi minimal"]
  C --> D["Refactor: rapikan"]
  D --> E["CI dan hooks menjalankan ulang"]
  E --> B

Contoh 1: harga dan kupon dengan Vitest

Logika harga, kupon, dan langganan cocok untuk TDD karena kesalahan kecil bisa berdampak ke pendapatan.

npm install -D vitest
{
  "type": "module",
  "scripts": {
    "test": "vitest run",
    "test:watch": "vitest"
  },
  "devDependencies": {
    "vitest": "^3.0.0"
  }
}

Buat src/cart.test.ts terlebih dahulu.

import { describe, expect, it } from "vitest";
import { priceCart, ValidationError } from "./cart";

describe("priceCart", () => {
  it("calculates subtotal and total without a coupon", () => {
    const result = priceCart({
      items: [
        { sku: "book", unitPriceCents: 1200, quantity: 2 },
        { sku: "video", unitPriceCents: 3000, quantity: 1 },
      ],
    });

    expect(result).toEqual({
      subtotalCents: 5400,
      discountCents: 0,
      totalCents: 5400,
    });
  });

  it("applies a valid percent coupon", () => {
    const result = priceCart(
      {
        items: [{ sku: "course", unitPriceCents: 10000, quantity: 1 }],
        coupon: {
          code: "SPRING20",
          percentOff: 20,
          expiresAt: "2026-06-30T00:00:00.000Z",
        },
      },
      { now: new Date("2026-06-02T00:00:00.000Z") },
    );

    expect(result.totalCents).toBe(8000);
    expect(result.discountCents).toBe(2000);
  });

  it("rejects expired coupons", () => {
    expect(() =>
      priceCart(
        {
          items: [{ sku: "course", unitPriceCents: 10000, quantity: 1 }],
          coupon: {
            code: "OLD20",
            percentOff: 20,
            expiresAt: "2026-05-01T00:00:00.000Z",
          },
        },
        { now: new Date("2026-06-02T00:00:00.000Z") },
      ),
    ).toThrow(ValidationError);
  });

  it("rejects zero or negative quantity", () => {
    expect(() =>
      priceCart({
        items: [{ sku: "book", unitPriceCents: 1200, quantity: 0 }],
      }),
    ).toThrow("quantity must be positive");
  });
});

Minta Claude Code mengonfirmasi Red.

Kita berada di tahap Red. src/cart.test.ts sudah ada, tetapi src/cart.ts belum ada.

Tolong:
1. Jalankan npm test dan konfirmasi kegagalan.
2. Implementasikan hanya src/cart.ts minimum agar test lulus.
3. Jangan menambahkan UI, database, API eksternal, atau fitur masa depan.
4. Refactor hanya setelah test target Green.

Implementasi minimum untuk src/cart.ts.

export class ValidationError extends Error {
  constructor(message: string) {
    super(message);
    this.name = "ValidationError";
  }
}

type CartItem = {
  sku: string;
  unitPriceCents: number;
  quantity: number;
};

type Coupon = {
  code: string;
  percentOff: number;
  expiresAt: string;
};

type CartInput = {
  items: CartItem[];
  coupon?: Coupon;
};

type PriceOptions = {
  now?: Date;
};

export function priceCart(input: CartInput, options: PriceOptions = {}) {
  if (input.items.length === 0) {
    throw new ValidationError("cart must contain at least one item");
  }

  const subtotalCents = input.items.reduce((sum, item) => {
    if (!Number.isInteger(item.quantity) || item.quantity <= 0) {
      throw new ValidationError("quantity must be positive");
    }
    if (!Number.isInteger(item.unitPriceCents) || item.unitPriceCents < 0) {
      throw new ValidationError("unitPriceCents must be a non-negative integer");
    }
    return sum + item.unitPriceCents * item.quantity;
  }, 0);

  const discountCents = calculateDiscount(subtotalCents, input.coupon, options.now ?? new Date());

  return {
    subtotalCents,
    discountCents,
    totalCents: subtotalCents - discountCents,
  };
}

function calculateDiscount(subtotalCents: number, coupon: Coupon | undefined, now: Date) {
  if (!coupon) return 0;

  if (coupon.percentOff <= 0 || coupon.percentOff > 100) {
    throw new ValidationError("percentOff must be between 1 and 100");
  }

  if (new Date(coupon.expiresAt).getTime() < now.getTime()) {
    throw new ValidationError("coupon expired");
  }

  return Math.round(subtotalCents * (coupon.percentOff / 100));
}

Contoh 2: batas CLI dengan node:test

Untuk utilitas Node kecil, node:test bawaan sudah cukup. Simpan sebagai limit.test.mjs.

import test from "node:test";
import assert from "node:assert/strict";

export function parseLimit(value, fallback = 20) {
  if (value === undefined || value === "") return fallback;

  const parsed = Number(value);
  if (!Number.isInteger(parsed)) {
    throw new TypeError("limit must be an integer");
  }
  if (parsed < 1 || parsed > 100) {
    throw new RangeError("limit must be between 1 and 100");
  }

  return parsed;
}

test("parseLimit uses fallback when the value is empty", () => {
  assert.equal(parseLimit(undefined), 20);
  assert.equal(parseLimit("", 50), 50);
});

test("parseLimit accepts values from 1 to 100", () => {
  assert.equal(parseLimit("1"), 1);
  assert.equal(parseLimit("100"), 100);
});

test("parseLimit rejects decimals and out-of-range values", () => {
  assert.throws(() => parseLimit("1.5"), /integer/);
  assert.throws(() => parseLimit("0"), /between 1 and 100/);
  assert.throws(() => parseLimit("101"), /between 1 and 100/);
});
node --test limit.test.mjs

Contoh 3: bug API menjadi regression test

Bug produksi sebaiknya dikunci menjadi test yang gagal sebelum implementasi diubah.

Tambahkan regression test API dengan TDD.

Konteks:
- POST /checkout salah menerima kupon yang sudah kedaluwarsa.
- Kupon valid dan checkout tanpa kupon harus tetap berjalan.

Red:
- Tambahkan test yang mengharapkan 400 untuk kupon kedaluwarsa.
- Konfirmasi implementasi saat ini gagal.

Green:
- Buat perubahan API paling kecil agar lulus.

Refactor:
- Ekstrak hanya perbandingan tanggal yang duplikatif.

Kembalikan:
- Nama test, output gagal, file yang berubah, perintah, risiko tersisa.

Untuk desain yang lebih luas, baca panduan API testing dan panduan strategi testing.

CI dan hooks

name: test
on:
  pull_request:
  push:
    branches: [main]

jobs:
  unit:
    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 test

Hook untuk menjalankan Vitest terkait setelah edit.

{
  "$schema": "https://json.schemastore.org/claude-code-settings.json",
  "hooks": {
    "PostToolUse": [
      {
        "matcher": "Edit|Write|MultiEdit",
        "hooks": [
          {
            "type": "command",
            "command": "node .claude/hooks/run-related-vitest.mjs",
            "timeout": 120
          }
        ]
      }
    ]
  }
}
import { spawnSync } from "node:child_process";
import path from "node:path";

let raw = "";
for await (const chunk of process.stdin) {
  raw += chunk;
}

const event = raw ? JSON.parse(raw) : {};
const filePath = event.tool_input?.file_path;

if (typeof filePath !== "string" || !/\.[cm]?[jt]sx?$/.test(filePath)) {
  process.exit(0);
}

const target = path.isAbsolute(filePath)
  ? path.relative(process.cwd(), filePath)
  : filePath;

const result = spawnSync("npx", ["vitest", "related", target, "--run"], {
  stdio: "inherit",
  shell: process.platform === "win32",
});

process.exit(result.status ?? 1);

Di CLAUDE.md, buat aturan pendek.

## TDD workflow
- Behavior changes start with a failing test.
- Show the Red result before implementation.
- Implement the smallest change that makes the test pass.
- Refactor only after the targeted test is Green.
- Report the command, result, changed files, and remaining risk.

Lihat juga panduan hooks dan praktik terbaik CLAUDE.md.

Template prompt

Fitur baru dengan TDD:
Tujuan:
  Tambahkan [fitur].
Spesifikasi:
  - [jalur sukses]
  - [nilai batas]
  - [perilaku gagal]
Proses:
  1. Tulis test terlebih dahulu.
  2. Jalankan npm test dan tampilkan Red.
  3. Implementasikan perubahan minimum untuk Green.
  4. Refactor tanpa mengubah perilaku.
Kembalikan:
  Output gagal, perintah, file berubah, risiko tersisa.
Perbaikan bug dengan TDD:
Reproduksi:
  [input, aksi, atau log]
Ekspektasi:
  [perilaku benar]
Saat ini:
  [perilaku sekarang]
Permintaan:
  Tambahkan regression test yang gagal terlebih dahulu.
  Lalu buat perbaikan paling kecil.
  Jangan melemahkan atau menghapus test lama tanpa alasan.
Refactor aman:
Target:
  [file/fungsi]
Batasan:
  Perilaku publik tidak berubah.
Langkah:
  1. Tambahkan characterization tests untuk perilaku saat ini.
  2. Konfirmasi Green.
  3. Ubah internal saja.
  4. Jalankan ulang test yang sama.

Kesalahan umum

Kesalahan pertama adalah melewati Red. Jika test sudah lulus sebelum perbaikan, test itu tidak melindungi apa pun. Kedua, menguji detail implementasi, bukan perilaku. Ketiga, memakai waktu nyata di test; injeksikan now. Keempat, terlalu percaya mock untuk pembayaran, email, atau CRM. Kelima, membiarkan Claude Code menghapus test hanya demi Green.

CTA

Mulai dari satu aturan harga, satu parser CLI, atau satu regression API. Pengembang solo bisa memakai cheatsheet Claude Code gratis dan template di atas. Untuk prompt, hooks, dan checklist review yang bisa dipakai ulang, lihat produk ClaudeCodeLab. Tim yang ingin menerapkan TDD, CI, permission, dan review workflow di repository nyata dapat memakai training dan konsultasi Claude Code.

Hasil saat dicoba

Dalam workflow Masa, meminta Claude Code menulis failing test terlebih dahulu mengurangi waktu review dibanding meminta implementasi langsung. Kupon kedaluwarsa, quantity nol, dan route API tanpa autentikasi muncul lebih awal. Menjalankan semua test lewat hook terlalu lambat, jadi setup praktisnya adalah related Vitest setelah edit dan E2E penuh di CI.

#Claude Code #TDD #test-driven development #testing #quality assurance
Gratis

PDF gratis: cheatsheet Claude Code

Masukkan email dan unduh satu halaman berisi command, kebiasaan review, dan workflow aman.

Kami menjaga datamu dan tidak mengirim spam.

Masa

Tentang penulis

Masa

Engineer yang berfokus pada workflow Claude Code praktis dan adopsi tim.