Dari menulis test case secara manual menjadi membiarkan AI membuat, memelihara, dan memperbaiki sendiri — revolusi cara kita melakukan pengujian software.
Test generation manual: Seperti dosen membuat soal ujian satu per satu — butuh waktu, tenaga, dan mungkin ada blind spot pada aspek yang tidak terpikirkan untuk diujikan.
AI-driven test generation: Seperti sistem yang otomatis menganalisis silabus, memetakan semua kemungkinan kombinasi konsep, lalu menghasilkan ratusan variasi soal yang menjamin semua topik tercakup — dan setiap kali silabus berubah, soal diperbarui otomatis.
SBST seperti GPS yang mencari rute terpendek ke tujuan. GPS tidak mencoba semua rute yang mungkin (terlalu banyak), tapi menggunakan algoritma heuristik untuk menemukan rute terbaik secara efisien. Dalam SBST, "tujuan" adalah memaksimalkan branch/path coverage, dan "rute" adalah nilai input yang dihasilkan secara otomatis.
| Teknik | Cara Kerja | Kelebihan | Digunakan Untuk |
|---|---|---|---|
| Genetic Algorithm (GA) | Populasi test case → seleksi berdasarkan coverage → crossover + mutasi → generasi baru | Efektif untuk ruang input besar, multi-objective | Whole test suite generation (EvoSuite) |
| Hill Climbing | Mulai dari test case acak, perbaiki sedikit demi sedikit menuju coverage lebih tinggi | Sederhana, cepat untuk fungsi lokal | Single branch coverage, unit testing |
| Simulated Annealing | Hill climbing + kemungkinan terima solusi lebih buruk untuk menghindari local optima | Lebih robust dari Hill Climbing biasa | Complex branching structures |
| Random Testing / Fuzzing | Generate input secara acak dalam jumlah sangat besar dengan sangat cepat | Sederhana, efektif untuk security testing | Security, boundary conditions, crash detection |
EvoSuite secara otomatis menghasilkan test suite JUnit yang memaksimalkan code coverage menggunakan Genetic Algorithm. Diberikan sebuah class Java, EvoSuite menghasilkan test suite lengkap tanpa intervensi manusia.
LLM seperti asisten yang sangat berpengalaman — setelah membaca kode Anda, ia langsung tahu: "Fungsi ini perlu ditest saat input negatif, saat nol, saat sangat besar, dan saat bertipe string." Asisten ini belajar dari jutaan test case di GitHub untuk mengetahui pola testing yang baik untuk setiap jenis fungsi.
Flaky test seperti lampu jalan yang kadang menyala, kadang tidak — tanpa alasan jelas. Pengemudi tidak bisa lagi mempercayai lampu tersebut. Developer yang sering melihat test "tiba-tiba fail lalu pass lagi" mulai mengabaikan notifikasi CI/CD — padahal mungkin ada bug nyata di antara kegagalan palsu tersebut.
⚠️ Studi Google (2016): 16% dari semua test failures di Google disebabkan flaky tests. Tim yang tidak menangani flaky tests kehilangan kepercayaan pada CI/CD mereka, memperlambat deteksi bug nyata.
Coverage tinggi ibarat banyak pemadam yang hadir latihan. Mutation testing ibarat memulai kebakaran kecil sungguhan — untuk membuktikan mereka bisa memadamkan api, bukan sekadar duduk manis. Kita sengaja memasukkan "bug kecil" ke kode, lalu memeriksa: apakah test suite kita mendeteksinya? Jika tidak, test kita lemah meskipun coverage-nya tinggi.
Mutation Score = (Mutants Killed / Total Mutants) × 100%
Catatan: 100% mutation score hampir tidak mungkin karena "equivalent mutants" — mutant yang berperilaku sama persis dengan kode asli meski berbeda sintaksnya.
Ketika kulit terluka, tubuh secara otomatis mengirim sel darah putih dan memulai proses penyembuhan tanpa instruksi manual. Self-healing tests bekerja sama: ketika UI berubah (tombol "Login" dipindahkan), test secara otomatis menemukan elemen yang benar berdasarkan konteks, dan memperbarui selectornya — tanpa perlu developer memperbaiki test satu per satu.
Test mencari elemen dengan ID #btn-submit-v1, tapi developer mengubahnya menjadi #submit-button setelah redesign UI.
Engine self-healing menganalisis: posisi elemen di halaman, teks label, tipe elemen, dan atribut lain (aria-label, class, xpath) untuk mencari kandidat elemen yang "paling mungkin sama".
Model ML menghitung similarity score untuk setiap kandidat elemen. Elemen dengan score tertinggi dipilih sebagai pengganti. Confidence > 0.85 digunakan otomatis.
Selector diperbarui otomatis. Test berhasil. Developer mendapat notifikasi: "Selector di-update — harap validasi untuk perubahan berikutnya."
# ================================================================
# S11409 - Sesi 10: Mutation Testing + AI-Generated Tests
# Dosen: Riadi Marta Dinata, S.Ti., M.Kom. | ISTN Jakarta
# pip install pytest mutmut
# ================================================================
# ── kalkulator_kredit.py ──────────────────────────────────────
class KalkulatorKredit:
"""Kalkulator cicilan kredit sederhana (OJK compliant)"""
def hitung_cicilan(self, pokok, bunga_tahunan, tenor_bulan):
if pokok <= 0:
raise ValueError("Pokok pinjaman harus positif")
if bunga_tahunan < 0 or bunga_tahunan > 100:
raise ValueError("Bunga harus antara 0 dan 100")
if tenor_bulan <= 0:
raise ValueError("Tenor harus lebih dari 0 bulan")
bunga_per_bulan = bunga_tahunan / 12 / 100
total_bunga = pokok * bunga_per_bulan * tenor_bulan
cicilan = (pokok + total_bunga) / tenor_bulan
return round(cicilan, 2)
def cek_kelayakan(self, penghasilan_bulanan, cicilan):
"""Aturan OJK: cicilan maks 30% dari penghasilan"""
if penghasilan_bulanan <= 0:
raise ValueError("Penghasilan harus positif")
rasio = cicilan / penghasilan_bulanan
return rasio <= 0.30
# ── tests/test_kredit_ai_generated.py ─────────────────────────
import pytest
# from kalkulator_kredit import KalkulatorKredit
@pytest.fixture
def kalkulator():
return KalkulatorKredit()
class TestHitungCicilan:
"""Test suite mensimulasikan output AI / GitHub Copilot"""
def test_cicilan_standar(self, kalkulator):
# 12jt, 12%/thn, 12bln → cicilan 1.120.000
cicilan = kalkulator.hitung_cicilan(12_000_000, 12, 12)
assert cicilan == 1_120_000.0
def test_cicilan_tanpa_bunga(self, kalkulator):
cicilan = kalkulator.hitung_cicilan(6_000_000, 0, 12)
assert cicilan == 500_000.0
# BVA lengkap yang dihasilkan AI
@pytest.mark.parametrize("pokok,bunga,tenor,valid", [
(1, 0, 1, True), # min boundary valid
(0, 12, 12, False), # zero → invalid
(-1, 12, 12, False), # negatif → invalid
(1_000_000, 0, 12, True), # bunga min valid
(1_000_000, 100, 12, True), # bunga max valid
(1_000_000, 101, 12, False), # bunga > max → invalid
(1_000_000, -1, 12, False), # bunga negatif → invalid
(1_000_000, 12, 1, True), # tenor min valid
(1_000_000, 12, 0, False), # tenor zero → invalid
])
def test_validasi_boundary(self, kalkulator, pokok, bunga, tenor, valid):
if valid:
assert kalkulator.hitung_cicilan(pokok, bunga, tenor) > 0
else:
with pytest.raises(ValueError):
kalkulator.hitung_cicilan(pokok, bunga, tenor)
def test_layak_tepat_30pct(self, kalkulator):
# Boundary: tepat 30% → LAYAK
assert kalkulator.cek_kelayakan(10_000_000, 3_000_000) == True
def test_tidak_layak_31pct(self, kalkulator):
# Boundary: 31% → TIDAK LAYAK
assert kalkulator.cek_kelayakan(10_000_000, 3_100_000) == False
# ── SIMULASI MUTATION TESTING ANALYSIS ───────────────────────
def simulate_mutation_analysis():
mutants = [
{"id":1, "desc":"pokok <= 0 → pokok < 0", "killed": True},
{"id":2, "desc":"bunga/12/100 → bunga/12*100", "killed": True},
{"id":3, "desc":"rasio <= 0.30 → rasio < 0.30", "killed": True},
{"id":4, "desc":"round(cicilan, 2) → cicilan", "killed": False},
{"id":5, "desc":"tenor_bulan <= 0 → tenor_bulan < 0","killed": True},
{"id":6, "desc":"cicilan * tenor → cicilan + tenor","killed": True},
]
killed = sum(1 for m in mutants if m["killed"])
total = len(mutants)
score = killed / total * 100
print("=" * 55)
print(" MUTATION TESTING RESULTS")
print("=" * 55)
for m in mutants:
status = "KILLED " if m["killed"] else "SURVIVED "
icon = "OK" if m["killed"] else "!!"
print(f" [{m['id']}] [{icon}] {status} | {m['desc']}")
print(f"\n Killed / Total : {killed}/{total}")
print(f" Mutation Score : {score:.1f}%")
grade = "GOOD" if score >= 80 else ("FAIR" if score >= 60 else "POOR")
print(f" Status : {grade}")
print("\n Tip: Mutant #4 survived — tambahkan test yang")
print(" memverifikasi presisi 2 desimal pada cicilan.")
return score
if __name__ == "__main__":
simulate_mutation_analysis()
print("\nJalankan pytest : pytest tests/ -v")
print("Jalankan mutmut : mutmut run && mutmut results")
Sebuah fungsi memiliki 15 cabang logika kompleks dengan tipe data numerik. Pendekatan mana yang lebih tepat: SBST (Genetic Algorithm) atau LLM-based generation? Jelaskan mengapa dan sebutkan kelebihan masing-masing.
Test test_kirim_email gagal 20% dari waktu di CI/CD. Log menunjukkan kadang koneksi SMTP timeout. Identifikasi: (a) jenis flakiness apa ini? (b) bagaimana cara memperbaikinya tanpa menghapus test? (c) apa risiko jika dibiarkan?
Test suite memiliki 95% statement coverage tapi mutation score hanya 52%. Bagaimana bisa terjadi? Berikan contoh konkret kondisi di mana coverage tinggi tapi mutation score rendah.
Tim QA punya 200 UI test cases. Setiap sprint rata-rata 15 test rusak karena perubahan CSS. (a) Jika rata-rata perbaikan 30 menit/test, berapa jam terbuang per sprint? (b) Bagaimana self-healing tool mengurangi ini? (c) Apa risiko yang perlu diwaspadai?
Tambahkan fungsi hitung_denda(hari_terlambat, cicilan) dengan denda 0.1% per hari, maksimum 5% dari cicilan. Buat test BVA lengkap dan identifikasi 4 mutant yang bisa dihasilkan.
SBST terbaik untuk coverage struktural komprehensif; LLM terbaik untuk test yang readable dan bermakna secara bisnis
Flaky tests merusak kepercayaan CI/CD — deteksi dini dan quarantine lebih baik dari membiarkan
Mutation testing mengukur kualitas test suite, bukan sekadar kuantitas — coverage 100% tidak sama dengan test berkualitas
Self-healing tests mengurangi maintenance overhead UI automation, tapi selalu perlu validasi manusia
Mutation score lebih dari 80% adalah target yang baik untuk sistem kritis seperti perbankan dan kesehatan
AI test generation bukan pengganti tester — AI membantu kecepatan, manusia memastikan relevansi dan kualitas