Penggunaan Large Language Model (LLM) untuk sintesis kode—dikenal juga dengan fenomena vibecoding—sering menciptakan ilusi produktivitas. LLM mahir menghasilkan kode yang tampak benar secara sintaksis dan bahkan meloloskan unit test konvensional berbasis contoh (example-based testing). Namun, ketergantungan ini memicu fenomena reverse centaur: insinyur tidak lagi merancang arsitektur bernilai tinggi, melainkan terdegradasi menjadi tukang pembersih edge-case tak terduga dan halusinasi logika AI.

Solusi deterministik dari masalah ini bukan menambah prompt engineering, melainkan menerapkan test harness berbasis Property-Based Testing (PBT). Alih-alih menguji pasangan input-output statis, PBT menguji invarian (hukum tetap) dari sistem terhadap ratusan hingga ribuan input tergenerasi otomatis.

Jebakan Example-Based Testing pada Kode AI

LLM cenderung menghasilkan implementasi yang bias terhadap happy path atau kumpulan data pengujian standar yang sering muncul di corpus pelatihan publik. Perhatikan kasus nyata fungsi parser URL/URI query serializer berikut yang dihasilkan oleh AI:

def parse_and_reconstruct_query(query_string: str) -> str:
    """AI-generated: Parse query string, strip empty values, and sort keys."""
    if not query_string:
        return ""
    
    # Menghapus '?' jika ada di awal
    clean_query = query_string.lstrip('?')
    pairs = clean_query.split('&')
    
    parsed = {}
    for pair in pairs:
        if '=' in pair:
            key, val = pair.split('=', 1)
            if val:  # Menghapus nilai kosong
                parsed[key] = val
                
    sorted_pairs = [f"{k}={parsed[k]}" for k in sorted(parsed.keys())]
    return "&".join(sorted_pairs)

Jika diuji dengan unit test manual standar:

def test_parser_standard():
    assert parse_and_reconstruct_query("?b=2&a=1") == "a=1&b=2"
    assert parse_and_reconstruct_query("tag=&id=10") == "id=10"
    assert parse_and_reconstruct_query("") == ""

Test di atas berhasil lolos (100% pass). Namun, implementasi tersebut mengandung cacat fatal: parsing gagal jika nilai mengandung karakter URL-encoded (seperti %26 atau %3D), key duplikat tertimpa secara implisit tanpa standarisasi, dan pemisahan via split('&') gagal menangani token kosong berturut-turut.

Implementasi Property-Based Testing dengan Hypothesis

Untuk Python, framework standar de-facto untuk PBT adalah Hypothesis (atau fast-check pada ekosistem TypeScript). PBT memaksa kita merumuskan invarian alih-alih mengecek nilai akhir yang spesifik.

Menentukan Invarian Data

Pada fungsi serialisasi/transformasi, tiga invarian fundamental yang wajib diuji adalah:

  • Idempotensi: f(f(x)) == f(x). Menjalankan transformasi berulang tidak boleh mengubah state hasil.
  • Ketahanan (Round-trip consistency): Data yang diparsing dan direkonstruksi tidak boleh kehilangan data esensial.
  • Robustness terhadap Noise: Fungsi tidak boleh melempar unhandled exception pada format string arbitrer.

Berikut adalah harness verifikasi menggunakan Hypothesis untuk menguji parser tersebut:

from hypothesis import given, strategies as st
import urllib.parse

# Definisikan strategi generator input: dictionary dengan karakter unicode dan ASCII
query_dict_strategy = st.dictionaries(
    keys=st.text(alphabet=st.characters(blacklist_categories=('Cs',)), min_size=1),
    values=st.text(alphabet=st.characters(blacklist_categories=('Cs',))),
    max_size=10
)

@given(query_dict_strategy)
def test_parser_idempotency_and_encoding(data):
    # Buat raw query string menggunakan serializer standar python urllib
    raw_query = urllib.parse.urlencode(data)
    
    # Run 1
    result_1 = parse_and_reconstruct_query(raw_query)
    # Run 2
    result_2 = parse_and_reconstruct_query(result_1)
    
    # Invarian 1: Idempotensi
    assert result_1 == result_2, f"Gagal idempotensi: {result_1} != {result_2}"
    
    # Invarian 2: Key preservation untuk non-empty values
    parsed_back = dict(urllib.parse.parse_qsl(result_1, keep_blank_values=False))
    for k, v in data.items():
        if v != "":
            assert k in parsed_back, f"Key hilang: {k} tidak ditemukan setelah rekonstruksi"
            assert parsed_back[k] == v, f"Value korup: {parsed_back[k]} != {v}"

Mekanisme Shrinking Hypothesis

Ketika kode AI di atas dijalankan terhadap pengujian Hypothesis, runner akan memunculkan kegagalan dalam hitungan milidetik dan menjalankan proses shrinking (mereduksi input failure ke representasi paling minimal):

Falsifying example: test_parser_idempotency_and_encoding(
    data={'=': 'a'}
)
# Explanation: Key '=' di-encode menjadi '%3D'.
# Fungsi AI gagal membedakan delimitasi asli dan karakter encoded karena tidak melakukan decode terstruktur.

Tanpa PBT, bug seperti delimiter encoding atau parameter duplicate handling sering lolos hingga ke production runtime.

Workflow Anti-Reverse-Centaur

Untuk mempertahankan kontrol arsitektural tanpa terjebak membersihkan sintaks AI secara manual, terapkan alur verifikasi berikut:

  1. Tulis Kontrak Invarian Lebih Dahulu: Tentukan boundary testing dan invarian sistem menggunakan PBT sebelum meminta LLM menulis kode implementasi.
  2. Jadikan Test Harness Sebagai Constraint: Umpankan error trace dan falsifying example minimal dari Hypothesis kembali ke LLM saat terjadi kegagalan. Biarkan LLM merevisi kodenya sendiri hingga memenuhi kontrak logis.
  3. Bekukan Falsifying Example ke Unit Test Regresi: Hypothesis menyediakan decorator @example(). Masukkan kasus ekstrem yang ditemukan ke dalam test harness permanen untuk mencegah regresi di masa depan:
from hypothesis import example

@example(data={'=': 'a'})
@example(data={'a&b': 'val'})
@given(query_dict_strategy)
def test_parser_idempotency_and_encoding(data):
    # Test harness tetap berjalan deterministik
    ...

Kesimpulan

Tanggung jawab inti software engineer di era synthesis AI bergeser dari pengetikan kode menjadi formal verification. Dengan memadukan model generator AI bersama invariant checking deterministik melalui Property-Based Testing, pengembang mempertahankan kontrol ketat terhadap integritas fungsional sistem, sekaligus memotong waktu yang terbuang untuk debugging manual.