Strategi Testing dengan Claude Code: Vitest, Testing Library, Playwright, dan CI
Rancang unit, integration, E2E, dan CI testing dengan Claude Code tanpa membuat test rapuh.
Strategi testing bukan sekadar menulis lebih banyak test. Strategi testing menentukan perilaku mana yang dilindungi unit test cepat, batas mana yang perlu integration test, dan alur bisnis mana yang layak dijaga dengan E2E.
Kalau Anda hanya meminta Claude Code “tambahkan test”, hasilnya bisa berupa assertion dangkal, selector CSS yang rapuh, atau test yang terlalu menempel ke implementasi saat ini. Prompt yang lebih baik menyebut layer test, perilaku yang ingin dijaga, command yang harus dijalankan, dan batas file yang boleh diubah.
Panduan ini dicek terhadap dokumentasi resmi Claude Code common workflows, Vitest coverage, Testing Library queries, serta Playwright untuk locators, assertions, dan CI.
Mulai dari piramida testing
flowchart TB
E2E["E2E: signup, checkout, CTA revenue"]
INT["Integration: API, DB, form, component"]
UNIT["Unit: calculation, validation, permission"]
E2E --> INT --> UNIT
| Layer | Patokan | Yang dilindungi | Tools |
|---|---|---|---|
| Unit | 60-70% | logika murni, validasi, permission | Vitest |
| Integration | 20-30% | component, API, batas DB | Vitest + Testing Library |
| E2E | 5-10% | signup, checkout, jalur revenue | Playwright |
| CI gate | setiap PR | lint, types, tests, reports | GitHub Actions |
Logika harga cocok untuk unit test. Tombol checkout cocok untuk integration test. Perjalanan dari CTA artikel ke halaman produk cocok untuk E2E.
Setup minimal
npm i -D vitest @vitest/coverage-v8 jsdom \
@testing-library/react @testing-library/jest-dom @testing-library/user-event \
@playwright/test
npx playwright install --with-deps
{
"scripts": {
"test": "vitest --run",
"test:watch": "vitest",
"test:coverage": "vitest --run --coverage",
"test:e2e": "playwright test"
}
}
// vitest.config.ts
import { defineConfig } from "vitest/config";
export default defineConfig({
test: {
environment: "jsdom",
setupFiles: ["./test/setup.ts"],
coverage: {
provider: "v8",
reporter: ["text", "html"],
thresholds: {
lines: 80,
functions: 80,
branches: 75,
statements: 80,
},
},
},
});
// test/setup.ts
import "@testing-library/jest-dom/vitest";
Vitest mendokumentasikan provider coverage v8 dan istanbul. Untuk project Node atau Chromium, v8 biasanya pilihan yang paling praktis.
Contoh 1: Unit test untuk logika harga
// src/lib/pricing.ts
export type PriceInput = {
unitPrice: number;
quantity: number;
discountRate?: number;
taxRate?: number;
};
export function calculateTotal({
unitPrice,
quantity,
discountRate = 0,
taxRate = 0.1,
}: PriceInput): number {
if (!Number.isInteger(quantity) || quantity < 0) {
throw new Error("quantity must be a non-negative integer");
}
if (unitPrice < 0) throw new Error("unitPrice must be non-negative");
if (discountRate < 0 || discountRate > 1) {
throw new Error("discountRate must be between 0 and 1");
}
const discounted = unitPrice * quantity * (1 - discountRate);
return Math.round(discounted * (1 + taxRate));
}
// src/lib/pricing.test.ts
import { describe, expect, it } from "vitest";
import { calculateTotal } from "./pricing";
describe("calculateTotal", () => {
it("calculates a tax-included total", () => {
expect(calculateTotal({ unitPrice: 1000, quantity: 2 })).toBe(2200);
});
it("applies discount before tax", () => {
expect(
calculateTotal({ unitPrice: 1000, quantity: 2, discountRate: 0.2 })
).toBe(1760);
});
it("allows zero quantity", () => {
expect(calculateTotal({ unitPrice: 1000, quantity: 0 })).toBe(0);
});
it("rejects invalid inputs", () => {
expect(() => calculateTotal({ unitPrice: 1000, quantity: -1 })).toThrow(
"quantity must be a non-negative integer"
);
});
});
Kesalahan umum adalah mengejar coverage 80% dengan happy path saja. Untuk harga, kasus pentingnya adalah quantity nol, diskon penuh, rate invalid, dan pembulatan.
Contoh 2: CTA dengan Testing Library
Testing Library merekomendasikan query yang mirip dengan cara pengguna menemukan elemen. Karena itu getByRole dan getByLabelText lebih stabil daripada class CSS.
// src/components/CheckoutButton.tsx
import { useState } from "react";
type Props = {
stock: number;
onCheckout: () => Promise<void>;
};
export function CheckoutButton({ stock, onCheckout }: Props) {
const [submitting, setSubmitting] = useState(false);
const soldOut = stock <= 0;
async function handleClick() {
setSubmitting(true);
try {
await onCheckout();
} finally {
setSubmitting(false);
}
}
return (
<button
type="button"
disabled={soldOut || submitting}
aria-busy={submitting}
onClick={handleClick}
>
{soldOut ? "Sold out" : submitting ? "Processing..." : "Buy now"}
</button>
);
}
// src/components/CheckoutButton.test.tsx
import { render, screen } from "@testing-library/react";
import userEvent from "@testing-library/user-event";
import { describe, expect, it, vi } from "vitest";
import { CheckoutButton } from "./CheckoutButton";
describe("CheckoutButton", () => {
it("calls checkout when in stock", async () => {
const user = userEvent.setup();
const onCheckout = vi.fn().mockResolvedValue(undefined);
render(<CheckoutButton stock={3} onCheckout={onCheckout} />);
await user.click(screen.getByRole("button", { name: "Buy now" }));
expect(onCheckout).toHaveBeenCalledTimes(1);
});
it("prevents checkout when sold out", async () => {
const user = userEvent.setup();
const onCheckout = vi.fn().mockResolvedValue(undefined);
render(<CheckoutButton stock={0} onCheckout={onCheckout} />);
const button = screen.getByRole("button", { name: "Sold out" });
expect(button).toBeDisabled();
await user.click(button);
expect(onCheckout).not.toHaveBeenCalled();
});
});
CTA yang berhubungan dengan revenue tidak sebaiknya diuji lewat .primary-button atau snapshot saja. Test harus memastikan role, teks, dan perilakunya benar.
Contoh 3: Playwright E2E yang kecil
// playwright.config.ts
import { defineConfig, devices } from "@playwright/test";
const baseURL =
process.env.PLAYWRIGHT_TEST_BASE_URL ?? "http://127.0.0.1:5173";
export default defineConfig({
testDir: "./e2e",
retries: process.env.CI ? 2 : 0,
use: { baseURL, trace: "on-first-retry" },
projects: [{ name: "chromium", use: { ...devices["Desktop Chrome"] } }],
webServer: process.env.PLAYWRIGHT_TEST_BASE_URL
? undefined
: {
command: "npm run dev -- --host 127.0.0.1",
url: baseURL,
reuseExistingServer: !process.env.CI,
},
});
// e2e/article-cta.spec.ts
import { expect, test } from "@playwright/test";
test("reader can move from article CTA to products", async ({ page }) => {
await page.goto("/id/blog/claude-code-testing-strategies");
await page.getByRole("link", { name: /products|templates|produk/i }).click();
await expect(page).toHaveURL(/\/products\/?$/);
await expect(page.getByRole("heading", { name: /products|produk/i })).toBeVisible();
});
Assertion web-first di Playwright melakukan retry otomatis. Gunakan await expect(locator).toBeVisible() daripada waktu tunggu tetap. Hindari path CSS yang dalam dan nth().
Contoh 4: CI gate
# .github/workflows/test.yml
name: Test
on:
pull_request:
push:
branches: [main]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v6
with:
node-version: 22
cache: npm
- run: npm ci
- run: npm run test:coverage
- run: npx playwright install --with-deps
- run: npm run test:e2e
env:
CI: "true"
- uses: actions/upload-artifact@v5
if: ${{ !cancelled() }}
with:
name: playwright-report
path: playwright-report/
retention-days: 30
Jika memakai Claude Code GitHub Actions, ikuti action GA saat ini, yaitu anthropics/claude-code-action@v1. Jangan menyalin contoh lama @beta tanpa mengecek dokumentasi.
Template prompt untuk Claude Code
Tambahkan unit test Vitest untuk src/lib/pricing.ts.
Cover success cases, boundary values, dan invalid inputs.
Jangan ubah implementasi kecuali Anda lebih dulu mencatat dugaan bug.
Jalankan npm run test -- pricing dan rangkum hasilnya.
Tambahkan test Testing Library untuk src/components/CheckoutButton.tsx.
Gunakan getByRole dan userEvent.setup().
Jangan gunakan selector CSS, snapshot sebagai assertion utama, atau detail implementasi.
Cover state in-stock, sold-out, dan submitting.
Tambahkan satu test Playwright E2E untuk flow CTA artikel ke products.
Hindari waitForTimeout, selector CSS dalam, dan nth().
Gunakan web-first assertions dan fokus pada revenue path.
Baca kegagalan CI terbaru.
Klasifikasikan sebagai lint, typecheck, unit, e2e, atau environment.
Perbaiki root cause dengan diff terkecil.
Jangan skip test dan jangan hanya menambah timeout.
Jebakan umum
Jangan mock semuanya. Payment provider dan email delivery boleh dimock, tetapi harga, permission, dan persistensi penting harus diuji di layer yang tepat.
Jangan gunakan E2E untuk mengunci semua detail visual. E2E harus melindungi signup, checkout, contact, dan klik produk.
Jangan anggap coverage sebagai bukti kualitas. Coverage 80% masih bisa melewatkan branch yang melakukan charge, delete, atau publish.
Jangan beri Claude Code happy path saja. Tambahkan larangan: tidak ada selector rapuh, tidak ada skip, tidak snapshot-only, dan tidak coupling ke detail implementasi.
CTA
Testing juga melindungi jalur revenue. Mulai dari Claude Code cheatsheet, ubah aturan review berulang menjadi products and templates, lalu gunakan Claude Code training jika tim perlu menyatukan CI, aturan review, dan kepemilikan test. Untuk lanjut, baca TDD with Claude Code, CI/CD setup, dan debugging techniques.
Hasil saat dicoba
Masa mencoba pola ini pada CTA artikel dan flow menuju halaman produk di ClaudeCodeLab. Hasil terbaik bukan dari menambah banyak E2E, tetapi dari menaruh pricing logic di unit test, perilaku CTA di Testing Library, dan satu test Playwright untuk flow artikel-ke-produk. Saat Claude Code diminta mencatat failure mode lebih dulu, test yang di-skip dan selector CSS rapuh berkurang. Risiko tersisa ada pada payment eksternal dan script iklan, yang masih perlu bukti CI ditambah pengecekan manual singkat sebelum publish.
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.
Tentang penulis
Masa
Engineer yang berfokus pada workflow Claude Code praktis dan adopsi tim.
Artikel terkait
Permission receipt Claude Code: mencatat scope, bukti, dan rollback
Pola permission receipt untuk Claude Code: aksi yang diizinkan, batas approval, command verifikasi, rollback, dan cek CTA revenue.
Agent Harness Aman untuk Claude Code dan Codex: Permission, Verifikasi, dan Rollback
Rancang Agent Harness praktis untuk Claude Code dan Codex dengan policy, plan, verification, dan recovery layer.
Subagent Claude Code: panduan praktis untuk delegasi artikel dan kode
Panduan subagent Claude Code untuk membagi pekerjaan artikel dan kode: aturan delegasi, prompt, risiko, dan checklist.