Menguasai Pengujian di Go: Praktik Terbaik untuk Kode Berkualitas
Pengujian adalah aspek penting dari pengembangan perangkat lunak yang memastikan kode Anda berfungsi seperti yang diharapkan, mencegah kesalahan, dan memfasilitasi pemfaktoran ulang yang aman. Dalam Go, bahasa yang dikenal dengan kesederhanaan dan efisiensinya, pengujian terintegrasi dengan baik ke dalam toolchain. Artikel ini akan membahas praktik terbaik untuk pengujian dalam Go, membantu Anda menulis tes yang efektif, terpelihara, dan andal.
Mengapa Pengujian Itu Penting di Go?
Sebelum kita membahas praktik terbaik, mari kita pahami mengapa pengujian sangat penting dalam Go:
- Keandalan: Pengujian membantu Anda memastikan bahwa kode Anda berfungsi dengan benar dan sesuai dengan spesifikasi.
- Pemeliharaan: Tes yang baik membuat kode Anda lebih mudah dipelihara dan dimodifikasi. Anda dapat dengan percaya diri melakukan perubahan tanpa takut merusak fungsi yang ada.
- Desain: Menulis tes sering kali memaksa Anda untuk memikirkan desain kode Anda dengan lebih cermat, yang mengarah ke kode yang lebih bersih dan modular.
- Dokumentasi: Tes berfungsi sebagai dokumentasi kode Anda. Mereka menunjukkan bagaimana kode Anda seharusnya digunakan dan perilaku yang diharapkan.
- Refactoring: Tes menyediakan jaring pengaman saat Anda memfaktorkan ulang kode Anda. Anda dapat yakin bahwa kode Anda terus berfungsi dengan benar setelah perubahan.
Kerangka Posting Blog: Mastering Testing in Go: Best Practices
- Pendahuluan
- Signifikansi pengujian dalam pengembangan perangkat lunak
- Mengapa pengujian sangat penting di Go
- Dasar-Dasar Pengujian di Go
- Paket `testing`
- Fungsi dan konvensi penamaan pengujian
- Assertion dasar menggunakan `t.Error`, `t.Errorf`, `t.Fatal`, `t.Fatalf`
- Menjalankan pengujian dengan `go test`
- Jenis Pengujian yang Berbeda
- Pengujian Unit
- Pengujian Integrasi
- Pengujian End-to-End
- Praktik Terbaik Pengujian Unit
- Menulis pengujian yang jelas dan ringkas
- Menggunakan tabel pengujian untuk skenario pengujian berganda
- Mocking dan stubbing dependensi eksternal
- Prinsip FIRST pengujian unit (Fast, Independent, Repeatable, Self-Validating, Timely)
- Menguji kasus tepi dan skenario kesalahan
- Menghindari logika berlebihan dalam pengujian
- Cakupan kode dan signifikansinya
- Pengujian Integrasi di Go
- Menguji interaksi antara komponen
- Menyiapkan lingkungan pengujian untuk integrasi
- Pertimbangan basis data dan jaringan
- Benchmark di Go
- Menulis benchmark dengan paket `testing`
- Menganalisis hasil benchmark
- Optimasi kinerja berdasarkan benchmark
- Pengujian Berbasis Properti
- Pengantar pengujian berbasis properti
- Menggunakan pustaka seperti `gopter`
- Menentukan properti untuk diuji
- Mengejek dan Mem-mock di Go
- Mengapa mocking penting
- Menggunakan pustaka mocking populer seperti `gomock`
- Membuat mock untuk antarmuka
- Mengelola ekspektasi pada mock
- Pengujian HTTP di Go
- Menguji handler HTTP
- Menggunakan `net/http/httptest`
- Menulis pengujian untuk middleware
- Pengujian Basis Data di Go
- Menyiapkan basis data pengujian
- Menggunakan transaksi untuk pengujian
- Memvalidasi data di basis data
- Continuous Integration (CI) dan Pengujian
- Mengintegrasikan pengujian ke dalam alur CI/CD
- Menggunakan alat CI seperti Jenkins, Travis CI, GitHub Actions
- Otomatisasi proses pengujian
- Tips dan Trik Lanjutan
- Menggunakan `go test -race` untuk mendeteksi kondisi balapan
- Menggunakan alat cakupan
- Menulis pengujian fuzz
- Kesalahan Umum yang Harus Dihindari
- Pengujian yang terlalu kompleks
- Mengabaikan kasus tepi
- Tidak menjaga agar pengujian tetap terisolasi
- Bergantung pada implementasi daripada perilaku
- Sumber Daya dan Alat Pengujian Go
- Pustaka dan kerangka pengujian populer
- Alat cakupan kode
- Alat mocking
- Kesimpulan
- Rangkuman praktik terbaik pengujian
- Dorongan untuk memasukkan pengujian ke dalam alur kerja pengembangan Anda
Dasar-Dasar Pengujian di Go
Go memiliki dukungan pengujian bawaan melalui paket testing
. Paket ini menyediakan alat yang diperlukan untuk menulis dan menjalankan tes.
Paket testing
Paket testing
menyediakan tipe T
, yang digunakan untuk melaporkan kegagalan dan menunjukkan informasi log. Ini juga menyediakan fungsi untuk mendefinisikan dan menjalankan tes.
Fungsi dan Konvensi Penamaan Pengujian
Fungsi pengujian di Go harus mengikuti konvensi penamaan tertentu:
- Nama fungsi harus dimulai dengan
Test
. - Fungsi harus mengambil satu argumen dari tipe
*testing.T
. - Fungsi harus berada dalam file yang diakhiri dengan
_test.go
.
Contoh:
“`go
package main
import “testing”
func TestAdd(t *testing.T) {
result := Add(2, 3)
if result != 5 {
t.Errorf(“Add(2, 3) = %d; want 5”, result)
}
}
“`
Assertion Dasar Menggunakan t.Error
, t.Errorf
, t.Fatal
, t.Fatalf
Paket testing
menyediakan beberapa fungsi untuk assertion:
t.Error
: Melaporkan kegagalan dan melanjutkan eksekusi.t.Errorf
: Melaporkan kegagalan dengan pesan yang diformat dan melanjutkan eksekusi.t.Fatal
: Melaporkan kegagalan dan menghentikan fungsi pengujian.t.Fatalf
: Melaporkan kegagalan dengan pesan yang diformat dan menghentikan fungsi pengujian.
Contoh:
“`go
package main
import “testing”
func TestSubtract(t *testing.T) {
result := Subtract(5, 2)
if result != 3 {
t.Errorf(“Subtract(5, 2) = %d; want 3”, result)
}
// Jika terjadi kesalahan fatal, hentikan pengujian
if result < 0 {
t.Fatalf("Result is negative: %d", result)
}
}
```
Menjalankan Pengujian dengan go test
Anda dapat menjalankan pengujian Anda menggunakan perintah go test
. Perintah ini akan secara otomatis menemukan dan menjalankan semua fungsi pengujian dalam paket Anda.
Untuk menjalankan semua pengujian di direktori saat ini:
“`bash
go test
“`
Untuk menjalankan pengujian verbose dan melihat output detail:
“`bash
go test -v
“`
Untuk menjalankan pengujian tertentu:
“`bash
go test -run TestAdd
“`
Jenis Pengujian yang Berbeda
Ada beberapa jenis pengujian yang dapat Anda tulis untuk kode Go Anda, masing-masing dengan tujuan yang berbeda:
- Pengujian Unit: Menguji unit terkecil dari kode, seperti fungsi atau metode individu.
- Pengujian Integrasi: Menguji interaksi antara komponen atau modul yang berbeda.
- Pengujian End-to-End: Menguji seluruh sistem dari perspektif pengguna akhir.
Pengujian Unit
Pengujian unit berfokus pada pengujian fungsi atau metode individu secara terpisah dari sisa kode. Tujuannya adalah untuk memastikan bahwa setiap unit kode berfungsi seperti yang diharapkan secara independen.
Pengujian Integrasi
Pengujian integrasi memverifikasi bahwa komponen atau modul yang berbeda bekerja sama dengan benar. Mereka menguji interaksi antara bagian-bagian yang berbeda dari sistem untuk memastikan bahwa mereka terintegrasi dengan lancar.
Pengujian End-to-End
Pengujian end-to-end (E2E) menguji seluruh sistem dari perspektif pengguna akhir. Mereka mensimulasikan interaksi pengguna dengan aplikasi untuk memverifikasi bahwa semuanya berfungsi sebagaimana dimaksud. Pengujian E2E seringkali lebih lambat dan lebih rumit untuk diatur daripada pengujian unit atau integrasi.
Praktik Terbaik Pengujian Unit
Berikut adalah beberapa praktik terbaik untuk menulis pengujian unit yang efektif di Go:
- Menulis Pengujian yang Jelas dan Ringkas: Jaga agar pengujian Anda tetap singkat dan fokus. Setiap pengujian harus memverifikasi satu aspek perilaku kode Anda.
- Menggunakan Tabel Pengujian untuk Skenario Pengujian Berganda: Tabel pengujian memungkinkan Anda menjalankan pengujian yang sama dengan input yang berbeda dengan mudah.
- Mocking dan Stubbing Dependensi Eksternal: Gunakan mocking dan stubbing untuk mengisolasi unit kode yang sedang diuji dan menghindari ketergantungan pada sistem eksternal.
- Prinsip FIRST Pengujian Unit: Ikuti prinsip FIRST untuk menulis pengujian unit yang baik:
- Fast (Cepat): Pengujian harus berjalan dengan cepat.
- Independent (Independen): Pengujian tidak boleh bergantung satu sama lain.
- Repeatable (Dapat Diulang): Pengujian harus menghasilkan hasil yang sama setiap saat.
- Self-Validating (Validasi Mandiri): Pengujian harus secara otomatis mendeteksi apakah mereka lulus atau gagal.
- Timely (Tepat Waktu): Pengujian harus ditulis sedini mungkin dalam siklus pengembangan.
- Menguji Kasus Tepi dan Skenario Kesalahan: Pastikan untuk menguji kasus tepi dan skenario kesalahan untuk memastikan bahwa kode Anda menangani input yang tidak terduga dengan anggun.
- Menghindari Logika Berlebihan dalam Pengujian: Jaga agar pengujian Anda tetap sederhana dan fokus pada verifikasi perilaku kode Anda. Hindari menambahkan logika yang kompleks ke dalam pengujian Anda.
- Cakupan Kode dan Signifikansinya: Cakupan kode mengukur persentase kode Anda yang dicakup oleh pengujian Anda. Berusahalah untuk cakupan kode yang tinggi, tetapi ingat bahwa cakupan kode yang tinggi tidak menjamin bahwa kode Anda bebas bug.
Tabel Pengujian
Tabel pengujian adalah cara yang ampuh untuk menjalankan pengujian yang sama dengan input yang berbeda. Mereka memungkinkan Anda untuk menghindari duplikasi kode dan membuat pengujian Anda lebih mudah dibaca dan dipelihara.
Contoh:
“`go
package main
import “testing”
func TestAdd(t *testing.T) {
testCases := []struct {
name string
a int
b int
expected int
}{
{name: “Positive Numbers”, a: 2, b: 3, expected: 5},
{name: “Negative Numbers”, a: -2, b: -3, expected: -5},
{name: “Zero”, a: 0, b: 0, expected: 0},
{name: “Mixed”, a: -2, b: 3, expected: 1},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
result := Add(tc.a, tc.b)
if result != tc.expected {
t.Errorf(“Add(%d, %d) = %d; want %d”, tc.a, tc.b, result, tc.expected)
}
})
}
}
“`
Mocking dan Stubbing
Mocking dan stubbing adalah teknik yang digunakan untuk mengganti dependensi eksternal dengan versi tiruan selama pengujian unit. Ini memungkinkan Anda untuk mengisolasi unit kode yang sedang diuji dan menghindari ketergantungan pada sistem eksternal yang mungkin lambat, tidak dapat diandalkan, atau sulit untuk dikendalikan.
Go tidak memiliki kerangka mocking bawaan, tetapi ada beberapa pustaka mocking populer yang tersedia, seperti gomock
dan testify/mock
.
Pengujian Integrasi di Go
Pengujian integrasi berfokus pada pengujian interaksi antara komponen atau modul yang berbeda. Mereka memverifikasi bahwa bagian-bagian yang berbeda dari sistem bekerja sama dengan benar.
Menguji Interaksi Antara Komponen
Saat menulis pengujian integrasi, penting untuk fokus pada pengujian interaksi antara komponen. Ini berarti memverifikasi bahwa data mengalir dengan benar antara komponen dan bahwa mereka menangani kesalahan dengan anggun.
Menyiapkan Lingkungan Pengujian untuk Integrasi
Pengujian integrasi sering kali memerlukan lingkungan pengujian yang lebih kompleks daripada pengujian unit. Anda mungkin perlu menyiapkan basis data, server jaringan, atau sistem eksternal lainnya untuk menguji interaksi antara komponen Anda.
Pertimbangan Basis Data dan Jaringan
Saat menguji interaksi dengan basis data atau sistem jaringan, penting untuk mempertimbangkan hal berikut:
- Basis Data Pengujian: Gunakan basis data pengujian terpisah untuk pengujian integrasi Anda untuk menghindari memengaruhi data produksi Anda.
- Transaksi: Gunakan transaksi untuk memastikan bahwa pengujian Anda atomik dan dapat digulirkan kembali jika terjadi kesalahan.
- Mocking Jaringan: Gunakan mocking jaringan untuk mensimulasikan respons dari sistem eksternal dan menguji penanganan kesalahan Anda.
Benchmark di Go
Benchmark adalah jenis pengujian khusus yang digunakan untuk mengukur kinerja kode Anda. Mereka membantu Anda mengidentifikasi bottleneck kinerja dan mengoptimalkan kode Anda untuk kecepatan.
Menulis Benchmark dengan Paket testing
Anda dapat menulis benchmark di Go menggunakan paket testing
. Fungsi benchmark harus mengikuti konvensi penamaan tertentu:
- Nama fungsi harus dimulai dengan
Benchmark
. - Fungsi harus mengambil satu argumen dari tipe
*testing.B
.
Contoh:
“`go
package main
import “testing”
func BenchmarkAdd(b *testing.B) {
for i := 0; i < b.N; i++ {
Add(2, 3)
}
}
```
Menganalisis Hasil Benchmark
Setelah Anda menulis benchmark Anda, Anda dapat menjalankannya menggunakan perintah go test
dengan flag -bench
:
“`bash
go test -bench=.
“`
Perintah ini akan menjalankan semua benchmark dalam paket Anda dan menampilkan hasilnya. Hasil benchmark menunjukkan jumlah operasi per detik dan waktu yang dibutuhkan untuk menyelesaikan setiap operasi.
Optimasi Kinerja Berdasarkan Benchmark
Anda dapat menggunakan hasil benchmark untuk mengidentifikasi bottleneck kinerja dan mengoptimalkan kode Anda untuk kecepatan. Beberapa teknik optimasi umum meliputi:
- Profiling: Gunakan profiler untuk mengidentifikasi bagian kode Anda yang menghabiskan waktu terbanyak.
- Algoritma: Pilih algoritma yang paling efisien untuk tugas tersebut.
- Struktur Data: Gunakan struktur data yang tepat untuk kebutuhan Anda.
- Concurrency: Gunakan concurrency untuk memanfaatkan beberapa core CPU.
Pengujian Berbasis Properti
Pengujian berbasis properti adalah teknik pengujian yang menghasilkan input acak dan memverifikasi bahwa kode Anda memenuhi properti tertentu. Ini adalah cara yang ampuh untuk menemukan kasus tepi dan bug yang mungkin terlewatkan oleh pengujian tradisional.
Pengantar Pengujian Berbasis Properti
Dalam pengujian berbasis properti, Anda menentukan properti yang harus selalu benar untuk kode Anda, terlepas dari inputnya. Kerangka pengujian kemudian menghasilkan banyak input acak dan memverifikasi bahwa properti tersebut tetap benar untuk setiap input.
Menggunakan Pustaka Seperti gopter
Go tidak memiliki kerangka pengujian berbasis properti bawaan, tetapi ada beberapa pustaka yang tersedia, seperti gopter
. gopter
menyediakan API yang kuat untuk mendefinisikan properti dan menghasilkan input acak.
Menentukan Properti untuk Diuji
Saat menentukan properti untuk diuji, penting untuk fokus pada properti yang relevan dan bermakna untuk kode Anda. Properti harus mudah untuk didefinisikan dan diverifikasi.
Mengejek dan Mem-mock di Go
Mocking adalah teknik yang digunakan untuk mengganti dependensi eksternal dengan versi tiruan selama pengujian. Ini memungkinkan Anda untuk mengisolasi unit kode yang sedang diuji dan menghindari ketergantungan pada sistem eksternal yang mungkin lambat, tidak dapat diandalkan, atau sulit untuk dikendalikan.
Mengapa Mocking Penting
Mocking penting karena memungkinkan Anda untuk menguji kode Anda secara terpisah dari dependensinya. Ini membuat pengujian Anda lebih cepat, lebih dapat diandalkan, dan lebih mudah dipelihara.
Menggunakan Pustaka Mocking Populer Seperti gomock
Go tidak memiliki kerangka mocking bawaan, tetapi ada beberapa pustaka mocking populer yang tersedia, seperti gomock
dan testify/mock
. gomock
adalah kerangka mocking yang kuat yang menghasilkan mock dari antarmuka Go.
Membuat Mock untuk Antarmuka
Untuk membuat mock dengan gomock
, Anda terlebih dahulu perlu mendefinisikan antarmuka untuk dependensi Anda. Kemudian, Anda dapat menggunakan gomock
untuk menghasilkan mock dari antarmuka ini.
Mengelola Ekspektasi pada Mock
Setelah Anda membuat mock, Anda perlu mengatur ekspektasi pada mock tersebut. Ekspektasi menentukan bagaimana mock seharusnya dipanggil dan nilai apa yang seharusnya dikembalikan.
Pengujian HTTP di Go
Menguji handler HTTP di Go memerlukan penggunaan paket net/http/httptest
. Paket ini menyediakan alat untuk membuat permintaan dan respons HTTP tiruan, sehingga Anda dapat menguji handler Anda secara terpisah dari server HTTP yang sebenarnya.
Menguji Handler HTTP
Untuk menguji handler HTTP, Anda dapat membuat httptest.NewRequest
untuk membuat permintaan HTTP tiruan dan httptest.NewRecorder
untuk membuat respons HTTP tiruan. Kemudian, Anda dapat memanggil handler Anda dengan permintaan dan respons tiruan.
Menggunakan net/http/httptest
Berikut adalah contoh cara menggunakan net/http/httptest
untuk menguji handler HTTP:
“`go
package main
import (
“net/http”
“net/http/httptest”
“testing”
)
func TestMyHandler(t *testing.T) {
req, err := http.NewRequest(“GET”, “/my-endpoint”, nil)
if err != nil {
t.Fatal(err)
}
rr := httptest.NewRecorder()
handler := http.HandlerFunc(MyHandler)
handler.ServeHTTP(rr, req)
if status := rr.Code; status != http.StatusOK {
t.Errorf(“handler returned wrong status code: got %v want %v”,
status, http.StatusOK)
}
expected := `{“message”:”Hello, world!”}`
if rr.Body.String() != expected {
t.Errorf(“handler returned unexpected body: got %v want %v”,
rr.Body.String(), expected)
}
}
“`
Menulis Pengujian untuk Middleware
Anda juga dapat menggunakan net/http/httptest
untuk menguji middleware HTTP. Middleware adalah fungsi yang membungkus handler HTTP dan menambahkan fungsionalitas tambahan, seperti otentikasi atau logging.
Pengujian Basis Data di Go
Menguji interaksi basis data di Go memerlukan menyiapkan basis data pengujian terpisah dan menggunakan transaksi untuk memastikan bahwa pengujian Anda atomik dan dapat digulirkan kembali jika terjadi kesalahan.
Menyiapkan Basis Data Pengujian
Penting untuk menggunakan basis data pengujian terpisah untuk pengujian Anda untuk menghindari memengaruhi data produksi Anda. Anda dapat membuat basis data pengujian menggunakan skrip SQL atau dengan menggunakan kerangka migrasi basis data.
Menggunakan Transaksi untuk Pengujian
Transaksi memastikan bahwa pengujian Anda atomik dan dapat digulirkan kembali jika terjadi kesalahan. Ini mencegah pengujian Anda meninggalkan data dalam keadaan yang tidak konsisten.
Memvalidasi Data di Basis Data
Saat menguji interaksi basis data, penting untuk memvalidasi bahwa data yang disimpan di basis data benar. Anda dapat melakukan ini dengan menjalankan kueri SQL untuk mengambil data dan membandingkannya dengan nilai yang diharapkan.
Continuous Integration (CI) dan Pengujian
Continuous Integration (CI) adalah praktik menggabungkan perubahan kode secara teratur ke repositori pusat, lalu menjalankan build dan pengujian otomatis. Ini membantu Anda mendeteksi masalah sedini mungkin dan memastikan bahwa kode Anda selalu dalam keadaan yang dapat dirilis.
Mengintegrasikan Pengujian ke dalam Alur CI/CD
Penting untuk mengintegrasikan pengujian ke dalam alur CI/CD Anda. Ini memastikan bahwa pengujian Anda dijalankan secara otomatis setiap kali perubahan kode dilakukan.
Menggunakan Alat CI Seperti Jenkins, Travis CI, GitHub Actions
Ada beberapa alat CI yang tersedia, seperti Jenkins, Travis CI, dan GitHub Actions. Alat-alat ini menyediakan platform untuk mengotomatiskan proses build, pengujian, dan penerapan.
Otomatisasi Proses Pengujian
Mengotomatiskan proses pengujian sangat penting untuk CI. Ini memastikan bahwa pengujian Anda dijalankan secara konsisten dan efisien.
Tips dan Trik Lanjutan
Berikut adalah beberapa tips dan trik lanjutan untuk pengujian di Go:
- Menggunakan
go test -race
untuk Mendeteksi Kondisi Balapan: Flag-race
pada perintahgo test
mendeteksi kondisi balapan dalam kode Anda. Kondisi balapan terjadi ketika dua atau lebih goroutine mengakses variabel yang sama secara bersamaan dan setidaknya salah satu dari mereka menulis ke variabel tersebut. - Menggunakan Alat Cakupan: Alat cakupan membantu Anda mengukur persentase kode Anda yang dicakup oleh pengujian Anda. Ini membantu Anda mengidentifikasi area kode Anda yang kurang diuji.
- Menulis Pengujian Fuzz: Pengujian fuzz adalah teknik pengujian yang menghasilkan input acak dan menjalankan kode Anda dengan input tersebut. Ini membantu Anda menemukan bug dan kerentanan yang mungkin terlewatkan oleh pengujian tradisional.
Kesalahan Umum yang Harus Dihindari
Berikut adalah beberapa kesalahan umum yang harus dihindari saat menulis pengujian di Go:
- Pengujian yang Terlalu Kompleks: Jaga agar pengujian Anda tetap sederhana dan fokus pada verifikasi perilaku kode Anda.
- Mengabaikan Kasus Tepi: Pastikan untuk menguji kasus tepi dan skenario kesalahan.
- Tidak Menjaga Agar Pengujian Tetap Terisolasi: Pengujian Anda harus independen satu sama lain dan tidak bergantung pada status global.
- Bergantung pada Implementasi Daripada Perilaku: Pengujian Anda harus memverifikasi perilaku kode Anda, bukan implementasinya.
Sumber Daya dan Alat Pengujian Go
Berikut adalah beberapa sumber daya dan alat pengujian Go:
- Paket
testing
: Paket pengujian bawaan Go. gomock
: Kerangka mocking untuk Go.testify/mock
: Kerangka mocking lain untuk Go.gopter
: Kerangka pengujian berbasis properti untuk Go.net/http/httptest
: Paket untuk menguji handler HTTP.
Kesimpulan
Pengujian adalah bagian penting dari pengembangan perangkat lunak. Dengan mengikuti praktik terbaik yang dibahas dalam artikel ini, Anda dapat menulis tes yang efektif, terpelihara, dan andal untuk kode Go Anda.
Ingatlah untuk memasukkan pengujian ke dalam alur kerja pengembangan Anda dan terus berinvestasi dalam kualitas kode Anda. Dengan demikian, Anda dapat memastikan bahwa aplikasi Anda andal, mudah dipelihara, dan bebas bug.
“`