- Published on
Tips dan trik unit test di Go
- Authors
- Name
- Moch Lutfi
- @kaptenupi
Unit Test memang tidak bisa dilepaskan dari proses pengembangan software. Namun seringkali dalam pembuatan Unit Test di Go terjadi banyak repetisi yang tidak perlu dan Unit Test yang tidak dikelola dengan baik. Salah satu contoh kasus yang paling banyak ditemui dalam pembuatan Unit Test yaitu tidak dipisahkannya logic dan data sehingga ketika penambahan data test terdapat penambahan pula logic.
Contoh Kode
Sebelum memulai lebih lanjut berikut adalah contoh kode untuk membantu memahami tulisan ini.
- Struktur direktori
_3$ tree ._3├── add.go_3└── add_test.go
- File
add.go
_6package main_6_6// Tambah merupakan fungsi sederhana penjumlahan_6func Tambah(a, b int)int{_6 return a+b_6}
- File
add_test.go
_15package main_15_15import "testing"_15_15func TestBasic(t *testing.T){_15 if Tambah(1,1) != 2{_15 t.Error("seharusnya 2")_15 }_15_15 if Tambah(1,2) != 3{_15 t.Error("seharusnya 3")_15 }_15_15 // kode berulang sebanyak jumlah data test_15}
Unit Test
Hasil running
_5$ go test -v_5=== RUN TestBasic_5--- PASS: TestBasic (0.00s)_5PASS_5ok github.com/h4ckm03d/blog-codes/golang101/6-tips-trik-unit-test 0.002s
Memanfaatkan array of struct
Contoh Unit Test diatas merupakan contoh paling sederhana, jika yang sederhana saja sudah terlalu verbose dan susah dikelola bagaimana jika kode dalam suatu package
semakin besar? Salah satu cara untuk memisahkan logic unit test dengan data yaitu menggunakan unit test array of struct untuk data dan tinggal menambahkan looping data ketika pengecekan. Tambahkan fungsi dibawah ini ke dalam add_test.go
_20func TestB(t *testing.T) {_20 testData := []struct {_20 name string_20 inputA int_20 inputB int_20 result int_20 }{_20 {"1+1", 1, 1, 2},_20 {"1+2", 1, 2, 3},_20 {"1+3", 1, 3, 4},_20 }_20_20 for _, tc := range testData {_20 t.Run(tc.name, func(t *testing.T) {_20 if Tambah(tc.inputA, tc.inputB) != tc.result {_20 t.Errorf("Seharusnya %d", tc.result)_20 }_20 })_20 }_20}
Jika Unit Test dijalankan maka hasilnya seperti berikut:
_13$ go test -v_13=== RUN TestBasic_13--- PASS: TestBasic (0.00s)_13=== RUN TestB_13=== RUN TestB/1+1_13=== RUN TestB/1+2_13=== RUN TestB/1+3_13--- PASS: TestB (0.00s)_13 --- PASS: TestB/1+1 (0.00s)_13 --- PASS: TestB/1+2 (0.00s)_13 --- PASS: TestB/1+3 (0.00s)_13PASS_13ok github.com/h4ckm03d/blog-codes/golang101/6-tips-trik-unit-test 0.002s
Dalam TestB
diatas ketika data test bertambah, kita hanya perlu menambahkan kedalam array
testData
tanpa perlu merubah logic sehingga kode lebih mudah dibaca dan dimodifikasi. struct
dalam peubah testData
terdiri dari 3 bagian label test(name
), input data (inputA
, inputB
), dan output dari fungsi (result
). Untuk input data dan output bisa diganti sesuai dengan fungsi yang ditest. Hasil running dari unit test juga lebih jelas jika menggunakan metode ini karena penggunaan label membantu tracking ketika ada kesalahan. Misalkan testData
1 ditambah 1 diubah menjadi 7 untuk mengetahui contoh jika unit test menghasilkan error, hasilnya sebagai berikut:
_15$ go test -v_15=== RUN TestBasic_15--- PASS: TestBasic (0.00s)_15=== RUN TestB_15=== RUN TestB/1+1_15=== RUN TestB/1+2_15=== RUN TestB/1+3_15--- FAIL: TestB (0.00s)_15 --- FAIL: TestB/1+1 (0.00s)_15 add_test.go:34: Seharusnya 7_15 --- PASS: TestB/1+2 (0.00s)_15 --- PASS: TestB/1+3 (0.00s)_15FAIL_15exit status 1_15FAIL github.com/h4ckm03d/blog-codes/golang101/6-tips-trik-unit-test 0.005s
Karena output diubah menjadi 7 sehingga unit testnya fail, akan tetapi karena menggunakan label kita segera mengetahui data apa yang salah sehingga bug fix lebih mudah.
Fatal VS Error
Pada fungsi TestBasic
diatas menggunakan t.Error("seharusnya 2")
, kenapa menggunakan t.Error
bukan menggunakan t.Fatal
? Penggunaan error diatas karena ketika terjadi kesalahan misalkan output salah atau tidak sesuai maka unit test tidak akan berhenti tetapi melanjutkan sampai semua proses dalam fungsi tersebut selesai. Jika menggunakan fatal maka ketika t.Fatal
atau t.Fatalf
dipanggil maka proses dihentikan. Dalam hal ini ketika 1 + 1 terjadi error maka proses berhenti dan tidak dilanjutkan pengecekan 1 + 2. Berikut contoh perbedaan penggunaan fatal dan error.
_23func TestBasicFatal(t *testing.T) {_23 if Tambah(1, 1) != 3 {_23 t.Error("seharusnya 2")_23 }_23_23 if Tambah(1, 2) != 4 {_23 t.Error("seharusnya 3")_23 }_23_23 // kode berulang sebanyak jumlah data test_23}_23_23func TestBasicError(t *testing.T) {_23 if Tambah(1, 1) != 3 {_23 t.Error("seharusnya 2")_23 }_23_23 if Tambah(1, 2) != 4 {_23 t.Error("seharusnya 3")_23 }_23_23 // kode berulang sebanyak jumlah data test_23}
Hasil go test -v
sebagai berikut
_21$ go test -v_21=== RUN TestBasic_21--- PASS: TestBasic (0.00s)_21=== RUN TestBasicFatal_21--- FAIL: TestBasicFatal (0.00s)_21 add_test.go:21: seharusnya 2_21=== RUN TestBasicError_21--- FAIL: TestBasicError (0.00s)_21 add_test.go:33: seharusnya 2_21 add_test.go:37: seharusnya 3_21=== RUN TestB_21=== RUN TestB/1+1_21=== RUN TestB/1+2_21=== RUN TestB/1+3_21--- PASS: TestB (0.00s)_21 --- PASS: TestB/1+1 (0.00s)_21 --- PASS: TestB/1+2 (0.00s)_21 --- PASS: TestB/1+3 (0.00s)_21FAIL_21exit status 1_21FAIL github.com/h4ckm03d/blog-codes/golang101/6-tips-trik-unit-test 0.004s
Ketika TestBasicFatal
dijalankan maka ketika ada error proses berhenti berbeda dengan TestBasicError
, meskipun ada error proses dilanjutkan sampai selesai.
Dengan pemisahan kode Unit Test dan penggunaan error/fatal yang tepat kita bisa lebih optimal memanfaatkan unit test. Semoga bermanfaat dan sampai jumpa lagi di tulisan selanjutnya.