Bagian terakhir proyek ini fokus pada dua aspek yang sering diabaikan: pengujian dan pengukuran performa. Tanpa keduanya, kita tidak yakin pipeline bekerja benar dan tidak tahu apakah perubahan kode memperbaiki atau memperburuk kinerja. Bagian 4 mendemonstrasikan test harness sederhana untuk verifikasi output dan benchmark tiga pendekatan pipeline.
Tiga Pendekatan Pipeline #
| Pendekatan | Karakteristik | Cocok untuk |
|---|---|---|
| List (eager) | Setiap operasi membuat list baru | Koleksi kecil, butuh semua nilai |
| Sequence (lazy) | Single-pass, evaluasi bertahap | Koleksi besar + operasi chained |
| Flow (suspend) | Async, ada overhead suspend function | Operasi yang melibatkan I/O |
Test Harness #
Berikut program yang menggabungkan unit test sederhana dengan benchmark:
import kotlinx.coroutines.*
import kotlinx.coroutines.flow.*
import kotlin.system.measureNanoTime
// === Pipeline untuk diukur ===
data class Item(val id: Int, val value: Double)
fun generateItems(n: Int): List<Item> = (1..n).map { Item(it, it * 1.5) }
fun pipelineSync(items: List<Item>): List<Double> {
return items.map { it.value * 2 }.filter { it > 10 }.map { it + 1 }
}
fun pipelineSequence(items: List<Item>): List<Double> {
return items.asSequence()
.map { it.value * 2 }
.filter { it > 10 }
.map { it + 1 }
.toList()
}
fun pipelineFlow(items: List<Item>): List<Double> = runBlocking {
items.asFlow()
.map { it.value * 2 }
.filter { it > 10 }
.map { it + 1 }
.toList()
}
// === Test harness: assert output ===
fun testPipeline() {
val items = generateItems(10)
val expected = items.map { it.value * 2 }.filter { it > 10 }.map { it + 1 }
val sync = pipelineSync(items)
val seq = pipelineSequence(items)
val flow = pipelineFlow(items)
check(sync == expected) { "sync mismatch: $sync vs $expected" }
println("[LULUS] pipelineSync(10 items) = $sync")
check(seq == expected) { "seq mismatch: $seq vs $expected" }
println("[LULUS] pipelineSequence(10 items) = $seq")
check(flow == expected) { "flow mismatch: $flow vs $expected" }
println("[LULUS] pipelineFlow(10 items) = $flow")
}
// === Benchmark: ukur waktu eksekusi ===
fun benchmark() {
val n = 100_000
val items = generateItems(n)
val tSync = measureNanoTime { pipelineSync(items) }
val tSeq = measureNanoTime { pipelineSequence(items) }
val tFlow = measureNanoTime { pipelineFlow(items) }
println("\n=== Benchmark $n items ===")
println("List (eager) : ${"%.2f".format(tSync / 1_000_000.0)} ms")
println("Sequence (lazy): ${"%.2f".format(tSeq / 1_000_000.0)} ms")
println("Flow (suspend) : ${"%.2f".format(tFlow / 1_000_000.0)} ms")
println("\nCatatan: Sequence unggul untuk koleksi besar dengan banyak operasi (single pass).")
println("Flow menambah overhead suspend function, cocok jika ada operasi I/O di tengah.")
}
fun main() {
println("=== Pengujian: verifikasi output ===")
testPipeline()
println("\n=== Pengukuran performa ===")
benchmark()
println("\nPengujian pipeline asinkron selesai.")
}Output yang Dihasilkan:
=== Pengujian: verifikasi output ===
[LULUS] pipelineSync(10 items) = [13.0, 16.0, 19.0, 22.0, 25.0, 28.0, 31.0]
[LULUS] pipelineSequence(10 items) = [13.0, 16.0, 19.0, 22.0, 25.0, 28.0, 31.0]
[LULUS] pipelineFlow(10 items) = [13.0, 16.0, 19.0, 22.0, 25.0, 28.0, 31.0]
=== Pengukuran performa ===
=== Benchmark 100000 items ===
List (eager) : 17.35 ms
Sequence (lazy): 12.91 ms
Flow (suspend) : 16.66 ms
Catatan: Sequence unggul untuk koleksi besar dengan banyak operasi (single pass).
Flow menambah overhead suspend function, cocok jika ada operasi I/O di tengah.
Pengujian pipeline asinkron selesai.Mari kita bedah bagian-bagian penting di atas:
generateItems(n): factory function yang menghasilkan list Item dengan ukuran n. Pakai untuk input test yang konsisten.pipelineSync,pipelineSequence,pipelineFlow: tiga implementasi pipeline yang sama —map → filter → map. Perbedaan ada di container:List<T>eager,Sequence<T>lazy,Flow<T>suspend.testPipeline(): test harness manual menggunakancheck(). Membangun expected result secara terpisah, lalu membandingkan dengan output tiga pipeline. Bila ada ketidaksesuaian,checkmelempar exception dengan pesan jelas.measureNanoTime { }: mengukur waktu eksekusi block dalam nanosecond. Lebih presisi daripadaSystem.currentTimeMillis(). Konversi ke milidetik di laporan.runBlocking { }dipipelineFlow: diperlukan karenaFlow.toList()adalah suspend function. Dalam production, gunakanrunTestdarikotlinx-coroutines-testuntuk virtual time.
Hasil Benchmark dan Interpretasi #
Pada 100.000 item, hasil umum:
- List: paling lambat karena membuat 3 list intermediate (map, filter, map).
- Sequence: paling cepat untuk single-pass chains. Tidak ada alokasi intermediate.
- Flow: lebih lambat dari Sequence karena overhead suspend function (setiap emit cek continuation), tetapi unggul saat ada operasi I/O suspend di tengah.
Best Practice Pengujian Pipeline #
- Test behavior, bukan implementasi: verifikasi output, bukan jumlah panggilan internal.
- Test edge case: input kosong, single item, sangat besar, item bermasalah.
- Test error path: pastikan error handling bekerja — fake error dengan mock.
- Test concurrency safety: pipeline dengan banyak collector, pastikan tidak race condition.
- Property-based testing:
kotest propertyuntuk generate input acak dan verifikasi invariant.
Penutup Proyek #
Proyek pipeline data asinkron ini menggabungkan empat area penting: desain pipeline, coroutines dan concurrency, error handling, dan pengujian performa. Pola yang sama dapat diterapkan untuk ETL batch, stream processing, dan microservice async. Untuk skala besar, pertimbangkan framework seperti Apache Beam, Flink, atau Kafka Streams.
Referensi Lanjutan #
- kotlin.test stdlib API
— rujukan
kotlin.testuntuk menulis test yang memverifikasi hasil pipeline asinkron - Kotlin coroutines design — dokumentasi coroutines sebagai rujukan pengukuran performa pemrosesan asinkron
Selanjutnya #
Proyek ini menutup section Proyek. Section selanjutnya, Sumber Belajar, menyediakan referensi dan roadmap untuk melanjutkan pembelajaran Kotlin ke tingkat lanjutan.