1. Pengenalan Unit Testing
Unit testing merujuk pada pemeriksaan dan validasi unit terkecil yang bisa diuji dalam sebuah program, seperti fungsi atau metode dalam bahasa Go. Unit testing memastikan bahwa kode berfungsi sesuai harapan dan memungkinkan pengembang untuk melakukan perubahan pada kode tanpa sengaja merusak fungsionalitas yang sudah ada.
Dalam proyek Golang, pentingnya unit testing tak perlu diragukan lagi. Pertama, dapat meningkatkan kualitas kode dan memberikan kepercayaan diri lebih kepada pengembang dalam melakukan perubahan pada kode. Kedua, unit testing dapat berfungsi sebagai dokumentasi untuk kode, menjelaskan perilaku yang diharapkan. Selain itu, menjalankan unit test secara otomatis dalam lingkungan integrasi berkelanjutan dapat dengan cepat menemukan bug baru yang muncul, sehingga meningkatkan stabilitas perangkat lunak.
2. Melakukan Pengujian Dasar Menggunakan Paket testing
Pustaka standar bahasa Go termasuk paket testing
, yang menyediakan alat dan fungsionalitas untuk menulis dan mengeksekusi pengujian.
2.1 Membuat Kasus Uji Pertama Anda
Untuk menulis fungsi uji, Anda perlu membuat file dengan akhiran _test.go
. Misalnya, jika file kode sumber Anda bernama calculator.go
, file uji Anda harus dinamai calculator_test.go
.
Selanjutnya, saatnya untuk membuat fungsi uji. Sebuah fungsi uji perlu mengimpor paket testing
dan mengikuti format tertentu. Berikut contoh sederhana:
// calculator_test.go
package calculator
import (
"testing"
"fmt"
)
// Uji fungsi penambahan
func TestAdd(t *testing.T) {
result := Add(1, 2)
expected := 3
if result != expected {
t.Errorf("Seharusnya %v, tapi dapat %v", expected, result)
}
}
Dalam contoh ini, TestAdd
adalah fungsi uji yang menguji sebuah fungsi Add
yang fiktif. Jika hasil dari fungsi Add
cocok dengan hasil yang diharapkan, uji akan berhasil; jika tidak, t.Errorf
akan dipanggil untuk mencatat informasi tentang kegagalan uji.
2.2 Memahami Aturan Penamaan dan Tanda Tangan Fungsi Uji
Fungsi uji harus diawali dengan Test
, diikuti oleh string non-huruf kecil apa pun, dan satu-satunya parameternya harus berupa pointer ke testing.T
. Seperti yang ditunjukkan dalam contoh, TestAdd
mengikuti aturan penamaan dan tanda tangan yang benar.
2.3 Menjalankan Kasus Uji
Anda dapat menjalankan kasus uji menggunakan perintah baris perintah. Untuk sebuah kasus uji tertentu, jalankan perintah berikut:
go test -v // Menjalankan uji di direktori saat ini dan menampilkan output detail
Jika Anda ingin menjalankan kasus uji tertentu, Anda dapat menggunakan flag -run
diikuti dengan ekspresi reguler:
go test -v -run TestAdd // Menjalankan hanya fungsi uji TestAdd
Perintah go test
akan secara otomatis menemukan semua file _test.go
dan mengeksekusi setiap fungsi uji yang memenuhi kriteria. Jika semua uji berhasil, Anda akan melihat pesan mirip PASS
di baris perintah; jika ada uji yang gagal, Anda akan melihat FAIL
beserta pesan kesalahan yang sesuai.
3. Menulis Kasus Uji
3.1 Melaporkan Kesalahan dengan Menggunakan t.Errorf
dan t.Fatalf
Dalam bahasa Go, kerangka pengujian menyediakan berbagai metode untuk melaporkan kesalahan. Dua fungsi yang paling umum digunakan adalah Errorf
dan Fatalf
, keduanya adalah metode dari objek testing.T
. Errorf
digunakan untuk melaporkan kesalahan dalam uji tapi tidak menghentikan kasus uji saat ini, sementara Fatalf
menghentikan uji saat ini segera setelah melaporkan kesalahan. Penting untuk memilih metode yang sesuai berdasarkan kebutuhan pengujian.
Contoh penggunaan Errorf
:
func TestAdd(t *testing.T) {
got := Add(1, 2)
want := 3
if got != want {
t.Errorf("Add(1, 2) = %d; seharusnya %d", got, want)
}
}
Jika Anda ingin menghentikan uji segera setelah mendeteksi kesalahan, Anda dapat menggunakan Fatalf
:
func TestSubtract(t *testing.T) {
got := Subtract(5, 3)
if got != 2 {
t.Fatalf("Subtract(5, 3) = %d; seharusnya 2", got)
}
}
Secara umum, jika kesalahan akan menyebabkan kode selanjutnya tidak dieksekusi dengan benar atau kegagalan uji dapat dikonfirmasi sebelumnya, disarankan untuk menggunakan Fatalf
. Jika tidak, disarankan untuk menggunakan Errorf
untuk mendapatkan hasil uji yang lebih komprehensif.
3.2 Mengatur Subtes dan Menjalankan Subtes
Di Go, kita dapat menggunakan t.Run
untuk mengatur subtes, yang membantu kita menulis kode uji dalam cara yang lebih terstruktur. Subtes dapat memiliki Setup
dan Teardown
mereka sendiri dan dapat dijalankan secara individual, memberikan fleksibilitas yang besar. Ini sangat berguna untuk melakukan uji kompleks atau uji parameter.
Contoh penggunaan subtes t.Run
:
func TestKalikan(t *testing.T) {
testcases := []struct {
name string
a, b, expected int
}{
{"2x3", 2, 3, 6},
{"-1x-1", -1, -1, 1},
{"0x4", 0, 4, 0},
}
for _, tc := range testcases {
t.Run(tc.name, func(t *testing.T) {
if got := Kalikan(tc.a, tc.b); got != tc.expected {
t.Errorf("Kalikan(%d, %d) = %d; harapannya %d", tc.a, tc.b, got, tc.expected)
}
})
}
}
Jika kita ingin menjalankan subtes bernama "2x3" secara individual, kita dapat menjalankan perintah berikut dalam baris perintah:
go test -run TestKalikan/2x3
Harap dicatat bahwa nama subtes bersifat case-sensitive.
4. Persiapan Sebelum dan Setelah Pengujian
4.1 Persiapan dan Kerja Bersih
Ketika melakukan pengujian, kita sering perlu menyiapkan beberapa keadaan awal untuk uji (seperti koneksi database, pembuatan file, dll.), dan begitu juga, kita perlu melakukan beberapa kerja bersih setelah pengujian selesai. Di Go, biasanya kita melakukan Setup
dan Teardown
langsung di dalam fungsi uji, dan fungsi t.Cleanup
memberikan kita kemampuan untuk mendaftarkan fungsi panggilan kerja bersih.
Berikut adalah contoh sederhana:
func TestDatabase(t *testing.T) {
db, err := SetupDatabase()
if err != nil {
t.Fatalf("persiapan gagal: %v", err)
}
// Daftarkan panggilan kerja bersih untuk memastikan koneksi database ditutup ketika pengujian selesai
t.Cleanup(func() {
if err := db.Close(); err != nil {
t.Errorf("gagal menutup database: %v", err)
}
})
// Lakukan pengujian...
}
Di dalam fungsi TestDatabase
, kita pertama-tama memanggil fungsi SetupDatabase
untuk menyiapkan lingkungan uji. Kemudian, kita menggunakan t.Cleanup()
untuk mendaftarkan fungsi yang akan dipanggil setelah pengujian selesai untuk melakukan kerja bersih, dalam contoh ini, menutup koneksi database. Dengan cara ini, kita dapat memastikan bahwa sumber daya tersebut dilepaskan dengan benar terlepas dari keberhasilan atau kegagalan pengujian.
5. Meningkatkan Efisiensi Pengujian
Meningkatkan efisiensi pengujian dapat membantu kita mengembangkan dengan cepat, dengan cepat menemukan isu, dan memastikan kualitas kode. Di bawah ini, kita akan membahas cakupan pengujian, uji berbasis tabel, dan penggunaan mock untuk meningkatkan efisiensi pengujian.
5.1 Cakupan Pengujian dan Alat Terkait
Alat go test
menyediakan fitur cakupan pengujian yang sangat berguna, yang membantu kita memahami bagian mana dari kode yang dicakup oleh kasus uji, sehingga menemukan area kode yang tidak dicakup oleh kasus uji.
Dengan menggunakan perintah go test -cover
, Anda dapat melihat persentase cakupan pengujian saat ini:
go test -cover
Jika Anda ingin memahami lebih detail langsung kode mana yang dieksekusi dan mana yang tidak, Anda dapat menggunakan parameter -coverprofile
, yang menghasilkan file data cakupan. Kemudian, Anda dapat menggunakan perintah go tool cover
untuk menghasilkan laporan cakupan uji yang mendetail.
go test -coverprofile=coverage.out
go tool cover -html=coverage.out
Perintah di atas akan membuka laporan web, yang memperlihatkan secara visual kode mana yang dicakup oleh uji dan mana yang tidak. Hijau menunjukkan kode yang dicakup oleh uji, sementara merah menunjukkan kode yang tidak dicakup.
5.2 Menggunakan Mock
Dalam pengujian, sering kali kita menghadapi skenario di mana kita perlu mensimulasikan ketergantungan eksternal. Mock dapat membantu kita mensimulasikan ketergantungan ini, menghilangkan kebutuhan untuk bergantung pada layanan atau sumber eksternal spesifik dalam lingkungan pengujian.
Ada banyak alat mock dalam komunitas Go, seperti testify/mock
dan gomock
. Alat-alat ini biasanya menyediakan serangkaian API untuk membuat dan menggunakan objek mock.
Berikut adalah contoh dasar penggunaan testify/mock
. Hal pertama yang harus dilakukan adalah mendefinisikan sebuah antarmuka dan versi mockingnya:
type DataService interface {
FetchData() (int, error)
}
type MockDataService struct {
mock.Mock
}
func (m *MockDataService) FetchData() (int, error) {
args := m.Called()
return args.Int(0), args.Error(1)
}
Dalam pengujian, kita dapat menggunakan MockDataService
untuk menggantikan layanan data aktual:
func TestSomething(t *testing.T) {
mockDataSvc := new(MockDataService)
mockDataSvc.On("FetchData").Return(42, nil) // Mengkonfigurasi perilaku yang diharapkan
result, err := mockDataSvc.FetchData() // Menggunakan objek mock
assert.NoError(t, err)
assert.Equal(t, 42, result)
mockDataSvc.AssertExpectations(t) // Memverifikasi apakah perilaku yang diharapkan terjadi
}
Melalui pendekatan di atas, kita dapat menghindari ketergantungan pada layanan eksternal, panggilan database, dll. dalam pengujian. Hal ini dapat mempercepat eksekusi pengujian dan membuat pengujian kita lebih stabil dan dapat diandalkan.
6. Teknik Pengujian Lanjutan
Setelah menguasai dasar-dasar pengujian unit Go, kita dapat menjelajahi beberapa teknik pengujian lebih lanjut, yang membantu dalam membangun perangkat lunak yang lebih kokoh dan meningkatkan efisiensi pengujian.
6.1 Pengujian Fungsi Private
Dalam Golang, fungsi private biasanya merujuk pada fungsi yang tidak diekspor, yaitu fungsi yang nama-namanya dimulai dengan huruf kecil. Biasanya, kita lebih suka menguji antarmuka publik, karena mereka mencerminkan kegunaan kode. Namun, ada kasus di mana langsung menguji fungsi private juga masuk akal, seperti ketika fungsi private memiliki logika kompleks dan dipanggil oleh beberapa fungsi publik.
Menguji fungsi private berbeda dari menguji fungsi publik karena mereka tidak dapat diakses dari luar paket. Teknik umum adalah menulis kode pengujian dalam paket yang sama, memungkinkan akses ke fungsi private.
Berikut adalah contoh sederhana:
// calculator.go
package calculator
func add(a, b int) int {
return a + b
}
Berkas pengujian yang sesuai adalah sebagai berikut:
// calculator_test.go
package calculator
import "testing"
func TestAdd(t *testing.T) {
expected := 4
actual := add(2, 2)
if actual != expected {
t.Errorf("harapkan %d, dapatkan %d", expected, actual)
}
}
Dengan meletakkan berkas pengujian dalam paket yang sama, kita dapat langsung menguji fungsi add
.
6.2 Pola Pengujian Umum dan Praktik Terbaik
Pengujian unit Go memiliki beberapa pola umum yang memfasilitasi pekerjaan pengujian dan membantu menjaga kejelasan dan keberlanjutan kode.
-
Pengujian Berbasis Tabel
Pengujian berbasis tabel adalah metode pengorganisasian masukan pengujian dan keluaran yang diharapkan. Dengan mendefinisikan serangkaian kasus pengujian dan kemudian meloopingnya untuk pengujian, metode ini membuat sangat mudah untuk menambahkan kasus uji baru dan juga membuat kode lebih mudah dibaca dan dipelihara.
// calculator_test.go
package calculator
import "testing"
func TestAddTableDriven(t *testing.T) {
var tests = []struct {
a, b int
want int
}{
{1, 2, 3},
{2, 2, 4},
{5, -1, 4},
}
for _, tt := range tests {
testname := fmt.Sprintf("%d,%d", tt.a, tt.b)
t.Run(testname, func(t *testing.T) {
ans := add(tt.a, tt.b)
if tt.want != ans {
t.Errorf("dapatkan %d, harapkan %d", ans, tt.want)
}
})
}
}
-
Menggunakan Mock untuk Pengujian
Mocking adalah teknik pengujian yang melibatkan penggantian dependensi untuk menguji berbagai bagian fungsionalitas. Dalam Golang, antarmuka adalah cara utama untuk mengimplementasikan mocks. Dengan menggunakan antarmuka, implementasi mock dapat dibuat dan digunakan dalam pengujian.