Testing adalah latihan disiplin yang memastikan kode berperilaku sesuai harapan sebelum sampai ke pengguna. Tanpa test, kita mengandalkan asumsi dan debug manual yang mahal ketika kode berkembang. Pada chapter ini kita akan membahas mengapa testing penting, tiga jenis test utama yang sering dijumpai, pola penulisan test Arrange-Act-Assert, dan bentuk test pyramid yang menjadi panduan distribusi jumlah test antar lapisan aplikasi.

Mengapa Testing Diperlukan #

Kode yang berjalan sekali belum tentu berjalan benar ketika dipakai berulang dengan input berbeda. Tanpa test otomatis, kita harus memverifikasi setiap perubahan secara manual — sebuah proses yang lambat, rentan lupa, dan tidak dapat diskalakan. Test otomatis menjadi jaring pengaman: setiap kali kode berubah, kita jalankan kembali test dan langsung tahu jika ada perilaku yang melenceng dari yang diharapkan.

Beberapa manfaat utama testing:

ManfaatPenjelasan
Regresi terdeteksiTest lama gagal ketika perubahan baru merusak perilaku yang sudah berfungsi
Dokumentasi hidupNama test yang baik menjelaskan apa yang seharusnya dilakukan kode
Refactoring amanKita dapat mengubah struktur internal tanpa takut mengubah output
Desain lebih baikKode yang sulit diuji biasanya berarti coupling terlalu kuat

Jenis Test: Unit, Integration, End-to-End #

Tidak semua test sama. Tiga kategori utama yang sering dijumpai:

JenisCakupanKecepatanContoh
Unit testSatu function/class terisolasiSangat cepat (<10ms)Test Kalkulator.tambah(2,3) == 5
Integration testBeberapa komponen bersamaanCepat (<1s)Test Service + Repository in-memory
End-to-end (e2e)Seluruh aplikasi dari UI hingga DBLambat (>1s)Test request HTTP end-to-end

Unit test fokus pada satu unit kecil — biasanya satu function atau class — dengan dependency diganti mock atau stub. Integration test menggabungkan beberapa komponen nyata untuk memverifikasi bahwa mereka bekerja sama. End-to-end test menjalankan seluruh alur dari pintu masuk aplikasi (HTTP request, klik tombol) sampai database dan kembali.

Pola Arrange-Act-Assert #

Pola Arrange-Act-Assert (AAA) membagi setiap test menjadi tiga bagian jelas. Arrange menyiapkan kondisi awal — objek yang dipakai, parameter input, state awal. Act memanggil fungsi yang sedang diuji dengan satu panggilan. Assert membandingkan hasil aktual dengan hasil yang diharapkan. Struktur ini membuat test mudah dibaca dan dipahami tanpa harus menebak langkah mana yang menyiapkan, mana yang mengeksekusi.

kotlin
// testing_fundamentals.kt
// Mendemonstrasikan pola Arrange-Act-Assert (AAA) dan test pyramid
// dengan kode Kotlin murni (tanpa dependensi framework eksternal).

class Kalkulator {
    fun tambah(a: Int, b: Int): Int = a + b
    fun bagi(a: Int, b: Int): Int {
        require(b != 0) { "Penyebut tidak boleh nol" }
        return a / b
    }
}

// Helper mini-assertion agar contoh tetap runnable tanpa JUnit.
fun cekHasil(nama: String, expected: Int, actual: Int) {
    if (expected == actual) {
        println("[LULUS] $nama => $actual")
    } else {
        println("[GAGAL]  $nama => expected=$expected actual=$actual")
    }
}

fun cekPengecualian(nama: String, blok: () -> Unit) {
    try {
        blok()
        println("[GAGAL]  $nama => tidak ada pengecualian")
    } catch (e: IllegalArgumentException) {
        println("[LULUS] $nama => ${e.message}")
    }
}

fun main() {
    // === Arrange-Act-Assert ===
    // Arrange
    val kalkulator = Kalkulator()
    val a = 10
    val b = 4

    // Act
    val hasil = kalkulator.tambah(a, b)

    // Assert
    cekHasil("tambah(10, 4)", 14, hasil)

    // Skenario negatif: pembagian dengan nol
    cekPengecualian("bagi(10, 0)") { kalkulator.bagi(10, 0) }

    // === Test Pyramid ===
    // Banyak unit test cepat di dasar, sedikit integration di tengah,
    // sangat sedikit end-to-end di puncak.
    println()
    println("Pyramid: unit=${20}, integration=${4}, end_to_end=${1}")
}

Output yang Dihasilkan:

text
[LULUS] tambah(10, 4) => 14
[LULUS] bagi(10, 0) => Penyebut tidak boleh nol

Pyramid: unit=20, integration=4, end_to_end=1

Mari kita telaah bagian-bagian kunci contoh di atas:

  1. Class Kalkulator: SUT (System Under Test) yang diuji. Memiliki method tambah dan bagi dengan kontrak jelas — bagi melempar IllegalArgumentException saat penyebut nol.
  2. cekHasil / cekPengecualian: helper assertion mini. Pada produksi kita akan pakai kotlin.test atau JUnit; di sini kita buat versi sederhana supaya file runnable tanpa library.
  3. Bagian Arrange: tiga baris yang menyiapkan instance kalkulator dan dua variabel input a dan b.
  4. Bagian Act: satu baris kalkulator.tambah(a, b) yang menghasilkan hasil.
  5. Bagian Assert: panggilan cekHasil yang membandingkan expected dan actual, lalu cekPengecualian yang memastikan pengecualian yang tepat dilempar.

Test Pyramid #

Test pyramid adalah panduan distribusi test: paling banyak unit test di dasar, sedikit integration test di tengah, sangat sedikit end-to-end test di puncak. Alasannya murni praktis: unit test cepat dan stabil, e2e test lambat dan rapuh karena banyak dependensi eksternal. Jika piramida terbalik (banyak e2e, sedikit unit), build akan lambat dan sering gagal karena alasan di luar kendali kode.

Referensi Lanjutan #

  • kotlin.test stdlib API — rujukan resmi paket kotlin.test untuk assertion lintas platform yang dipakai sebagai fondasi pengujian
  • JUnit 5 user guide — panduan resmi JUnit Jupiter yang menjelaskan anotasi @Test, lifecycle, dan runner untuk pengujian JVM

Selanjutnya #

Setelah memahami pola AAA dan test pyramid, langkah berikutnya adalah memilih assertion library. Chapter kotlin-test akan memperkenalkan paket kotlin.test — lapisan assertion sederhana yang berjalan di multiplatform.