munit adalah framework pengujian untuk Scala yang menyediakan suite, asersi, dan dukungan properti ScalaCheck sekaligus. Topik ini membahas penulisan suite dengan FunSuite, penggunaan assertEquals dan intercept, fixture untuk persiapan dan pembersihan sumber daya, bentuk keluaran ketika sebuah tes gagal, serta pemeriksaan properti dengan artefak munit-scalacheck. Seluruh contoh dijalankan dengan perintah scala-cli test, sehingga pengujian berlangsung dari terminal tanpa build tool tambahan.
Suite pertama dengan FunSuite#
Suite pengujian pada munit adalah kelas yang mewarisi munit.FunSuite, dan setiap kasus uji dinyatakan dengan metode test yang menerima nama tes beserta blok isinya. Nama tes ditulis sebagai deskripsi perilaku yang diuji agar keluaran pengujian terbaca seperti daftar klaim tentang program. Berkas uji membawa direktif dependensi munit karena kode pengujian tidak ikut terkompilasi bersama kode program biasa.
//> using dep org.scalameta::munit:1.1.1
import java.nio.file.{Files, Path}
import scala.io.Source
object Statistik:
def rataRata(nilai: List[Double]): Double =
nilai.sum / nilai.size
def bagi(a: Int, b: Int): Int = a / b
class StatistikTest extends munit.FunSuite:
val berkasSementara = FunFixture[Path](
setup = _ => Files.createTempFile("nilai", ".txt"),
teardown = berkas => { Files.deleteIfExists(berkas); () }
)
test("rata-rata tiga nilai") {
assertEquals(Statistik.rataRata(List(8.0, 9.0, 10.0)), 9.0)
}
test("pembagian dengan nol melempar pengecualian") {
val t = intercept[java.lang.ArithmeticException] {
Statistik.bagi(10, 0)
}
assertEquals(t.getMessage, "/ by zero")
}
berkasSementara.test("rata-rata dari isi berkas sementara") { berkas =>
Files.writeString(berkas, "10 20 30")
val angka = Source.fromFile(berkas.toFile).getLines().next().split(" ").toList.map(_.toDouble)
assertEquals(Statistik.rataRata(angka), 20.0)
}# durasi tiap tes dihapus dengan sed agar keluaran stabil untuk dibandingkan
scala-cli test uji.test.scala 2>/dev/null | sed -E 's/[0-9]+(\.[0-9]+)?s//'StatistikTest:
+ rata-rata tiga nilai
+ pembagian dengan nol melempar pengecualian
+ rata-rata dari isi berkas sementaraKeluaran menampilkan nama suite, lalu satu baris untuk tiap tes dengan tanda + yang menandai keberhasilan. Durasi eksekusi tiap tes ditulis setelah namanya, misalnya 0.013s, dan bagian durasi itu dihapus dengan sed pada perintah di atas karena nilainya berubah pada setiap eksekusi. Nama tes dan urutannya tetap, sehingga keluaran yang tersisa dapat dibandingkan dari satu eksekusi ke eksekusi berikutnya.
Asersi assertEquals dan intercept#
Metode assertEquals(obtained, expected) membandingkan dua nilai dan menggagalkan tes apabila keduanya berbeda. Asersi ini mencatat lokasi pemanggilannya melalui parameter implicit bertipe munit.Location, sehingga laporan kegagalan menunjuk baris kode yang tepat tanpa pencarian manual. Pada tes pertama di uji.test.scala, nilai kembalian rataRata(List(8.0, 9.0, 10.0)) dibandingkan dengan 9.0, dan kesamaan ketat itulah yang dituntut setiap kali tes berjalan.
Metode intercept[T] { ... } menjalankan bloknya dan menuntut eksepsi bertipe T dilempar dari dalam blok tersebut. Apabila tidak ada eksepsi atau tipenya berbeda, tes gagal, sedangkan jika eksepsinya muncul, intercept mengembalikan eksepsi itu untuk diperiksa lebih lanjut. Tes kedua memanfaatkan hal tersebut dengan membandingkan pesan eksepsi pembagian bilangan bulat dengan nol pada JVM, yaitu / by zero, sehingga tipe eksepsi dan isi pesannya sama-sama terverifikasi.
Fixture untuk berkas sementara#
Fixture menyiapkan sumber daya sebelum tes berjalan dan membersihkannya setelah tes selesai, sehingga kode persiapan tidak berulang di setiap tes. munit menyediakan FunFixture, yaitu fixture fungsional yang menerima dua fungsi, setup untuk membuat sumber daya dan teardown untuk menghapusnya. Pada uji.test.scala, fixture berkasSementara membuat berkas sementara melalui Files.createTempFile dan menghapusnya pada teardown.
Tes yang menggunakan fixture dinyatakan dengan berkasSementara.test("nama") { nilai => ... }, dan sumber daya yang telah disiapkan diterima sebagai argumen bloknya. munit memanggil teardown meskipun isi tes gagal, sehingga berkas sementara tidak tertinggal di sistem. Apabila sebuah tes memerlukan dua sumber daya, FunFixture.map2 menggabungkan dua fixture menjadi satu, dan fixture yang sama tetap dapat digunakan oleh beberapa tes dalam suite.
Keluaran ketika tes gagal#
Bentuk keluaran kegagalan perlu dikenali karena laporan itulah yang dibaca saat memperbaiki kode. Berkas berikut memuat satu tes yang lolos dan satu tes yang sengaja dibuat keliru, yaitu menuntut hasil 5 untuk pengurangan yang menghasilkan 4.
//> using dep org.scalameta::munit:1.1.1
class PenguranganTest extends munit.FunSuite:
def kurang(a: Int, b: Int): Int = a - b
test("kurang(10, 2) sama dengan 8") {
assertEquals(kurang(10, 2), 8)
}
test("kurang(7, 3) sama dengan 4") {
assertEquals(kurang(7, 3), 5) // sengaja keliru agar gagal
}# durasi dihapus dengan sed agar keluaran kegagalannya stabil
scala-cli test uji-gagal.test.scala 2>/dev/null | sed -E 's/[0-9]+(\.[0-9]+)?s//'PenguranganTest:
+ kurang(10, 2) sama dengan 8
==> X PenguranganTest.kurang(7, 3) sama dengan 4 munit.ComparisonFailException: /home/kal/my-project/content/scala/alat-dan-pengujian/pengujian-munit/uji-gagal.test.scala:11
10: test("kurang(7, 3) sama dengan 4") {
11: assertEquals(kurang(7, 3), 5) // sengaja keliru agar gagal
12: }
values are not the same
=> Obtained
4
=> Diff (- expected, + obtained)
-5
+4
at munit.FunSuite.assertEquals(FunSuite.scala:13)
at PenguranganTest.$init$$$anonfun$2(uji-gagal.test.scala:11)Tanda ==> X menandai tes yang gagal, diikuti nama lengkapnya dan lokasi kegagalan sampai ke nomor baris. Baris-baris berikutnya menyalin potongan kode sumber di sekitar lokasi tersebut, menyatakan bahwa values are not the same, lalu menampilkan nilai yang diperoleh beserta diff terhadap nilai yang diharapkan. Dari laporan ini terbaca langsung bahwa kurang(7, 3) menghasilkan 4 sedangkan tes menuntut 5, sehingga arah perbaikannya jelas tanpa membuka kode terlebih dahulu.
Properti dengan ScalaCheck#
Pengujian berbasis properti (property-based testing) membalik arah pengujian konvensional. Penulis kode tidak menyediakan pasangan masukan dan keluaran satu per satu, melainkan menyatakan sifat yang harus berlaku untuk masukan apa pun, kemudian framework menghasilkan ratusan kelompok nilai secara acak untuk mengujinya. ScalaCheck adalah pustaka Scala untuk gaya pengujian ini, dan artefak munit-scalacheck menyatukannya dengan munit, sehingga properti dinyatakan dengan metode property di dalam suite yang mewarisi ScalaCheckSuite.
//> using dep org.scalameta::munit:1.1.1
//> using dep org.scalameta::munit-scalacheck:1.3.1
import org.scalacheck.Prop.forAll
object Statistik:
def bagi(a: Int, b: Int): Int = a / b
class PropertiStatistikTest extends munit.ScalaCheckSuite:
property("pembalikan ganda mengembalikan daftar awal") {
forAll { (xs: List[Int]) =>
assertEquals(xs.reverse.reverse, xs)
}
}
property("pembagian dengan satu mengembalikan bilangan asal") {
forAll { (n: Int) =>
assertEquals(Statistik.bagi(n, 1), n)
}
}# durasi dihapus dengan sed agar keluaran properti stabil
scala-cli test uji-properti.test.scala 2>/dev/null | sed -E 's/[0-9]+(\.[0-9]+)?s//'Test run PropertiStatistikTest started
PropertiStatistikTest: finished
Test run PropertiStatistikTest finished: 0 failed, 0 ignored, 2 totalDua properti pada berkas itu berlaku untuk masukan apa pun yang dihasilkan ScalaCheck. Properti pertama menuntut xs.reverse.reverse selalu kembali menjadi xs, sedangkan properti kedua menuntut pembagian dengan satu selalu mengembalikan bilangan asalnya. Baris ringkasan menyatakan dua properti dijalankan dan tidak ada yang gagal. Apabila sebuah kelompok nilai melanggar properti, ScalaCheck menyusutkan (shrink) masukannya ke contoh terkecil yang masih melanggar, lalu contoh itu dilaporkan pada keluaran kegagalan.
Ringkasan#
- Suite munit adalah kelas yang mewarisi
FunSuite, dan tiap tes dinyatakan dengantestyang memuat nama deskriptif beserta asersinya. assertEqualsmembandingkan dua nilai dengan laporan kegagalan yang menunjuk baris asalnya, sedangkaninterceptmenuntut eksepsi bertipe tertentu dan mengembalikannya untuk diperiksa.FunFixturememisahkan setup dan teardown dari isi tes, dan teardown tetap dipanggil ketika tes gagal.- Artefak
munit-scalacheckmenambahkanpropertydenganforAll, sehingga sifat umum kode diuji terhadap banyak nilai yang dihasilkan otomatis.
Referensi#
- munit — Scalameta — Dokumentasi resmi kerangka uji munit.
- Getting started — munit — Penyiapan dependensi munit dan penulisan suite pertama.
- Fixtures — munit
— Jenis-jenis fixture, termasuk
FunFixturedengan setup dan teardown. - ScalaCheck — Typelevel — Dokumentasi resmi ScalaCheck untuk pengujian berbasis properti.