🤖 Sesi 10 dari 16 — QA Cerdas

AI-Driven Test Generation
& Self-Healing Tests

Dari menulis test case secara manual menjadi membiarkan AI membuat, memelihara, dan memperbaiki sendiri — revolusi cara kita melakukan pengujian software.

🔍 SBST & Search-Based Testing
🧬 Mutation Testing
🔧 Self-Healing Test Automation
3 × 50 menit
🗻 Bagian 1 — Landscape AI-Driven Test Generation

Empat Pendekatan Utama Test Generation

💡 ANALOGI — Ujian Otomatis vs Ujian Manual

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.

🔍
Search-Based Software Testing (SBST)
Menggunakan algoritma optimasi (Genetic Algorithm, Hill Climbing) untuk mencari input test case yang memaksimalkan coverage. Populer di industri: EvoSuite untuk Java.
Structural Coverage Automated
🧠
LLM-Based Test Generation
GPT-4, Claude, GitHub Copilot membaca kode sumber dan menghasilkan test case yang natural language-friendly. Cocok untuk unit tests dan integration tests.
LLM / GPT Contextual
🧬
Mutation Testing
Sengaja memasukkan bug kecil ke kode (mutant) lalu mengecek apakah test suite berhasil mendeteksinya. Mengukur kualitas test suite, bukan sekadar quantity coverage.
Quality Measurement Fault Injection
📋
Specification-Based Generation
Menghasilkan test dari formal specification, OpenAPI/Swagger docs, atau user story. Tools: Restler (REST API fuzzing), Dredd (API testing dari OpenAPI spec).
Spec-Driven API Testing
🔍 Bagian 2 — Search-Based Software Testing (SBST)

Menemukan Test Case Terbaik dengan Algoritma Optimasi

💡 ANALOGI — GPS Mencari Rute Terbaik

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.

TeknikCara KerjaKelebihanDigunakan Untuk
Genetic Algorithm (GA)Populasi test case → seleksi berdasarkan coverage → crossover + mutasi → generasi baruEfektif untuk ruang input besar, multi-objectiveWhole test suite generation (EvoSuite)
Hill ClimbingMulai dari test case acak, perbaiki sedikit demi sedikit menuju coverage lebih tinggiSederhana, cepat untuk fungsi lokalSingle branch coverage, unit testing
Simulated AnnealingHill climbing + kemungkinan terima solusi lebih buruk untuk menghindari local optimaLebih robust dari Hill Climbing biasaComplex branching structures
Random Testing / FuzzingGenerate input secara acak dalam jumlah sangat besar dengan sangat cepatSederhana, efektif untuk security testingSecurity, boundary conditions, crash detection

🏆 EvoSuite — SBST Terbaik untuk Java

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.

90%+
Branch coverage rata-rata pada benchmark standard
3x
Lebih banyak coverage vs test manual dalam waktu sama
14s
Rata-rata waktu generate test suite untuk 1 class Java
🧠 Bagian 3 — LLM untuk Test Generation

GitHub Copilot & AI Models Menulis Test untuk Anda

💡 ANALOGI — Asisten Cerdas yang Baca Kode

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.

Kekuatan LLM untuk Test Gen

  • Memahami semantik bisnis dari nama variabel dan komentar kode
  • Menghasilkan test yang readable dan bermakna, bukan sekadar angka acak
  • Generate test dari docstring/komentar fungsi tanpa melihat implementasi
  • Mendukung berbagai framework: pytest, Jest, JUnit, RSpec
  • Dapat diprompt untuk fokus pada edge cases tertentu

Keterbatasan LLM untuk Test Gen

  • Bisa menghasilkan test yang selalu pass tapi tidak meaningful
  • Tidak mengerti runtime behavior — hanya analisis statis kode
  • Mungkin melewatkan edge case yang butuh pengetahuan domain spesifik
  • Test yang dihasilkan perlu review manual sebelum digunakan di produksi
  • Halusinasi: bisa membuat assertion yang salah secara logika

Contoh: AI Test Generation dari Fungsi Transfer Saldo

KODE SUMBER (input ke LLM)
def transfer(sender, rcvr, amt):
  if amt <= 0:
    raise ValueError()
  if sender.saldo < amt:
    raise InsufficientFunds()
  sender.saldo -= amt
  rcvr.saldo += amt
  return True
TEST CASES YANG DIHASILKAN AI
# TC1: Transfer valid
def test_transfer_valid(): ...

# TC2: Amount = 0 → error
def test_amount_zero(): ...

# TC3: Amount negatif → error
def test_amount_negative(): ...

# TC4: Saldo tidak cukup
def test_insufficient(): ...
⚡ Bagian 4 — Flaky Tests: Musuh Tersembunyi CI/CD

Test yang Kadang Pass, Kadang Fail — Berbahaya!

💡 ANALOGI — Lampu Jalan yang Kadang Mati

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.

Visualisasi: test_login_flow — 12 kali jalan di CI/CD
4/12 gagal tanpa perubahan kode — Flaky!

Penyebab Umum Flaky Tests

  • Async/Timing issues — test tidak menunggu operasi async selesai
  • Shared mutable state — test A mengubah state yang dibaca test B
  • External dependencies — network/DB/API eksternal tidak stabil
  • Order dependency — test hanya pass jika dijalankan urutan tertentu
  • Resource leaks — file/port/connection tidak di-close setelah test
  • Concurrency — race condition di test yang berjalan paralel

Cara Mendeteksi & Memperbaiki

  • Run test N kali — jalankan 10–50× untuk membuktikan flakiness
  • AI-assisted analysis — Launchable mengidentifikasi pola kegagalan
  • Explicit waits — ganti sleep() dengan wait_until(condition)
  • Test isolation — setiap test setup/teardown state-nya sendiri
  • Mock external deps — gunakan mock/stub untuk network calls
  • Quarantine — pisahkan flaky tests, perbaiki, baru kembalikan

⚠️ 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.

🧬 Bagian 5 — Mutation Testing: Mengukur Kualitas Test Suite

"Apakah Test Kita Benar-Benar Bisa Menangkap Bug?"

💡 ANALOGI — Latihan Pemadam Kebakaran

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.

Arithmetic Mutation
total = a + b
total = a - b
Relational Mutation
if age >= 18:
if age > 18:
Logical Mutation
if a and b:
if a or b:
Return Mutation
return True
return False
Boundary Mutation
if i <= limit:
if i < limit:
Statement Deletion
x = validate(input)
(dihapus)

Mutation Score — Ukuran Kualitas Test Suite

Mutation Score = (Mutants Killed / Total Mutants) × 100%

<60%
Test suite lemah — banyak bug tidak akan terdeteksi
60-80%
Cukup baik — masih ada ruang perbaikan test
>80%
Test suite berkualitas — standar industri kritis

Catatan: 100% mutation score hampir tidak mungkin karena "equivalent mutants" — mutant yang berperilaku sama persis dengan kode asli meski berbeda sintaksnya.

🔧 Bagian 6 — Self-Healing Test Automation

Test yang Memperbaiki Dirinya Sendiri

💡 ANALOGI — Tubuh yang Menyembuhkan Dirinya

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 Gagal — UI Berubah

Test mencari elemen dengan ID #btn-submit-v1, tapi developer mengubahnya menjadi #submit-button setelah redesign UI.

🔍

AI Analisis DOM & Konteks

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".

🤖

ML Scoring — Pilih Kandidat Terbaik

Model ML menghitung similarity score untuk setiap kandidat elemen. Elemen dengan score tertinggi dipilih sebagai pengganti. Confidence > 0.85 digunakan otomatis.

Test Diperbarui & Dijalankan Ulang

Selector diperbarui otomatis. Test berhasil. Developer mendapat notifikasi: "Selector di-update — harap validasi untuk perubahan berikutnya."

🎯
Testim
Pioneer self-healing UI tests. ML-based element locator yang terus belajar dari perubahan UI nyata.
👁️
Applitools
Visual AI — membandingkan screenshot dengan baseline menggunakan computer vision yang sangat akurat.
🌊
Mabl
Auto-heals tests berdasarkan historical patterns. Terintegrasi penuh dengan CI/CD pipeline modern.
🤖
Diffblue Cover
AI menulis dan memperbarui unit tests Java secara otomatis setiap kali kode berubah di repository.
Launchable
Memprediksi test mana yang paling mungkin fail dan memprioritaskannya di awal CI/CD run untuk feedback cepat.
🐍 Bagian 7 — Praktik: Mutation Testing & AI Test Generation

Implementasi dengan pytest & mutmut

Mutation Testing Simulation + AI-Style Test Suite Python · pytest · mutmut
# ================================================================
# 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")
KUIS SESI 10

Uji Pemahaman Anda

Soal 1 — SBST vs LLM

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.

Soal 2 — Flaky Tests

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?

Soal 3 — Mutation Testing

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.

Soal 4 — Self-Healing Calculation

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?

Soal 5 — Python Challenge

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.

Rangkuman Kunci Sesi 10

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