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
Buat Budget Log Claude Code Sebelum Biaya Tim Menjadi Kabur
Catat siapa memakai Claude Code, untuk pekerjaan apa, dan hasil apa yang muncul.
Cek 3 Menit Sebelum Commit: Pastikan Area yang Disentuh Claude Code
Cara menemukan perubahan yang diam-diam diperluas Claude Code dalam 3 menit sebelum commit. Urutan cek scope, diff, bukti, dan staging file.
Risk register sebelum adopsi Claude Code di tim
Cara membuat risk register agar adopsi Claude Code di tim tidak berakhir jadi insiden permission, CI, dan deploy. Lengkap contoh dan kode.