Go membawa perangkat pengujian sebagai bagian dari distribusinya, sehingga pengujian otomatis dapat ditulis tanpa pustaka tambahan apa pun. Topik ini membahas paket testing, yaitu pustaka bawaan untuk menulis pengujian unit (unit test) yang memeriksa kebenaran satu bagian kode secara otomatis, beserta konvensi penamaan berkas dan fungsinya serta perintah go test yang menjalankannya. Setelah membaca topik ini, pengujian pertama dapat ditulis, dijalankan, dan keluarannya dapat dibaca dalam keadaan lolos maupun gagal.
Konvensi berkas dan fungsi pengujian#
Konvensi pengujian Go bertumpu pada penamaan. Pengujian ditulis dalam berkas yang namanya berakhiran _test.go dan ditempatkan pada folder yang sama dengan kode yang diuji, memakai nama paket yang sama pula. Di dalam berkas itu, setiap fungsi pengujian diberi nama diawali Test diikuti huruf besar, misalnya TestDiskon, dan menerima satu parameter bertipe *testing.T yang menyediakan cara melaporkan kegagalan. Berkas _test.go tidak diabaikan begitu saja oleh alat bantu Go, tetapi perintah go build dan go install melewatinya, sehingga kode pengujian tidak pernah ikut terbawa ke dalam biner hasil build.
Contoh pada topik ini memakai fungsi penghitung diskon dengan persentase tetap. Fungsinya menerima harga dan persentase dalam bilangan bulat, sehingga hasil pembagiannya mengikuti aritmetika bilangan bulat, yaitu dibulatkan ke bawah:
package main
import "fmt"
// Diskon mengembalikan harga akhir setelah dikurangi potongan dengan
// persentase tetap. Pembulatan mengikuti aritmetika bilangan bulat,
// yaitu potongan dibulatkan ke bawah.
func Diskon(harga, persen int) int {
potongan := harga * persen / 100
return harga - potongan
}
func main() {
fmt.Println(Diskon(200000, 25))
fmt.Println(Diskon(50000, 10))
}150000
45000Fungsi main pada bagian bawah berkas hanya memperagakan pemanggilan Diskon untuk dua pasang nilai, sehingga berkas ini dapat dijalankan sendirian dengan go run dan hasilnya terbaca langsung. Kedua pasangan nilai itu sengaja dipilih sama dengan yang dipakai pengujian pada bagian berikutnya, sehingga hasil fungsi dan ekspektasi pengujian dapat dibandingkan berdampingan.
Komentar yang berawalan nama fungsi itu bukan pelengkap. Komentar semacam itu menjadi dokumentasi yang tampil pada dokumentasi paket, sekaligus menjelaskan perilaku pembulatan yang nantinya diperiksa oleh pengujian.
Menulis fungsi pengujian pertama#
Fungsi pengujian berperilaku seperti fungsi biasa yang isinya berupa serangkaian pemanggilan dan pengecekan. Setiap pemanggilan Diskon dibandingkan dengan nilai yang dihitung manual, yaitu harga 200000 dengan diskon 25 persen menghasilkan 150000, serta harga 50000 dengan diskon 10 persen menghasilkan 45000:
// tidak-dijalankan: dijalankan melalui go test
package main
import "testing"
func TestDiskon(t *testing.T) {
if hasil := Diskon(200000, 25); hasil != 150000 {
t.Errorf("Diskon(200000, 25) = %d, seharusnya 150000", hasil)
}
if hasil := Diskon(50000, 10); hasil != 45000 {
t.Errorf("Diskon(50000, 10) = %d, seharusnya 45000", hasil)
}
}Baris komentar pertama pada berkas itu menandai bahwa berkas pengujian tidak dijalankan sendirian, melainkan digabungkan dengan kode utama oleh go test. Sebabnya sederhana, yaitu berkas pengujian tidak memiliki fungsi main, sehingga perintah go run menolaknya. Kebalikannya, go test yang ditunjukkan pada bagian berikutnya mengompilasi berkas pengujian bersama hitung.go dalam satu paket, lalu mengeksekusi fungsi-fungsi yang memenuhi konvensi namanya.
Perhatikan bahwa pemanggilan Diskon dan perbandingannya digabung dalam pernyataan if dengan inisialisasi. Pola ini membuat cakupan variabel hasil terbatas pada pengecekannya saja. Kata seharusnya pada pesan memuat nilai yang dihitung manual, sehingga pembaca pesan dapat langsung melihat pasangan nilai aktual dan nilai yang diharapkan tanpa membuka kode lagi.
Menjalankan dengan go test#
Perintah go test mencari seluruh berkas _test.go pada paket di folder kerja, mengompilasinya bersama kode utama, lalu menjalankan setiap fungsi yang namanya memenuhi konvensi. Berkas contoh disalin ke folder kerja terpisah dan diberi modul sementara agar perintahnya dapat direproduksi apa adanya:
# salin berkas contoh ke folder uji
rm -rf /tmp/gotest-hitung && mkdir -p /tmp/gotest-hitung && cp *.go /tmp/gotest-hitung/ && cd /tmp/gotest-hitung
# buat modul sementara tanpa mencatat pesannya
go mod init contoh >/dev/null 2>&1
# jalankan pengujian dalam bentuk paling ringkasnya
go test 2>&1 | sed -E 's/^(ok|FAIL)[[:space:]]+contoh[[:space:]].*/\1 contoh/; s/\([0-9]+\.[0-9]+s\)/(WAKTU)/'PASS
ok contohKeluaran dalam bentuk ringkas hanya terdiri atas dua baris. Baris PASS menyatakan seluruh fungsi pengujian pada paket itu lolos, sedangkan baris ok menyatakan nama paket beserta waktu eksekusinya yang di sini dirapikan menjadi ok contoh. Bila salah satu fungsi gagal, kedua baris itu berubah menjadi FAIL. Keluaran yang sudah dirapikan seperti di atas memang tidak memuat angka waktu, sehingga hasilnya cocok untuk dibaca manusia maupun dibandingkan antar eksekusi.
Untuk melihat fungsi mana saja yang dijalankan, tambahkan opsi -v dari kata verbose, yang berarti menampilkan keluaran secara rinci:
# ulangi pengujian dengan keluaran yang lebih rinci
go test -v 2>&1 | sed -E 's/^(ok|FAIL)[[:space:]]+contoh[[:space:]].*/\1 contoh/; s/\([0-9]+\.[0-9]+s\)/(WAKTU)/'=== RUN TestDiskon
--- PASS: TestDiskon (WAKTU)
PASS
ok contohBaris === RUN menandai fungsi yang mulai dieksekusi, sedangkan baris --- PASS menandai fungsi yang selesai dengan status lolos beserta lamanya. Angka lama eksekusi di sini dinormalisasi menjadi (WAKTU) karena nilainya berubah-ubah antar eksekusi. Urutan baris keluaran selalu mengikuti urutan definisi pada berkas.
Membaca keluaran kegagalan#
Keluaran kegagalan justru bagian yang paling sering dibaca, karena keberadaannya menandai ada perilaku yang belum sesuai. Blok berikut sengaja mengubah satu masukan pada hitung_test.go di folder uji agar hasilnya menyimpang dari ekspektasi, lalu menjalankan ulang pengujian:
# ubah masukan pada satu pemanggilan sehingga hasilnya tidak sesuai ekspektasi
sed -i 's/Diskon(200000, 25)/Diskon(200000, 20)/g' hitung_test.go
# jalankan ulang lalu amati bentuk keluaran kegagalannya
go test 2>&1 | sed -E 's/^(ok|FAIL)[[:space:]]+contoh[[:space:]].*/\1 contoh/; s/\([0-9]+\.[0-9]+s\)/(WAKTU)/'--- FAIL: TestDiskon (WAKTU)
hitung_test.go:9: Diskon(200000, 20) = 160000, seharusnya 150000
FAIL
exit status 1
FAIL contohStrukturnya terbaca dari atas ke bawah. Baris --- FAIL menyebut nama fungsi yang gagal. Baris menjorok di bawahnya menyebut berkas, nomor baris, dan isi pesan t.Errorf lengkap dengan nilai aktual dan nilai seharusnya. Baris FAIL contoh menyatakan status paket. Nomor baris menunjuk langsung ke baris t.Errorf pada berkas pengujian, sehingga perbaikan dapat dimulai dari situ tanpa mencari-cari.
Dua metode pelaporan pada *testing.T memiliki perilaku yang berbeda. t.Errorf mencatat kegagalan lalu melanjutkan eksekusi fungsi, sehingga satu fungsi dapat melaporkan beberapa kegagalan sekaligus, sedangkan t.Fatalf mencatat kegagalan lalu menghentikan fungsi tersebut seketika. t.Fatalf dipakai apabila langkah berikutnya tidak bermakna tanpa keadaan yang benar, misalnya ketika nilai yang gagal itu akan dipakai oleh pengecekan berikutnya.
Kesalahan umum#
Dua kesalahan berikut paling sering muncul ketika pengujian pertama ditulis.
- Nama fungsi yang tidak memenuhi konvensi. Fungsi bernama
Testdiskondengan huruf kecil setelah kataTestdiabaikan olehgo testtanpa pesan error apa pun, sehingga pengujian itu tidak pernah dijalankan. Aturannya,Testdiikuti huruf besar atau karakter selain huruf kecil, sehinggaTestDiskondanTestDiskon25Persenmemenuhi syarat, sedangkanTestdiskontidak. - Memeriksa hasil dengan
fmt.Printlnalih-alih metode*testing.T. Pencetakan hanya menampilkan teks pada terminal dan tidak mengubah statusgo test, sehingga pengujian tetap tercatat lolos meskipun nilainya salah. Pengecekan yang benar selalu berakhir padat.Errorfataut.Fatalfagar kegagalan tercatat dalam status paket.
Ringkasan#
- Pengujian ditulis pada berkas
_test.godi folder yang sama dengan kode yang diuji, memakai nama paket yang sama, dan fungsinya diberi namaTestXxxdengan parameter*testing.T. go testmengompilasi dan menjalankan pengujian pada paket di folder kerja, sedangkan opsi-vmenampilkan setiap fungsi yang dijalankan beserta statusnya.- Keluaran kegagalan menyebut berkas, nomor baris, dan pesan lengkap dengan nilai aktual serta nilai seharusnya.
t.Errorfmencatat kegagalan lalu melanjutkan eksekusi, sedangkant.Fatalfmenghentikan fungsinya seketika, dango buildselalu melewatikan berkas pengujian.
Referensi#
- Package testing — The Go Authors — Dokumentasi resmi paket testing yang memuat konvensi penamaan dan metode pelaporan kegagalan.
- Add a test! — The Go Authors — Tutorial resmi menulis pengujian pertama beserta perintah go test.