Fungsi suspend yang menunggu dengan delay menimbulkan masalah baru pada pengujian, karena penundaan sungguhan membuat pengujian lambat tanpa memberi informasi apa pun. Pustaka kotlinx-coroutines-test menyelesaikannya dengan runTest, yaitu runner pengujian yang memakai waktu virtual (virtual time), sehingga penundaan berapa detik pun dijalankan seketika. Topik ini membahas masalah penungguan nyata, cara kerja runTest dengan jam virtual, dan cara membaca hasilnya pada pengujian yang benar-benar dieksekusi.
Delay dan mahalnya menunggu sungguhan#
Fungsi buatLaporan pada topik ini menunda pekerjaannya selama 10.000 milidetik sebelum mengembalikan hasil, pola yang wajar pada kode yang mengirim permintaan jaringan atau menunggu antrean. Pengujian yang menjalankan fungsi tersebut dengan runBlocking memakai waktu sungguhan, sehingga setiap eksekusi benar-benar menunggu sepuluh detik.
// tidak-dijalankan
@Test
fun laporanDenganRunBlocking() = runBlocking {
val laporan = buatLaporan()
assertEquals("laporan-2026", laporan)
}Pengujian seperti itu masih benar secara logika, tetapi biayanya menumpuk. Sebuah suite yang memuat sepuluh pengujian dengan pola yang sama membutuhkan waktu menunggu sekitar seratus detik untuk setiap eksekusi penuh, padahal penundaan itu bukan bagian yang ingin diperiksa. Yang perlu diuji adalah urutan kejadian sesudah penundaan, bukan lamanya penundaan itu sendiri, dan perbedaan inilah yang dipisahkan oleh waktu virtual.
runTest dan waktu virtual#
Fungsi runTest menjalankan bloknya di dalam TestScope, yaitu lingkup coroutine dengan penjadwal berbasis waktu virtual. Ketika fungsi suspend di dalamnya memanggil delay, penjadwal tidak menunggu sungguhan, melainkan melompatkan jam virtual sebesar penundaan yang diminta, kemudian melanjutkan coroutine yang sedang menunggu. Kelas berikut memuat dua pengujian yang memanggil buatLaporan dengan penundaan sepuluh detik, dan keduanya tetap berjalan seketika.
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.delay
import kotlinx.coroutines.test.runTest
import org.junit.jupiter.api.Assertions.assertEquals
import org.junit.jupiter.api.Assertions.assertTrue
import org.junit.jupiter.api.Test
suspend fun buatLaporan(): String {
delay(10_000)
return "laporan-2026"
}
@OptIn(ExperimentalCoroutinesApi::class)
class PengujianLaporan {
@Test
fun laporanTerbentukTanpaMenunggu() = runTest {
val mulai = System.nanoTime()
val laporan = buatLaporan()
val durasiDetik = (System.nanoTime() - mulai) / 1_000_000_000L
assertEquals("laporan-2026", laporan)
assertTrue(durasiDetik < 1)
println("Selesai tanpa menunggu sungguhan, durasi terukur $durasiDetik detik")
}
@Test
fun jamVirtualNaikSepuluhDetik() = runTest {
assertEquals(0L, testScheduler.currentTime)
buatLaporan()
assertEquals(10_000L, testScheduler.currentTime)
println("Jam virtual berakhir di ${testScheduler.currentTime} ms")
}
}Anotasi @OptIn menandai pemakaian API eksperimental, yaitu properti testScheduler pada pustaka coroutines-test. Perintah berikut mengompilasi kelas pengujian lalu mengeksekusinya melalui ConsoleLauncher dengan tiga jar pada classpath, yaitu junit-jupiter-api untuk kompilasi, kemudian kotlinx-coroutines-core dan kotlinx-coroutines-test untuk runtime.
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 pengujian dengan coroutines-test pada classpath
kotlinc Pengujian.kt -cp "$LIBS/junit-jupiter-api-5.14.4.jar:$LIBS/kotlinx-coroutines-core-jvm-1.11.0.jar:$LIBS/kotlinx-coroutines-test-jvm-1.11.0.jar" -d kelas
# coroutines-core dan coroutines-test ikut pada classpath runtime
java -cp "$LIBS/junit-platform-console-standalone-1.14.4.jar:kelas:$STDLIB:$LIBS/kotlinx-coroutines-core-jvm-1.11.0.jar:$LIBS/kotlinx-coroutines-test-jvm-1.11.0.jar" org.junit.platform.console.ConsoleLauncher execute --details=summary --select-class PengujianLaporan --disable-banner --disable-ansi-colors | grep -v "Test run finished"Selesai tanpa menunggu sungguhan, durasi terukur 0 detik
Jam virtual berakhir di 10000 ms
[ 4 containers found ]
[ 0 containers skipped ]
[ 4 containers started ]
[ 0 containers aborted ]
[ 4 containers successful ]
[ 0 containers failed ]
[ 2 tests found ]
[ 0 tests skipped ]
[ 2 tests started ]
[ 0 tests aborted ]
[ 2 tests successful ]
[ 0 tests failed ]Baris pertama keluaran adalah bukti utamanya, yaitu pengujian yang memuat delay(10_000) selesai dengan durasi terukur 0 detik. Durasi dihitung dengan System.nanoTime di dalam pengujian lalu dibulatkan ke detik, sehingga angkanya stabil dan tidak menampilkan milidetik mentah. Ringkasan akhir menyatakan kedua pengujian lulus, dan tidak ada satu detik pun yang benar-benar ditunggu.
Membaca jam virtual#
Properti testScheduler.currentTime menampilkan posisi jam virtual dalam milidetik, dan nilainya dimulai dari 0 pada setiap runTest. Pengujian kedua memeriksa posisi awal itu, memanggil buatLaporan, lalu memeriksa kembali posisi jamnya. Hasilnya 10000 milidetik, persis sebesar penundaan yang diminta, padahal durasi nyata eksekusinya mendekati nol.
Jam virtual menjadikan penjadwalan coroutine dapat diasersi seperti nilai biasa. Dua penundaan berurutan dijumlahkan, sedangkan penundaan yang berjalan berdampingan diambil nilai maksimumnya, karena penjadwal hanya melompat ke waktu kejadian terdekat yang sedang menunggu. Dengan demikian, skenario seperti percobaan ulang yang berjarak tertentu dapat diuji tanpa menunggu sama sekali.
Ringkasan#
runBlockingmenjalankandelaydengan waktu sungguhan, sehingga pengujian yang menunggu penundaan panjang menjadi lambat tanpa manfaat.runTestmemakai penjadwal dengan waktu virtual, sehinggadelayberapa pun dijalankan seketika dan urutan kejadiannya tetap teruji.- Properti
testScheduler.currentTimememuat posisi jam virtual dan dapat diasersi, dengan 10000 milidetik sebagai hasil penundaan sepuluh detik. - Pustaka kotlinx-coroutines-test diperlukan pada classpath kompilasi dan runtime, berdampingan dengan kotlinx-coroutines-core.
Referensi#
- kotlinx-coroutines-test — JetBrains — Dokumentasi resmi pustaka coroutines-test, mencakup runTest dan waktu virtual.
- JUnit 5 User Guide — JUnit Team — Panduan eksekusi kelas pengujian JUnit 5 yang dipakai topik ini.