Test-Driven Development (TDD)
TDD adalah metodologi pengembangan di mana kita menulis test sebelum menulis kode implementasi. Prosesnya mengikuti siklus Red-Green-Refactor:
Siklus Red-Green-Refactor
- Red — Tulis test yang gagal (belum ada implementasi)
- Green — Tulis kode minimum agar test pass
- Refactor — Perbaiki kode tanpa mengubah perilaku, test tetap pass
Refactor Itu Langkah Eksplisit, Bukan Opsional
Setelah fase Green, sebelum masuk ke Red berikutnya, ambil waktu khusus untuk refactor:
- Ekstrak magic number menjadi konstanta bernama (
BATAS_DISKON, bukan100000) - Rename variabel agar intent-nya jelas (
x→totalBelanja) - Hilangkan duplikasi (rule of three: dua kali boleh, tiga kali ekstrak)
- Pecah fungsi panjang jadi helper kecil
Syaratnya satu: setelah refactor, semua test harus tetap hijau. Kalau ada test yang patah, kamu mengubah behaviour — itu bukan refactor, itu bug baru.
Contoh TDD
// LANGKAH 1 (Red): Tulis test dulu
import { describe, it, expect } from "vitest";
import { hitungDiskon } from "./diskon";
describe("hitungDiskon", () => {
it("memberikan diskon 10% untuk belanja di atas 100rb", () => {
expect(hitungDiskon(200000)).toBe(180000);
});
it("tidak ada diskon untuk belanja di bawah 100rb", () => {
expect(hitungDiskon(50000)).toBe(50000);
});
it("mengembalikan 0 untuk nilai negatif", () => {
expect(hitungDiskon(-10000)).toBe(0);
});
});
// LANGKAH 2 (Green): Implementasi minimum
export function hitungDiskon(total) {
if (total < 0) return 0;
if (total > 100000) return total * 0.9;
return total;
}
// LANGKAH 3 (Refactor): Perbaiki kode
const BATAS_DISKON = 100000;
const PERSENTASE_DISKON = 0.1;
export function hitungDiskon(total) {
if (total < 0) return 0;
if (total > BATAS_DISKON) {
return total * (1 - PERSENTASE_DISKON);
}
return total;
}
Kapan Menggunakan TDD?
- Cocok untuk: logika bisnis, utility functions, API endpoints, bug fixing
- Kurang cocok untuk: prototyping cepat, UI eksperimental, kode yang sering berubah total
CI/CD — Continuous Integration & Delivery
CI (Continuous Integration) berarti menjalankan test otomatis setiap kali ada push atau pull request. CD (Continuous Delivery) berarti deploy otomatis setelah test lulus.
GitHub Actions untuk JavaScript
# .github/workflows/test.yml
name: Test
on:
push:
branches: [main]
pull_request:
branches: [main]
jobs:
test-js:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 20
cache: "npm"
- run: npm ci
- run: npm test -- --run
GitHub Actions untuk Laravel
test-php:
runs-on: ubuntu-latest
services:
mysql:
image: mysql:8
env:
MYSQL_DATABASE: testing
MYSQL_ROOT_PASSWORD: password
ports:
- 3306:3306
steps:
- uses: actions/checkout@v4
- uses: shivammathur/setup-php@v2
with:
php-version: "8.3"
- run: composer install --no-interaction
- run: cp .env.example .env
- run: php artisan key:generate
- run: php artisan test
Test Coverage
Test coverage mengukur persentase kode yang dijalankan oleh test:
# Vitest coverage
npx vitest --coverage
# PHPUnit coverage
php artisan test --coverage
Metrik Coverage
- Line coverage — persentase baris kode yang dieksekusi
- Branch coverage — persentase cabang kondisional (if/else) yang diuji
- Function coverage — persentase fungsi yang dipanggil
Target realistis: 80% coverage untuk kode bisnis utama. 100% coverage bukan tujuan — fokus pada kualitas test, bukan kuantitas.
Tips CI/CD
- Jalankan test di setiap push dan pull request
- Gagalkan merge jika test gagal (branch protection rules)
- Cache dependencies (
node_modules,vendor) untuk mempercepat CI - Pisahkan job untuk unit test dan E2E test (E2E lebih lambat)