Minitest

Menulis assertion dengan Minitest yang tersedia bawaan Ruby dan disiplin pengujian dasar.

Daftar isi

Minitest adalah framework pengujian yang disertakan sebagai bagian dari pustaka standar Ruby, sehingga uji otomatis dapat ditulis tanpa menginstal gem tambahan. Topik ini membahas struktur berkas uji Minitest, assertion utama yang digunakan untuk memeriksa perilaku kode, cara menjalankan berkas uji dari terminal, serta pola penyusunan metode uji yang rapi. Setelah membacanya, sebuah kelas sederhana dapat diuji ulang secara otomatis setiap kali kodenya berubah.

Berkas uji pertama#

Minitest mulai bekerja setelah pustaka minitest/autorun dimuat. Pustaka tersebut memasang hook yang pada akhir program menjalankan seluruh kelas uji yang terdefinisi, sehingga berkas uji tidak memerlukan kode pemanggil apa pun. Kelas uji ditulis sebagai kelas biasa yang mewarisi Minitest::Test, dan setiap metode yang namanya diawali test_ dihitung sebagai satu uji. Konvensi penamaannya juga tetap: berkas uji diberi akhiran _test.rb agar perannya langsung terbaca dari namanya.

Kelas yang diuji pada topik ini adalah Kalkulator dengan empat metode yang masing-masing akan menyentuh satu kelompok assertion.

ruby kalkulator.rb
class Kalkulator
  def tambah(a, b)
    a + b
  end

  def bagi(a, b)
    a / b
  end

  def dibagi_habis?(a, b)
    (a % b).zero?
  end

  def akar(n)
    n.negative? ? nil : Math.sqrt(n)
  end
end

Berkas tersebut tidak mencetak apa pun ketika dijalankan, karena tugasnya hanya mendefinisikan kelas. Perhatikan dua perilaku yang sengaja disertakan sebagai bahan uji: pembagian dua bilangan bulat pada bagi membulatkan hasilnya, dan akar mengembalikan nil untuk bilangan negatif. Berkas uji berikut memeriksa keempat metode itu.

ruby kalkulator_test.rb
# tidak-dijalankan: keluaran penuh uji ditampilkan pada blok bash di bawah dengan seed tetap
require "minitest/autorun"
require_relative "kalkulator"

class KalkulatorTest < Minitest::Test
  def test_tambah_mengembalikan_jumlah_dua_bilangan
    kalkulator = Kalkulator.new
    hasil = kalkulator.tambah(2, 3)
    assert_equal 5, hasil
  end

  def test_bagi_membulatkan_hasil_pembagian_bilangan_bulat
    kalkulator = Kalkulator.new
    assert_equal 2, kalkulator.bagi(5, 2)
  end

  def test_bagi_nol_menimbulkan_zero_division_error
    kalkulator = Kalkulator.new
    assert_raises(ZeroDivisionError) { kalkulator.bagi(5, 0) }
  end

  def test_dibagi_habis_mengembalikan_nilai_kebenaran
    kalkulator = Kalkulator.new
    assert kalkulator.dibagi_habis?(10, 2)
    refute kalkulator.dibagi_habis?(10, 3)
  end

  def test_akar_bilangan_negatif_mengembalikan_nil
    kalkulator = Kalkulator.new
    assert_nil kalkulator.akar(-4)
  end
end

Berkas uji dijalankan seperti berkas Ruby biasa, yaitu dengan menyebut namanya setelah perintah ruby. Pipa sed pada perintah berikut membuang baris waktu eksekusi yang nilainya berbeda pada setiap eksekusi, sehingga keluaran yang diperlihatkan berfokus pada hasil uji.

bash
# menjalankan seluruh metode uji pada kalkulator_test.rb
ruby kalkulator_test.rb --seed 456 2>&1 | sed '/^Finished in/d'
text Keluaran
Run options: --seed 456

# Running:

.....

5 runs, 6 assertions, 0 failures, 0 errors, 0 skips

Baris Run options menampilkan opsi eksekusi, lima titik menyatakan lima metode uji yang berhasil berjalan, dan baris terakhir merangkum semuanya: lima uji dengan enam assertion, tanpa failure, error, atau skip. Angka failure menghitung uji yang assertion-nya terbukti keliru, angka error menghitung uji yang terputus oleh exception, sedangkan skip menghitung uji yang sengaja dilewati. Semua angka nol pada tiga kolom terakhir adalah keadaan yang dituju setiap kali uji dijalankan.

Assertion utama Minitest#

Assertion adalah pernyataan yang memeriksa sebuah dugaan tentang kode dan menandai uji sebagai gagal apabila dugaan itu tidak terbukti. Minitest menyediakan assertion dalam bentuk metode biasa, dan setiap assertion menghasilkan pesan kegagalan yang berbeda sesuai maksudnya. Tabel berikut merangkum lima assertion yang paling sering digunakan beserta pemeriksaannya.

AssertionPemeriksaan yang dilakukan
assert_equal 5, hasilnilai aktual sama dengan nilai yang diharapkan
assert kondisikondisi bernilai benar
refute kondisikondisi bernilai salah
assert_raises(KelasError) { ... }blok menimbulkan kelas error yang disebutkan
assert_nil nilainilai yang diperiksa adalah nil

Pasangan assert dan refute menangani nilai kebenaran. Metode test_dibagi_habis_mengembalikan_nilai_kebenaran menggunakan keduanya: assert untuk dibagi_habis?(10, 2) yang memang benar, dan refute untuk dibagi_habis?(10, 3) yang memang salah. Kedua pemanggilan itu tetap bisa ditulis dengan assert dan tanda !, tetapi refute menyatakan maksudnya lebih langsung dan pesan kegagalannya lebih informatif.

assert_raises memeriksa exception. Pemanggilan bagi(5, 0) pada pembagian bilangan bulat menimbulkan ZeroDivisionError, dan assertion dalam bentuk blok memastikan error itu benar-benar terjadi. Apabila blok tidak menimbulkan exception apa pun atau menimbulkan kelas yang berbeda, uji dinyatakan gagal. Sementara itu, assert_nil memeriksa nilai nil secara khusus: akar(-4) sengaja dirancang mengembalikan nil, dan assert_nil menyatakan pemeriksaan itu dengan lebih tegas daripada assert_equal nil, .... Kaidahnya sederhana, yaitu memilih assertion yang paling spesifik untuk pemeriksaan yang diinginkan, karena pesan kegagalannya pun menjadi lebih jelas.

Pola arrange-act-assert#

Metode uji yang mudah dibaca mengikuti pola arrange-act-assert, yaitu menyusun keadaan awal, menjalankan perilaku yang diuji, lalu memeriksa hasilnya. Metode test_tambah_mengembalikan_jumlah_dua_bilangan memperlihatkan ketiganya dalam tiga baris: baris pertama menyusun objek Kalkulator, baris kedua menjalankan tambah(2, 3), dan baris ketiga memeriksa hasilnya terhadap 5. Bentuk tiga baris seperti ini dimungkinkan karena hasil metode disimpan ke variabel lokal terlebih dahulu. Bentuk ringkas yang menggabungkan pemanggilan ke dalam assertion, seperti pada test_bagi_membulatkan_hasil_pembagian_bilangan_bulat, juga lazim digunakan untuk pemeriksaan yang pendek.

Disiplin yang menyertainya adalah satu metode uji untuk satu perilaku. Nama metode uji diperlakukan sebagai deskripsi perilaku, sehingga apabila sebuah uji gagal, penyebabnya terbaca langsung dari namanya tanpa membuka isi metodenya. Metode test_dibagi_habis_mengembalikan_nilai_kebenaran memuat dua assertion, dan hal itu tetap wajar karena keduanya memeriksa satu perilaku yang sama, yaitu nilai kembalian yang benar untuk dua kasus berlawanan.

Kesalahan umum#

Kesalahan yang paling sering luput adalah menamai metode uji tanpa prefiks test_. Metode seperti itu tidak dihitung sebagai uji, dan Minitest tetap melaporkan hasil yang tampak sukses karena memang tidak ada uji yang berjalan. Berkas berikut memuat metode bernama cek_tambah, yang secara isi identik dengan uji penjumlahan tetapi tidak pernah dieksekusi.

ruby uji_tanpa_prefiks.rb
# tidak-dijalankan: keluaran penuh uji ditampilkan pada blok bash di bawah dengan seed tetap
require "minitest/autorun"
require_relative "kalkulator"

class KalkulatorTanpaPrefiksTest < Minitest::Test
  def cek_tambah
    kalkulator = Kalkulator.new
    assert_equal 5, kalkulator.tambah(2, 3)
  end
end
bash
# menjalankan berkas uji yang metodenya tidak berprefiks test_
ruby uji_tanpa_prefiks.rb --seed 456 2>&1 | sed '/^Finished in/d'
text Keluaran
Run options: --seed 456

# Running:

0 runs, 0 assertions, 0 failures, 0 errors, 0 skips

Keluaran menyatakan 0 runs, dan itulah tanda yang harus dicurigai: tidak ada satu uji pun yang berjalan, sehingga laporan tanpa failure tidak berarti apa-apa. Perbaikannya adalah mengganti nama cek_tambah menjadi test_tambah beserta penyesuaian deskripsinya, sehingga metode tersebut terhitung sebagai uji. Kesalahan serupa yang lain terjadi ketika require "minitest/autorun" lupa ditulis, dan definisi kelas langsung berakhir dengan NameError karena konstanta Minitest belum dikenal. Kedua kasus ini menegaskan satu kebiasaan yang sehat, yaitu selalu membaca angka runs pada akhir keluaran, bukan hanya ketiadaan failure.

Ringkasan#

  • Berkas uji Minitest memuat require "minitest/autorun" serta kelas yang mewarisi Minitest::Test, dan dijalankan langsung dengan ruby nama_berkas_test.rb.
  • Metode uji wajib diawali test_ agar dihitung sebagai uji, sedangkan opsi --seed menetapkan urutan eksekusinya.
  • Assertion utama adalah assert_equal, assert, refute, assert_raises, dan assert_nil, dengan kaidah memilih bentuk yang paling spesifik.
  • Pola arrange-act-assert dan satu perilaku per metode uji menjaga diagnosis tetap cepat, dan angka runs pada keluaran wajib dipastikan bukan nol.

Referensi#