Pengujian unit memeriksa unit terkecil kode, misalnya satu kelas atau satu fungsi, secara otomatis dan berulang. JUnit 5 adalah framework pengujian standar pada platform JVM yang mengeksekusi fungsi-fungsi bertanda tertentu lalu melaporkan mana yang lulus dan mana yang gagal. Topik ini membahas penulisan fungsi pengujian dengan anotasi @Test, asersi (assertion) dari kelas Assertions, siklus hidup pengujian yang mengatur pembuatan instans, serta cara membaca laporan kegagalan dari terminal.
Kelas yang diuji dan fungsi @Test#
Kelas yang diuji pada topik ini adalah kalkulator statistik sederhana yang menghitung rata-rata dari sekumpulan nilai ujian. Fungsi rataRata menolak daftar kosong dengan melempar IllegalArgumentException melalui require, sehingga kelas ini cukup kaya untuk memperagakan beberapa jenis asersi.
class KalkulatorNilai {
fun rataRata(nilai: List<Double>): Double {
require(nilai.isNotEmpty()) { "Daftar nilai tidak boleh kosong" }
return nilai.sum() / nilai.size
}
}Fungsi pengujian ditandai dengan anotasi @Test, dan setiap fungsi tersebut menguji satu perilaku yang terpisah. Asersi membandingkan nilai yang diharapkan dengan nilai yang benar-benar dihasilkan, lalu melempar exception bila keduanya berbeda. Kelas pengujian berikut memuat tiga fungsi pengujian dengan tiga jenis asersi yang berbeda, yaitu assertEquals, assertThrows, dan assertTrue.
import org.junit.jupiter.api.Assertions.assertEquals
import org.junit.jupiter.api.Assertions.assertThrows
import org.junit.jupiter.api.Assertions.assertTrue
import org.junit.jupiter.api.DisplayName
import org.junit.jupiter.api.Test
class PengujianKalkulator {
companion object {
private var jumlahInstans = 0
}
init {
jumlahInstans += 1
println("Instans pengujian ke-$jumlahInstans dibuat")
}
@Test
fun rataRataDariTigaNilai() {
val kalkulator = KalkulatorNilai()
assertEquals(80.0, kalkulator.rataRata(listOf(70.0, 80.0, 90.0)))
}
@Test
fun rataRataSatuNilai() {
val kalkulator = KalkulatorNilai()
assertEquals(75.0, kalkulator.rataRata(listOf(75.0)))
}
@Test
@DisplayName("Daftar kosong memicu exception")
fun daftarKosong() {
val kalkulator = KalkulatorNilai()
val e = assertThrows(IllegalArgumentException::class.java) {
kalkulator.rataRata(emptyList())
}
assertTrue("tidak boleh kosong" in e.message.orEmpty())
}
}Anotasi @DisplayName mengganti nama fungsi dengan kalimat yang lebih terbaca pada laporan, sedangkan assertThrows memastikan sebuah blok kode melempar jenis exception yang diharapkan. Blok yang diteruskan ke assertThrows adalah lambda, sehingga pemanggilan yang seharusnya melempar exception ditulis apa adanya di dalamnya.
Menjalankan pengujian dari terminal#
Proyek Gradle menjalankan JUnit 5 melalui task test, sedangkan tanpa Gradle pengujian dapat dieksekusi langsung dengan junit-platform-console-standalone, yaitu jar yang memuat ConsoleLauncher beserta mesin pengujian JUnit Jupiter. Perintah berikut mengompilasi kelas yang diuji beserta kelas pengujinya, lalu mengeksekusi kelas PengujianKalkulator secara eksplisit lewat pemilih --select-class.
export PATH=/home/kal/my-project/tools/kotlinc/bin:/home/kal/my-project/tools/jdk-merged/bin:$PATH
export LIBS=/home/kal/my-project/tools/libs
export STDLIB=/home/kal/my-project/tools/kotlinc/lib/kotlin-stdlib.jar
# kompilasi kelas yang diuji beserta kelas pengujian ke direktori kelas
kotlinc KalkulatorNilai.kt Pengujian.kt -cp "$LIBS/junit-jupiter-api-5.14.4.jar" -d kelas
# menjalankan seluruh fungsi pengujian melalui ConsoleLauncher
java -cp "$LIBS/junit-platform-console-standalone-1.14.4.jar:kelas:$STDLIB" org.junit.platform.console.ConsoleLauncher execute --details=summary --select-class PengujianKalkulator --disable-banner --disable-ansi-colors | grep -v "Test run finished"Instans pengujian ke-1 dibuat
Instans pengujian ke-2 dibuat
Instans pengujian ke-3 dibuat
[ 4 containers found ]
[ 0 containers skipped ]
[ 4 containers started ]
[ 0 containers aborted ]
[ 4 containers successful ]
[ 0 containers failed ]
[ 3 tests found ]
[ 0 tests skipped ]
[ 3 tests started ]
[ 0 tests aborted ]
[ 3 tests successful ]
[ 0 tests failed ]Keluaran tersebut dibagi menjadi dua bagian. Tiga baris pertama berasal dari println di dalam blok init kelas pengujian, sedangkan dua belas baris berkurung siku adalah ringkasan akhir ConsoleLauncher. Bagian ringkasan memisahkan container, yaitu mesin pengujian dan kelas pengujian, dari test, yaitu fungsi bertanda @Test. Angka 3 tests successful dengan 0 tests failed menyatakan seluruh fungsi pengujian lulus. Baris yang menyatakan durasi total eksekusi dibuang melalui grep agar keluaran tetap stabil di antara eksekusi ulang.
Siklus hidup pengujian dan instans baru#
Tiga baris keluaran di atas juga membuktikan aturan siklus hidup (lifecycle) JUnit 5, yaitu satu instans baru dibuat untuk setiap metode pengujian. Penghitung jumlahInstans dideklarasikan pada companion object sehingga dihitung sepanjang kelas, dan nilainya naik dari 1 sampai 3 karena blok init dieksekusi tiga kali. Tiga instans berarti JUnit benar-benar membuat objek pengujian baru untuk setiap fungsi @Test.
Aturan ini menjaga isolasi antarpengujian. Properti yang disimpan pada instans, seperti kalkulator, tidak membawa nilai sisa dari pengujian sebelumnya, sehingga urutan eksekusi tidak mengubah hasilnya. Ketika persiapan yang sama dibutuhkan oleh beberapa pengujian, kode tersebut dipindahkan ke fungsi bertanda @BeforeEach yang dijalankan sebelum setiap pengujian, berpasangan dengan @AfterEach yang dijalankan sesudahnya.
Membaca laporan kegagalan#
Laporan kegagalan adalah bagian terpenting dari framework pengujian, karena di situlah letak kesalahan ditunjukkan. Kelas berikut sengaja memuat ekspektasi yang keliru, yaitu mengharapkan 80.0 dari rata-rata empat nilai yang sebenarnya 85.0.
import org.junit.jupiter.api.Assertions.assertEquals
import org.junit.jupiter.api.Test
class PengujianGagal {
private val kalkulator = KalkulatorNilai()
@Test
fun rataRataDariEmpatNilai() {
assertEquals(80.0, kalkulator.rataRata(listOf(70.0, 80.0, 90.0, 100.0)))
}
}# kompilasi ulang dengan kelas pengujian yang sengaja dibuat gagal
# (variabel $LIBS dan $STDLIB dari blok sebelumnya masih berlaku di sesi terminal yang sama)
kotlinc KalkulatorNilai.kt PengujianGagal.kt -cp "$LIBS/junit-jupiter-api-5.14.4.jar" -d kelas-gagal
# ConsoleLauncher mengeksekusi kelas yang gagal, lalu ringkasannya ditampilkan
java -cp "$LIBS/junit-platform-console-standalone-1.14.4.jar:kelas-gagal:$STDLIB" org.junit.platform.console.ConsoleLauncher execute --select-class PengujianGagal --disable-banner --disable-ansi-colors | grep -v "Test run finished"╷
├─ JUnit Platform Suite ✔
├─ JUnit Jupiter ✔
│ └─ PengujianGagal ✔
│ └─ rataRataDariEmpatNilai() ✘ expected: <80.0> but was: <85.0>
└─ JUnit Vintage ✔
Failures (1):
JUnit Jupiter:PengujianGagal:rataRataDariEmpatNilai()
MethodSource [className = 'PengujianGagal', methodName = 'rataRataDariEmpatNilai', methodParameterTypes = '']
=> org.opentest4j.AssertionFailedError: expected: <80.0> but was: <85.0>
org.junit.jupiter.api.AssertionFailureBuilder.build(AssertionFailureBuilder.java:151)
org.junit.jupiter.api.AssertionFailureBuilder.buildAndThrow(AssertionFailureBuilder.java:132)
org.junit.jupiter.api.AssertEquals.failNotEqual(AssertEquals.java:197)
org.junit.jupiter.api.AssertEquals.assertEquals(AssertEquals.java:70)
org.junit.jupiter.api.AssertEquals.assertEquals(AssertEquals.java:65)
org.junit.jupiter.api.Assertions.assertEquals(Assertions.java:889)
PengujianGagal.rataRataDariEmpatNilai(PengujianGagal.kt:9)
java.base/java.lang.reflect.Method.invoke(Method.java:580)
java.base/java.util.ArrayList.forEach(ArrayList.java:1596)
java.base/java.util.ArrayList.forEach(ArrayList.java:1596)
[ 4 containers found ]
[ 0 containers skipped ]
[ 4 containers started ]
[ 0 containers aborted ]
[ 4 containers successful ]
[ 0 containers failed ]
[ 1 tests found ]
[ 0 tests skipped ]
[ 1 tests started ]
[ 0 tests aborted ]
[ 0 tests successful ]
[ 1 tests failed ]Pohon di bagian atas menandai fungsi yang gagal dengan simbol ✘ beserta ringkasannya, yaitu expected: <80.0> but was: <85.0>. Bagian Failures kemudian merinci exception org.opentest4j.AssertionFailedError dan baris penyebabnya, yaitu PengujianGagal.kt baris 9, tepat pada pemanggilan assertEquals yang ekspektasinya keliru. Ringkasan akhir menegaskan 1 tests failed, dan perbaikannya adalah mengubah nilai ekspektasi menjadi 85.0.
Ringkasan#
- Fungsi pengujian ditandai
@Test, dan asersi dari kelasAssertionsmembandingkan nilai harapan dengan nilai hasil eksekusi. - junit-platform-console-standalone menyediakan ConsoleLauncher untuk mengeksekusi kelas pengujian dari terminal tanpa Gradle.
- JUnit 5 membuat satu instans kelas pengujian baru untuk setiap metode
@Test, sehingga status antarpengujian terisolasi secara default. - Laporan kegagalan menampilkan nilai yang diharapkan, nilai yang diperoleh, dan baris kode penyebabnya.
Referensi#
- JUnit 5 User Guide — JUnit Team — Panduan resmi penulisan pengujian, asersi, dan siklus hidup JUnit 5.