Membangun kebiasaan testing sejak awal: MUnit sebagai framework ringan dengan assertEquals, struktur suite test di sbt, pengenalan ScalaTest untuk gaya BDD, serta debugging interaktif dengan breakpoints di Metals dan eksperimen cepat di REPL.

Setelah di episode 9 kalian membangun proyek sbt pertama, sekarang kita memasang pagar pengaman: testing. Jika episod-episode sebelumnya mengajarkan menulis kode, episode ini mengajarkan membuktikan kode itu benar — dan itu justru lebih penting di dunia produksi.
Mengapa testing penting di Scala? Karena bahasa ini memberi kalian type system yang kuat, tetapi tipe tidak menjamin logika benar. Keberuntungannya, sifat pure function yang kita pelajari sejak episode 4 membuat kode Scala sangat mudah diuji: fungsi murni selalu memberi output yang sama untuk input yang sama — ideal untuk assertion yang deterministik.
MUnit adalah framework testing Scala yang ringan dan modern. Tambahkan ke build.sbt:
lazy val root = project
.in(file("."))
.settings(
name := "scala-app",
version := "0.1.0",
scalaVersion := "3.3.8",
libraryDependencies ++= Seq(
"org.scalameta" %% "munit" % "1.0.0" % Test
)
)Suffix % Test berarti dependency hanya dipakai saat fase test — tidak ikut ke artefak produksi.
Kode produksi sederhana yang akan kita uji:
object Calculator:
def add(a: Int, b: Int): Int = a + b
def divide(a: Int, b: Int): Either[String, Int] =
if b == 0 then Left("pembagian nol")
else Right(a / b)Test MUnit adalah class yang meng-extends FunSuite, dengan method test:
class CalculatorSuite extends munit.FunSuite:
test("add menjumlahkan dua angka") {
assertEquals(Calculator.add(2, 3), 5)
}
test("add menangani angka negatif") {
assertEquals(Calculator.add(-1, -1), -2)
}
test("divide mengembalikan Left untuk pembagian nol") {
assertEquals(Calculator.divide(10, 0), Left("pembagian nol"))
}
test("divide menghitung dengan benar") {
assertEquals(Calculator.divide(10, 2), Right(5))
}Jalankan dengan sbt test:
[info] CalculatorSuite:
[info] + add menjumlahkan dua angka
[info] + add menangani angka negatif
[info] + divide mengembalikan Left untuk pembagian nol
[info] + divide menghitung dengan benar
[info] All tests passed.MUnit menampilkan hasil per-test, dan assertion gagal memberikan pesan kontekstual yang jelas: nilai yang diharapkan vs yang aktual.
| Assertion | Fungsi |
|---|---|
assertEquals(actual, expected) | Kesetaraan nilai |
assert(condition) | Kondisi boolean |
assertNotEquals(a, b) | Nilai tidak sama |
intercept[Exception] | Memastikan exception dilempar |
fail("pesan") | Gagalkan test secara eksplisit |
test("fail untuk kasus tak terduga") {
assert(Calculator.add(0, 0) == 0)
}
test("intercept memvalidasi exception") {
intercept[ArithmeticException] {
1 / 0
}
}Test yang baik mengikuti pola AAA — Arrange, Act, Assert:
test("hitung total keranjang") {
val items = List(5000.0, 3000.0, 2000.0) // Arrange
val total = items.sum // Act
assertEquals(total, 10000.0) // Assert
}Beberapa prinsip untuk diingat:
ScalaTest adalah framework lain yang menawarkan berbagai gaya. Yang paling populer adalah AnyFlatSpec dengan gaya BDD:
import org.scalatest.funsuite.AnyFunSuite
class CalculatorSpec extends AnyFunSuite:
test("Calculator.add harus menjumlahkan") {
assert(Calculator.add(2, 3) == 5)
}import org.scalatest.flatspec.AnyFlatSpec
class CalculatorSpec extends AnyFlatSpec:
"Calculator.add" should "menjumlahkan dua angka" in {
assert(Calculator.add(2, 3) == 5)
}Pilih satu framework dan konsisten: MUnit untuk project yang ingin ringan dan modern, ScalaTest jika kalian suka gaya BDD yang deskriptif. Keduanya bisa dipakai bersamaan dalam satu proyek.
Tip
Untuk series ini, MUnit adalah pilihan utama — ringan, cepat, dan assertion-nya menampilkan diff yang jelas saat gagal. Pola yang dipelajari (suite, assertion, AAA) 100% berlaku juga di ScalaTest.
Metals mengintegrasikan DAP (Debug Adapter Protocol) ke VS Code. Alurnya:
@main def run(): Unit =
val prices = List(5000.0, 3000.0)
val total = prices.sum
println(s"Total: $total") // pasang breakpoint di baris iniUntuk masalah kecil, REPL sering lebih cepat daripada breakpoint — tiru logika yang bermasalah di sesi interaktif:
scala> List(1,2,3).map(_ * 2)
val res0: List[Int] = List(2, 4, 6)
scala> :quitJika hasil REPL sama dengan yang kalian harapkan tetapi program produksi tidak, masalahnya ada di alur sekitarnya — bukan di logika murni tersebut. Pola ini memanfaatkan kekuatan pure function: reproduksi langsung tanpa setup kompleks.
Warning
Saat debugging dengan breakpoint, pastikan Metals sudah mengimport project (banner "Import build" di pojok kanan bawah). Tanpa import, breakpoint sering tidak aktif karena bytecode yang dijalankan berbeda dengan source yang kalian lihat.
sbt test bisa menjalankan dengan paralelisme.assertEquals(actual, expected): nilai aktual di kiri, harapan di kanan, agar pesan error terbaca benar.src/test tidak boleh dipakai dari src/main.Inti yang harus dibawa pulang:
FunSuite, test, dan assertEquals.% Test di build.sbt; jalankan dengan sbt test.Di episode 11 selanjutnya, kita memasuki topik yang membedakan Scala dari bahasa JVM lain: functional effects dengan cats-effect dan ZIO — komposisi program pure, resource safety, dan cancellation. Sampai jumpa di episode 11!