Kotest adalah framework pengujian untuk Kotlin yang menawarkan dua hal di luar kebiasaan JUnit 5, yaitu banyak pilihan gaya penulisan pengujian dan dukungan property testing. Gaya penulisan yang beragam membuat struktur pengujian mengikuti cara berpikir penulisnya, sedangkan property testing menguji sebuah properti terhadap ratusan kasus acak, bukan hanya beberapa contoh tetap. Topik ini membahas gaya-gaya penulisan Kotest, konsep property testing, dan properti yang sama yang dipraktikkan dengan Kotlin standar agar bisa dijalankan langsung.
Banyak gaya penulisan pengujian#
Pada JUnit 5, satu pengujian selalu berupa metode dalam sebuah kelas. Kotest memandang pengujian sebagai blok kode di dalam spesifikasi (spec), sehingga nama kasus uji boleh berupa kalimat biasa tanpa aturan penamaan fungsi. Gaya yang tersedia antara lain FunSpec, StringSpec, ShouldSpec, dan BehaviorSpec, dan setiap gaya menata blok pengujian dengan bentuk yang berbeda. Dua contoh berikut memperlihatkan pengujian yang sama pada dua gaya yang paling umum.
// tidak-dijalankan
import io.kotest.core.spec.style.FunSpec
import io.kotest.matchers.shouldBe
class PengujianRataRata : FunSpec({
test("rata-rata tiga nilai") {
rataRata(listOf(70.0, 80.0, 90.0)) shouldBe 80.0
}
})// tidak-dijalankan
import io.kotest.core.spec.style.StringSpec
import io.kotest.matchers.shouldBe
class PengujianRataRata : StringSpec({
"rata-rata dari satu nilai adalah nilai itu sendiri" {
rataRata(listOf(75.0)) shouldBe 75.0
}
})Tabel berikut merangkum perbedaan gaya tersebut agar pilihannya dapat disesuaikan dengan bentuk pengujian yang sedang ditulis.
| Gaya | Bentuk deklarasi | Cocok untuk |
|---|---|---|
| FunSpec | test("nama") { } | Gaya paling dekat dengan JUnit, mudah dibaca tim campuran |
| StringSpec | "nama kasus" { } | Kasus uji berjumlah banyak dengan nama berupa kalimat |
| ShouldSpec | should("nama") { } | Gaya naratif berbahasa Inggris dengan kata should |
| BehaviorSpec | given / when / then | Perilaku yang punya tiga tahap, yaitu keadaan, aksi, dan hasil |
Perbandingan matcher juga relevan di sini. Kotest memakai shouldBe dan keluarganya alih-alih assertEquals, dan pesan kegagalannya menampilkan kedua nilai yang dibandingkan. Pemilihan gaya tidak memengaruhi cara pengujian dijalankan, karena semua gaya menghasilkan jenis spesifikasi yang sama bagi mesin pengujian.
Property testing#
Property testing menguji pernyataan yang harus benar untuk seluruh ruang masukan, lalu membiarkan framework menghasilkan masukannya. Pernyataan tersebut disebut properti, misalnya rata-rata sebuah daftar selalu berada di antara nilai terkecil dan nilai terbesarnya. Kotest menjalankan properti tersebut terhadap ratusan kasus acak secara default, dan jumlah kasusnya dapat diatur. Bila ada satu saja kasus yang melanggar properti, pustaka menyusutkan (shrinking) masukannya menjadi kasus paling sederhana yang masih gagal, sehingga penyebab kegagalan lebih mudah dibaca.
Fungsi forAll pada pustaka property testing Kotest adalah pintu masuk utamanya. Blok yang diteruskan ke forAll menerima satu masukan acak lalu mengembalikan nilai boolean, dan generator masukannya dipilih dari jenis Arb.
// tidak-dijalankan
import io.kotest.property.Arb
import io.kotest.property.forAll
class PengujianProperti : StringSpec({
"menjumlahkan nol tidak mengubah bilangan" {
forAll(Arb.int()) { bilangan ->
bilangan + 0 == bilangan
}
}
})Kode di atas tidak dieksekusi pada topik ini karena jar Kotest tidak tersedia di lingkungan kerjanya. Mekanisme dasarnya, yaitu menghasilkan banyak kasus acak lalu memeriksa satu properti pada setiap kasus, dapat direplikasi dengan Kotlin standar seperti pada bagian berikut.
Properti rata-rata yang diuji seratus kali#
Program berikut menguji properti yang sama dengan pengujian JUnit pada modul ini, yaitu rata-rata selalu berada di antara nilai terkecil dan terbesar. Seratus kasus dihasilkan secara acak dengan jumlah nilai yang berbeda-beda, dan setiap kasus diperiksa terhadap propertinya.
import kotlin.random.Random
fun rataRata(nilai: List<Double>): Double {
require(nilai.isNotEmpty()) { "Daftar nilai tidak boleh kosong" }
return nilai.sum() / nilai.size
}
fun main() {
val acak = Random(42)
var lulus = 0
repeat(100) { nomor ->
val banyak = acak.nextInt(1, 20)
val nilai = List(banyak) { acak.nextDouble(0.0, 100.0) }
val rata = rataRata(nilai)
if (rata >= nilai.min() && rata <= nilai.max()) {
lulus += 1
} else {
println("Kasus ke-$nomor gagal: rata-rata di luar rentang")
}
}
println("Kasus yang lulus: $lulus dari 100")
}export PATH=/home/kal/my-project/tools/kotlinc/bin:/home/kal/my-project/tools/jdk-merged/bin:$PATH
export STDLIB=/home/kal/my-project/tools/kotlinc/lib/kotlin-stdlib.jar
# kompilasi program pengujian properti lalu jalankan seluruhnya
kotlinc UjiProperti.kt -d out.jar
# program menguji 100 kasus acak dan mencetak jumlah kasus yang lulus
java -cp "out.jar:$STDLIB" UjiPropertiKtKasus yang lulus: 100 dari 100Keluaran menyatakan seluruh 100 kasus lulus terhadap propertinya. Angka tersebut bukan bukti matematis, melainkan bukti empiris sebatas kasus yang dihasilkan, dan itulah sifat property testing secara umum. Konstruktor Random(42) menerima benih (seed) sehingga deretan kasusnya selalu sama pada setiap eksekusi, kebiasaan yang berguna saat sebuah kegagalan perlu direproduksi.
Ringkasan#
- Kotest menyediakan beberapa gaya penulisan spesifikasi, antara lain FunSpec, StringSpec, ShouldSpec, dan BehaviorSpec, dengan matcher seperti
shouldBe. - Property testing memeriksa satu properti terhadap banyak kasus acak melalui
forAlldan generatorArb, lalu menyusutkan kasus yang gagal menjadi kasus paling sederhana. - Mekanisme dasar property testing dapat direplikasi dengan perulangan Kotlin standar, seperti seratus kasus acak yang diperiksa terhadap properti rata-rata.
- Kotest berjalan di atas JUnit Platform sehingga cara menjalankannya sama dengan pengujian JUnit 5.
Referensi#
- Kotest — Kotest Team — Dokumentasi resmi Kotest, mencakup gaya spesifikasi, matcher, dan property testing.
- JUnit 5 User Guide — JUnit Team — Panduan JUnit Platform yang menjadi dasar eksekusi pengujian Kotest.