Test-Driven-Development-Golang
Ikhtisar Kursus.........................................................................................................................4
Mekanisme Pembelajaran............................................................................................................. 4
Materi Teks Interaktif............................................................................................................... 4
Kuis Interaktif dan Latihan.......................................................................................................4
Proyek dan Tugas Praktis........................................................................................................ 4
Platform Diskusi............................................................................................................................. 4
Pengenalan Testing................................................................................................................. 5
Bentuk Implementasi Testing........................................................................................................5
Mengenal Automation Testing...................................................................................................... 6
Mengenal package untuk implementasi testing.......................................................................... 6
Mempelajari Prinsip Test Driven Development............................................................................ 8
Red-Green-Refactor cycle pada Test Driven Development.......................................................... 9
Unit Testing dengan Go............................................................................................................ 9
Pengenalan pada testing package dalam Go...............................................................................9
Memahami bagaimana menulis test functions dalam Go........................................................ 11
Eksplorasi testing package functions dengan testing.T........................................................... 12
Table-Driven Tests........................................................................................................................12
Parameterized tests pada Golang...............................................................................................14
Integration Testing.................................................................................................................15
Perbedaan antara Unit Tests dan Integration Tests.................................................................. 15
Unit Testing............................................................................................................................ 15
Integration Testing................................................................................................................. 16
Implementasi Contoh Fungsi................................................................................................ 17
Membuat Test Cases untuk Multiple Components Interactions.............................................. 18
Membuat Test Case Skeleton............................................................................................... 18
Inisialisasi Komponen............................................................................................................18
Test Case untuk Interaksi...................................................................................................... 19
Implementasi Komponen...................................................................................................... 19
Naming Conventions....................................................................................................................20
Mengorganisasi Test Files dan Packages..................................................................................22
Handling Test Dependencies.......................................................................................................24
Testing pada HTTP Handler................................................................................................... 26
TDD Workflow dengan Echo........................................................................................................ 26
Hands-On: Menulis tests untuk API endpoints...........................................................................29
Strategi untuk Testing HTTP handlers........................................................................................31
Hands-On: Menggunakan net/http/httptest untuk HTTP responses....................................... 32
Testing pada Middleware....................................................................................................... 34
Testing Echo middleware functions............................................................................................34
Menentukan Kasus Pengujian (Test Case).......................................................................... 34
Implementasi Middleware..................................................................................................... 35
Property-based testing untuk Middleware..................................................................................36
Middleware: AuthorizeMiddleware....................................................................................... 36
Fungsi Properti: propertyAuthorizeMiddleware................................................................... 37
Fungsi Pengujian: TestAuthorizeMiddleware_Properties....................................................37
Table-driven tests untuk Middleware.......................................................................................... 38
Hands-On: Menulis Failing Test untuk Middleware....................................................................40
Testing pada Database...........................................................................................................42
Memulai Database Testing Interactions dalam Isolasi............................................................. 42
Menulis Unit Tests untuk CRUD operations dengan Database................................................. 45
Menyiapkan Database Testing Terpisah untuk Integration Testing..........................................48
Hands-On: Handling database transactions dalam tests..........................................................51
Mocking.................................................................................................................................53
Mengenal Mocking dalam Testing..............................................................................................53
Manual Mocks, Code Generation, and Dynamic Mocks............................................................ 54
Hands-On: Introduction to Mockgen........................................................................................... 55
Membangun Unit Testing dengan Mocking............................................................................... 57
Mocking functions dan methods dengan side effects.............................................................. 60
Mocking Interaksi pada Database.............................................................................................. 62
Inisialisasi Struktur Data........................................................................................................63
Fungsi-Fungsi Database........................................................................................................ 63
Handler dan Router................................................................................................................ 64
Unit Test dengan Mocking.....................................................................................................65
Mocking Best Practices............................................................................................................... 66
Test Coverage dan Benchmarking.......................................................................................... 67
Memahami Test Coverage dan Benchmarking.......................................................................... 67
Menganalisa Hasil Test Coverage dengan go test.................................................................... 68
Best Practice dalam Mendapatkan High Test Coverage........................................................... 69
Profiling dan Optimization pada Test Coverage dan Benchmarking........................................ 71
Test Coverage........................................................................................................................ 71
Benchmarking........................................................................................................................ 71
Optimization........................................................................................................................... 72
Ikhtisar Kursus
Duration: 10:00
Mekanisme Pembelajaran
Hai Teman-teman SMKDEV, Selamat Datang 🙌🙌🙌 . Ini beberapa mekanisme yang kami
terapkan selama kamu mengakses pembelajaran ini agar menjadi tempat yang efektif untuk
meningkatkan efektifitas pembelajaran kamu.
Platform Diskusi
Selama kamu belajar pada platform ini, akan sangat memungkinkan adanya pertanyaan yang
dapat membantu menyelesaikan pembelajaran kamu. Disini juga memungkinkan kamu untuk
berinteraksi melalui forum atau fasilitas diskusi untuk berbagi pemikiran, memecahkan
masalah, dan belajar bersama. Para Curriculum Developer yang membangun kelas ini akan siap
membantu kamu jika ada pertanyaan yang berkaitan dengan proses pembelajaran kamu.
Untuk melakukan diskusi maupun pertanyaan yang berkaitan, kamu bisa bergabung ke
Komunitas SMKDEV disini
Pengenalan Testing
Duration: 60:00
Salah satu bentuk implementasi testing yang umum digunakan dalam TDD pada Golang adalah
menggunakan package bawaan "testing" yang disertakan dengan bahasa pemrograman ini.
Pengujian unit dapat dibuat dalam bentuk file terpisah yang mengandung fungsi-fungsi
pengujian yang memvalidasi berbagai bagian kode. Setiap fungsi pengujian harus diawali
dengan kata kunci "Test" dan dapat menggunakan fungsi bantu seperti t.Errorf untuk
memberikan laporan kesalahan.
Selain itu, Golang juga mendukung benchmarking dengan menggunakan package "testing" yang
memungkinkan pengukuran kinerja dan identifikasi area-area yang memerlukan optimalisasi.
Fungsi benchmark ditulis dalam file yang berbeda dan diawali dengan kata kunci "Benchmark".
Pentingnya integrasi berkelanjutan dalam TDD pada Golang menekankan perlunya otomatisasi
pengujian melalui perangkat lunak seperti Jenkins atau GitLab CI. Ini memastikan bahwa setiap
kali ada perubahan dalam kode, serangkaian pengujian dapat dijalankan secara otomatis untuk
memastikan bahwa perubahan tersebut tidak merusak fungsionalitas yang ada.
Selain itu, Golang juga mendukung konsep mocking melalui pustaka-pustaka seperti
"github.com/stretchr/testify/mock," yang memungkinkan penggantian objek atau fungsi palsu
untuk mengisolasi unit pengujian dan memastikan bahwa setiap komponen bekerja
sebagaimana mestinya tanpa terpengaruh oleh komponen lainnya.
Dengan mengintegrasikan berbagai bentuk implementasi testing ini, pengembang Golang dapat
membangun sistem yang andal, mudah dipelihara, dan sesuai dengan prinsip-prinsip TDD,
memastikan bahwa setiap perubahan pada kode diuji secara menyeluruh sepanjang siklus
pengembangan perangkat lunak.
Mengenal Automation Testing
Automation testing merupakan suatu pendekatan dalam pengujian perangkat lunak yang
menggunakan alat dan skrip otomatis untuk menjalankan tes perangkat lunak, mengidentifikasi
kegagalan, dan memverifikasi bahwa aplikasi berperilaku sesuai dengan yang diharapkan.
Dalam konteks pengenalan testing untuk Test Driven Development (TDD) pada bahasa
pemrograman Golang, automation testing memainkan peran kunci dalam mendukung siklus
pengembangan perangkat lunak yang terfokus pada uji coba.
Pertama-tama, dalam TDD, pengembang menulis tes unit sebelum mengimplementasikan kode
fungsional. Dengan adanya automation testing pada Golang, pengujian ini dapat dengan mudah
dijalankan secara otomatis setiap kali terjadi perubahan pada kode. Framework otomatisasi
seperti testing dan mocking tools di Golang memungkinkan pembuatan dan pelaksanaan tes
secara efisien, mempercepat siklus pengembangan dan mengidentifikasi kesalahan lebih awal.
Selain itu, automation testing memberikan kepastian bahwa perubahan pada kode tidak
merusak fungsionalitas yang sudah ada. Saat pengembang membuat perubahan, tes otomatis
dapat memberikan umpan balik segera tentang apakah perubahan tersebut memenuhi
spesifikasi yang telah ditentukan sebelumnya. Hal ini mendukung prinsip TDD dalam
mengurangi risiko terjadinya bug atau ketidakcocokan dengan kebutuhan.
Selain uji unit, automation testing pada Golang juga mendukung uji integrasi dan uji fungsional.
Dengan menggunakan alat-alat otomatisasi yang sesuai, pengembang dapat mengotomatiskan
pengujian pada berbagai tingkatan, memastikan bahwa keseluruhan sistem berfungsi dengan
baik dan sesuai dengan kebutuhan pengguna.
Secara keseluruhan, mengenal automation testing pada pengenalan testing untuk Test Driven
Development pada Golang menjadi krusial dalam meningkatkan efisiensi pengembangan
perangkat lunak, meningkatkan kualitas kode, dan memastikan kehandalan aplikasi secara
berkelanjutan. Integrasi yang baik antara TDD dan automation testing pada Golang menjadi
landasan untuk mencapai pengembangan perangkat lunak yang responsif, teruji, dan dapat
dipertahankan.
Selain itu, package "testing/quick" memberikan kemampuan untuk melakukan generasi data uji
otomatis. Dengan menggunakan fitur ini, pengembang dapat menguji fungsi atau metode
dengan berbagai macam input secara otomatis, sehingga memperluas cakupan pengujian.
Package ini sangat berguna untuk menemukan skenario uji yang tidak terduga dan mendukung
pendekatan TDD dengan memungkinkan pengembang menulis tes sebelum
mengimplementasikan fungsionalitas.
Dalam konteks TDD, pengembang biasanya memulai dengan menulis tes unit menggunakan
package "testing", mengeksekusi tes tersebut untuk memverifikasi kegagalan, dan kemudian
mengimplementasikan kode yang dibutuhkan untuk membuat tes tersebut berhasil. Proses ini
diulang secara berulang hingga seluruh fungsionalitas yang diinginkan tercapai. Dengan
integrasi package "testing/quick", pengembang dapat memastikan bahwa kode mereka dapat
menangani berbagai situasi input dengan baik.
Selain package "testing" dan "testing/quick," terdapat juga package "testing/assert" yang dapat
membantu dalam penulisan tes dengan lebih ringkas dan ekspresif. Package ini menyediakan
fungsi-fungsi bantu untuk memeriksa kebenaran pernyataan dan menghasilkan laporan yang
lebih deskriptif saat tes gagal. Penggunaan package "testing/assert" dapat membuat kode tes
menjadi lebih mudah dibaca dan meminimalisir boilerplate code yang mungkin diperlukan
dalam pengujian.
Dalam praktik TDD, penting untuk memahami konsep subtes dan sub-benchmark. Subtes
memungkinkan pengembang untuk membagi tes utama menjadi bagian-bagian yang lebih kecil
dan terfokus, sementara sub-benchmark memungkinkan pengukuran performa yang lebih
terperinci pada bagian-bagian tertentu dari implementasi.
Selain package bawaan dari Golang, terdapat juga library pihak ketiga seperti "testify" yang
menyediakan fitur tambahan untuk mempermudah penulisan tes. Testify menyediakan assert
yang lebih kaya dan juga menyediakan fasilitas untuk membuat tes yang lebih terstruktur dan
mudah dibaca.
Dengan menggabungkan package bawaan, library pihak ketiga, dan praktik TDD, pengembang
Golang dapat membangun sistem pengujian yang kuat dan efektif untuk memastikan kualitas
dan keandalan perangkat lunak mereka. Keseluruhan, penggunaan package dan library ini
membantu menciptakan alur kerja TDD yang baik dan mendorong pengembang untuk menulis
kode yang lebih aman, mudah dimengerti, dan mudah dipelihara.
Pertama, langkah pertama dalam pengenalan testing untuk TDD pada Golang melibatkan
penulisan tes yang merinci perilaku yang diinginkan dari suatu fungsi atau fitur. Golang
menyediakan package testing yang memudahkan penulisan tes, dan tes dapat ditempatkan
dalam file terpisah dengan awalan "_test.go". Tes ini kemudian dapat dieksekusi dengan
perintah go test.
Setelah implementasi selesai, langkah terakhir adalah melakukan refaktor. Tujuan refaktor
adalah meningkatkan kejelasan, efisiensi, dan pemeliharaan kode tanpa mengubah
fungsionalitasnya. Dengan TDD, pengembang dapat dengan percaya diri melakukan refaktor
karena ada serangkaian tes yang dapat memverifikasi bahwa perubahan tidak merusak
fungsionalitas yang ada.
Penerapan TDD pada Golang juga melibatkan penggunaan fitur-fitur unik bahasa ini, seperti
goroutines dan channels, untuk menguji dan mengelola konkurensi. Dengan pendekatan ini,
pengembangan perangkat lunak menjadi lebih terstruktur dan terkendali, dengan keuntungan
tambahan dalam hal keamanan dan pemeliharaan kode. Dengan memahami dan menerapkan
prinsip-prinsip TDD ini, pengembang Golang dapat menghasilkan perangkat lunak yang lebih
handal, mudah dipelihara, dan sesuai dengan kebutuhan pengguna.
Red-Green-Refactor cycle pada Test Driven Development
Red-Green-Refactor (RGR) cycle adalah konsep fundamental dalam Test Driven Development
(TDD), suatu pendekatan pengembangan perangkat lunak yang menempatkan pengujian
sebagai tahap utama dalam proses pengembangan. Siklus ini terdiri dari tiga tahap: Red, Green,
dan Refactor.
Pertama-tama, dalam tahap "Red," pengembang menulis tes unit sebelum menulis kode
implementasi. Tujuan dari tes ini adalah untuk gagal atau "merah" karena implementasi yang
sebenarnya belum ada. Tes ini membantu merinci fungsionalitas yang diinginkan dan berfungsi
sebagai panduan untuk pengembangan selanjutnya.
Setelah tes unit ditulis dan gagal, langkah selanjutnya adalah "Green." Pada tahap ini,
pengembang menulis kode implementasi yang cukup untuk membuat tes unit berhasil atau
"hijau." Fokus utama pada tahap ini adalah memenuhi persyaratan tes dengan cara yang
sederhana dan langsung.
Setelah tahap "Green," dilanjutkan dengan tahap "Refactor." Pada tahap ini, pengembang
memperbaiki dan meningkatkan kode implementasi tanpa mengubah fungsionalitas yang diuji
oleh tes. Refaktorisasi bertujuan untuk meningkatkan kejelasan, pemeliharaan, dan efisiensi
kode tanpa mengorbankan fungsi yang diinginkan.
RGR cycle kemudian berulang secara iteratif. Setiap kali sebuah fitur ditambahkan atau
perubahan dilakukan, siklus ini diulang. Dengan mengikuti siklus ini, pengembang dapat
memastikan bahwa kode mereka terus diuji secara otomatis dan tetap memenuhi persyaratan
bisnis yang diinginkan. Pendekatan ini tidak hanya membantu menciptakan kode yang lebih
andal dan mudah dimengerti, tetapi juga memungkinkan refaktorisasi yang berkelanjutan,
mendukung evolusi perangkat lunak seiring waktu. Dalam konteks pengenalan testing untuk
Test Driven Development pada Golang, penerapan siklus RGR memungkinkan pengembang Go
untuk menghasilkan kode yang handal dan mudah diuji dalam lingkungan yang khas untuk
bahasa pemrograman tersebut.
// calculator.go
package calculator
Selanjutnya, kita akan membuat file calculator_test.go untuk menguji fungsi Add tersebut:
// calculator_test.go
package calculator
import "testing"
if result != expected {
t.Errorf("Expected %d, but got %d", expected, result)
}
}
Pada contoh di atas, kita membuat fungsi TestAdd yang menggunakan objek *testing.T
untuk melakukan asertasi. Jika hasil dari fungsi Add(2, 3) tidak sama dengan nilai yang
diharapkan (expected), maka test ini akan gagal dan mengeluarkan pesan kesalahan yang
dijelaskan dalam t.Errorf.
Setelah menulis test, kita dapat menjalankannya dengan perintah go test pada terminal:
go test
Golang akan secara otomatis menemukan dan menjalankan semua file test dalam direktori saat
ini. Jika semua test berhasil, tidak akan ada output yang dihasilkan, tetapi jika ada test yang
gagal, Golang akan menampilkan informasi kesalahan yang membantu untuk mengidentifikasi
dan memperbaiki masalahnya.
Dengan menggunakan package testing dan mengikuti praktik TDD, pengembang dapat
membangun dan memelihara kode yang lebih handal, serta memastikan bahwa perubahan yang
dilakukan tidak merusak fungsionalitas yang ada.
Pertama, seorang pengembang perlu membuat file test terpisah dengan awalan nama file
"namafile_test.go" untuk setiap file yang akan diuji. Ini memisahkan kode aplikasi dari kode
pengujian, memfasilitasi pengelolaan yang lebih baik.
Pada contoh di atas, Tambah adalah fungsi yang perlu diuji, dan pengujian dilakukan dengan
membandingkan hasil yang diharapkan dengan hasil yang sebenarnya. Jika hasilnya tidak
sesuai, t.Errorf akan digunakan untuk memberi tahu pengembang tentang kesalahan.
Pada contoh di atas, TestTambah dan TestKurang adalah dua fungsi pengujian yang dijalankan
sebagai bagian dari pengujian utama.
Dengan menggunakan pendekatan ini, pengembang dapat secara berkelanjutan menulis atau
memperbarui fungsi pengujian saat mengembangkan kode aplikasi. Ini memastikan bahwa
perubahan kode tidak merusak fungsionalitas yang ada dan memberikan kepercayaan bahwa
kode berfungsi seperti yang diinginkan. Dengan demikian, pemahaman komprehensif tentang
penulisan test functions dalam Go untuk TDD membantu menciptakan kode yang andal dan
mudah dipelihara.
Pertama, fungsi t.Run dapat digunakan untuk mengelompokkan beberapa tes ke dalam satu
unit yang lebih besar. Hal ini mempermudah organisasi dan eksekusi berbagai skenario
pengujian. Selanjutnya, t.Helper dapat digunakan untuk menandai fungsi bantuan (helper
functions) yang membantu dalam pengujian, sehingga laporan kegagalan lebih mudah
dipahami.
Penting untuk memahami cara menggunakan fungsi-fungsi seperti t.Errorf dan t.FailNow
untuk melaporkan kesalahan dan menghentikan eksekusi tes jika ditemukan kegagalan. Selain
itu, t.Skip dapat digunakan untuk melewati tes jika suatu kondisi tertentu tidak terpenuhi,
memungkinkan pengembang untuk mengatasi masalah lebih lanjut.
Melalui eksplorasi testing package functions dengan testing.T, pengembang Golang dapat
membangun suite pengujian yang kuat dan efektif, yang membantu memastikan kualitas kode
secara berkelanjutan selama pengembangan. Dengan TDD, pengembang dapat merancang,
mengimplementasikan, dan memvalidasi fungsionalitas baru dengan keyakinan bahwa kode
tersebut telah melewati serangkaian tes unit yang komprehensif.
Table-Driven Tests
Table-Driven Tests adalah pendekatan dalam pengujian unit di Go yang digunakan untuk
menerapkan Test-Driven Development (TDD). Dalam TDD, pengembangan perangkat lunak
dimulai dengan menulis tes sebelum mengimplementasikan kode fungsional. Table-Driven
Tests memperkuat konsep ini dengan menyediakan struktur yang terorganisir untuk menguji
serangkaian kasus uji dengan menggunakan tabel atau struktur data yang menyimpan input dan
output yang diharapkan.
Dalam konteks Go, Table-Driven Tests biasanya melibatkan penggunaan slice atau array untuk
menyimpan data uji dan perulangan untuk mengulangi serangkaian kasus uji. Misalnya, sebuah
fungsi yang perlu diuji dapat diberikan sejumlah kasus uji, dan tabel tersebut dapat mencakup
parameter input dan nilai yang diharapkan untuk setiap kasus.
package mypackage
import (
"testing"
)
Dalam contoh ini, fungsi TestAdd mengulangi serangkaian kasus uji yang didefinisikan dalam
testCases. Untuk setiap kasus uji, fungsi Add dipanggil dengan parameter yang sesuai, dan
hasilnya dibandingkan dengan nilai yang diharapkan. Jika ada perbedaan, fungsi t.Errorf
digunakan untuk memberikan laporan kesalahan yang menyertakan parameter input, hasil
aktual, dan nilai yang diharapkan.
Dalam Golang, kita dapat menggunakan package pihak ketiga seperti "testify" atau membuat
solusi kustom sendiri untuk mengimplementasikan parameterized tests. Berikut adalah contoh
penggunaan package "testify" untuk membuat parameterized tests:
package main
import (
"testing"
"github.com/stretchr/testify/assert"
)
Pada contoh kode di atas, kita memiliki fungsi Add yang akan diuji, dan kita menggunakan
struktur testData untuk menyimpan data input dan hasil yang diharapkan. Dalam fungsi
TestAdd, kita mendefinisikan daftar kasus uji dan menggunakan t.Run untuk menjalankan
setiap tes dengan memberikan pesan yang jelas berdasarkan input.
Dengan cara ini, jika salah satu kasus uji gagal, laporan kesalahan akan memberikan informasi
yang lebih kaya mengenai input yang menyebabkan kesalahan. Ini membuat proses debug
menjadi lebih efisien dan membantu pengembang memahami di mana masalahnya terjadi.
Integration Testing
Duration: 90:00
Unit Testing
Unit Tests bertujuan untuk menguji komponen perangkat lunak secara terisolasi, fokus pada
satu fungsi atau metode pada suatu waktu. Dalam konteks TDD, Unit Tests dibuat sebelum
implementasi fungsi atau metode itu sendiri. Contoh implementasinya dalam Go bisa seperti
berikut:
package main
import (
"testing"
)
package main
Pada contoh di atas, TestAdd merupakan Unit Test yang menguji fungsi Add untuk
memastikan bahwa operasi penjumlahan berfungsi dengan benar.
Integration Testing
Integration Tests, di sisi lain, fokus pada interaksi antar komponen atau modul perangkat lunak.
Dalam TDD, Integration Tests dibuat setelah sejumlah Unit Tests berhasil diimplementasikan.
Contoh implementasinya dalam Go dapat seperti berikut:
package main
import (
"testing"
)
package main
Dengan demikian, Unit Tests dan Integration Tests saling melengkapi dalam TDD, memastikan
setiap komponen berfungsi secara terpisah dan juga bersama-sama saat diintegrasikan.
import (
"testing"
)
Inisialisasi Komponen
Buat fungsi untuk menginisialisasi dan menyiapkan komponen-komponen yang akan diuji.
func setupComponents() (*ComponentA, *ComponentB) {
// Inisialisasi dan kembalikan instance komponen
a := NewComponentA()
b := NewComponentB()
return a, b
}
Test Case untuk Interaksi
Implementasikan test case yang mengevaluasi interaksi antara ComponentA dan
ComponentB.
func TestComponentInteractions(t *testing.T) {
// Inisialisasi komponen
componentA, componentB := setupComponents()
Implementasi Komponen
Implementasikan komponen-komponen dan fungsinya. Misalnya:
type ComponentA struct{}
Dalam Go, konvensi penamaan umumnya mengikuti pola CamelCase, di mana kata-kata
pertama dimulai dengan huruf kapital, dan kata-kata berikutnya dimulai dengan huruf kapital
juga. Misalnya, untuk suatu tes integrasi dalam package integration_test, nama fungsi tes
bisa diawali dengan "Test" diikuti dengan nama fungsionalitas yang diuji. Sebagai contoh, jika
kita memiliki fungsionalitas untuk mengintegrasikan dua komponen yang berurusan dengan
pengguna, kita dapat menamai tes sebagai TestUserIntegration.
// integration_test.go
package integration_test
import (
"testing"
"github.com/yourusername/yourproject/pkg/user"
"github.com/yourusername/yourproject/pkg/auth"
)
// Langkah 1
newUser := user.User{Name: "John Doe", Email:
"john@example.com"}
err := userManager.CreateUser(newUser)
if err != nil {
t.Fatalf("Gagal membuat pengguna: %v", err)
}
// Langkah 2
createdUser, err :=
userManager.GetUserByEmail("john@example.com")
if err != nil {
t.Fatalf("Gagal mendapatkan pengguna: %v", err)
}
// Langkah 3
token, err := authService.GenerateToken(createdUser.ID)
if err != nil {
t.Fatalf("Gagal membuat token otentikasi: %v", err)
}
// Langkah 4
authenticatedUser, err := authService.Authenticate(token)
if err != nil {
t.Fatalf("Gagal melakukan otentikasi: %v", err)
}
if authenticatedUser.ID != createdUser.ID {
t.Errorf("ID pengguna tidak sesuai setelah
otentikasi. Got: %d, Expected: %d", authenticatedUser.ID,
createdUser.ID)
}
}
myproject/
|-- app/
| |-- main.go
|-- pkg/
| |-- mypackage/
| |-- mymodule.go
|-- test/
| |-- integration/
| |-- mymodule_test.go
Di sini, myproject adalah proyek utama, app berisi file utama, pkg berisi package yang ingin
di-test, dan test adalah direktori untuk menyimpan file test. Sekarang, mari buat file
mymodule_test.go untuk melakukan integration testing:
// mymodule_test.go
package integration
import (
"testing"
"myproject/pkg/mypackage"
)
Penting untuk memisahkan unit testing dan integration testing. Jika kita memiliki banyak fungsi
atau modul dalam package mypackage, kita dapat membuat beberapa fungsi pengujian dalam
satu file atau membuat file pengujian terpisah untuk setiap fungsi atau modul.
Dengan mengatur struktur direktori dan file test kita dengan baik, memisahkan unit dan
integration testing, serta menyediakan deskripsi yang jelas dalam setiap fungsi pengujian, kita
dapat dengan mudah mengintegrasikan praktik Test Driven Development (TDD) ke dalam proyek
Golang kita. Prinsip-prinsip ini membantu memastikan bahwa kode kita dapat diuji dan
diperbarui dengan efisien sambil menjaga keberlanjutan proyek.
Go memiliki beberapa cara untuk mengelola dependensi uji integrasi, dan salah satu
pendekatannya adalah menggunakan fitur yang disediakan oleh package testing. Kode
implementasi di bawah ini memberikan contoh cara menangani dependensi uji integrasi dalam
Go:
// package main_test
package main_test
import (
"net/http"
"net/http/httptest"
"testing"
"github.com/stretchr/testify/assert"
yang disimulasikan.
return httptest.NewServer(http.HandlerFunc(func(w
http.ResponseWriter, r *http.Request) {
w.Write([]byte("Hello, Test!"))
}))
diuji.
if err != nil {
defer resp.Body.Close()
lainnya).
server simulasi.
server := setupTestServer()
defer server.Close()
// URL server simulasi
serverURL := server.URL
assert.NoError(t, err)
Penjelasan:
Workflow TDD dalam Echo dimulai dengan menentukan spesifikasi atau kasus uji (test case)
untuk HTTP handler yang akan diimplementasikan. Setelah itu, implementasi HTTP handler
dibuat dengan kode yang memenuhi syarat tes tersebut. Berikutnya, kita menjalankan tes untuk
memastikan bahwa handler tersebut berfungsi dengan benar. Jika tes berhasil, kita dapat
melanjutkan untuk menambahkan fitur atau handler baru dengan mengulangi langkah-langkah
tersebut.
// main.go
package main
import (
"net/http"
"github.com/labstack/echo/v4"
)
func main() {
e := echo.New()
// handler.go
package main
import (
"net/http"
"github.com/labstack/echo/v4"
)
// HelloHandler adalah contoh HTTP handler yang akan diuji
func HelloHandler(c echo.Context) error {
return c.String(http.StatusOK, "Hello, World!")
}
Berikut adalah contoh implementasi tes (test) untuk HTTP handler di atas:
// main_test.go
package main
import (
"net/http"
"net/http/httptest"
"testing"
"github.com/labstack/echo/v4"
"github.com/stretchr/testify/assert"
)
Dengan menggunakan pendekatan TDD seperti ini, kita dapat memastikan bahwa setiap
perubahan atau penambahan fitur pada handler diuji secara otomatis, sehingga meningkatkan
keandalan dan kualitas kode yang dikembangkan.
Dalam TDD untuk HTTP handler pada Golang, langkah pertama adalah menentukan API
endpoint yang akan diimplementasikan. Setelah itu, kita dapat membuat tes untuk
memverifikasi perilaku yang diharapkan dari endpoint tersebut. Sebagai contoh, pertimbangkan
pengembangan API endpoint sederhana untuk mendapatkan informasi pengguna.
// user_handler.go
package handlers
import (
"encoding/json"
"net/http"
)
w.Header().Set("Content-Type", "application/json")
w.Write(jsonResponse)
}
// user_handler_test.go
package handlers
import (
"net/http"
"net/http/httptest"
"testing"
)
Tes ini menguji apakah handler berfungsi seperti yang diharapkan, yaitu mengembalikan status
OK (200) dan data pengguna dalam format JSON yang benar. Dengan cara ini, setiap perubahan
dalam implementasi handler dapat segera terdeteksi jika menyebabkan kesalahan dalam
perilaku yang diharapkan. Hal ini mendukung pengembangan yang aman, terdokumentasi, dan
mudah dipelihara.
package main
import (
"net/http"
"net/http/httptest"
"testing"
)
Dalam contoh ini, MyHandler adalah fungsi HTTP handler yang sederhana yang merespons
dengan status OK dan pesan "Hello, World!". TestMyHandler adalah fungsi pengujian yang
menggunakan httptest.NewRecorder untuk merekam respon HTTP dan
http.NewRequest untuk membuat permintaan palsu. Setelah menangani permintaan dengan
handler yang diuji, kode pengujian memeriksa apakah status kode dan isi respon sesuai dengan
yang diharapkan.
Dengan strategi ini, pengujian dapat diintegrasikan ke dalam siklus pengembangan dengan
cepat dan memberikan keyakinan bahwa handler HTTP berfungsi dengan benar. Pengujian ini
juga membantu dalam mendeteksi perubahan yang tidak diinginkan ketika kode handler diubah
atau diperbarui.
package main
import (
"net/http"
"net/http/httptest"
"testing"
)
Pada contoh di atas, helloHandler adalah handler HTTP yang sederhana. Fungsi
TestHelloHandler adalah fungsi pengujian yang menggunakan
httptest.ResponseRecorder untuk merekam respons HTTP dan
httptest.NewRequest untuk membuat permintaan HTTP simulasi. Setelah itu, handler
dijalankan dan respons yang dihasilkan diperiksa dengan membandingkannya dengan nilai yang
diharapkan. Jika ada ketidakcocokan, pengujian akan gagal dan memberikan pesan kesalahan
yang sesuai. Dengan cara ini, penggunaan net/http/httptest sangat mendukung
pendekatan Test Driven Development dalam pengembangan aplikasi web menggunakan
Golang.
Dalam TDD, langkah pertama adalah menentukan kasus pengujian atau "test case" sebelum
mengimplementasikan kode. Untuk menguji middleware pada Echo, kita dapat menggunakan
package pengujian bawaan Golang (testing) dan Echo untuk membuat unit test yang memeriksa
fungsi-fungsi middleware.
// file: middleware_test.go
package main
import (
"net/http"
"net/http/httptest"
"testing"
"github.com/labstack/echo/v4"
"github.com/stretchr/testify/assert"
)
Implementasi Middleware
Setelah menentukan kasus pengujian, kita dapat mengimplementasikan middleware yang akan
diuji. Berikut adalah contoh middleware yang menambahkan header kustom ke setiap
tanggapan.
// file: middleware.go
package main
import "github.com/labstack/echo/v4"
return err
}
}
}
Dengan menggunakan pendekatan TDD, kita dapat memastikan bahwa middleware berfungsi
dengan benar dan dapat memahami dampaknya terhadap aplikasi secara keseluruhan. Proses
pengujian dapat diulangi setiap kali ada perubahan pada middleware, sehingga memastikan
keberlanjutan dan keandalan fungsionalitasnya.
Property-based testing melibatkan generasi otomatis sejumlah besar data uji yang memenuhi
properti tertentu, dan pengujian dilakukan dengan memverifikasi apakah properti tersebut tetap
terjaga. Dalam kasus Middleware, properti dapat mencakup kestabilan sistem, ketersediaan,
dan responsibilitas yang sesuai.
Contoh implementasi Property-based testing pada Middleware di Golang dapat dibagi menjadi
beberapa bagian dengan menggunakan library testing seperti testing dan quick. Pertimbangkan
contoh Middleware sederhana yang memvalidasi otorisasi pada permintaan HTTP.
Middleware: AuthorizeMiddleware
// Middleware untuk otorisasi
func AuthorizeMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r
*http.Request) {
// Logika otorisasi (contoh sederhana, hanya untuk tujuan
demonstrasi)
if r.Header.Get("Authorization") != "valid_token" {
http.Error(w, "Unauthorized",
http.StatusUnauthorized)
return
}
handler := AuthorizeMiddleware(http.HandlerFunc(func(w
http.ResponseWriter, r *http.Request) {}))
Property-based testing memungkinkan kita untuk secara otomatis menguji sejumlah besar
kasus uji potensial tanpa menuliskan setiap kasus uji secara manual. Hal ini dapat membantu
meningkatkan cakupan pengujian dan mengidentifikasi kasus uji yang mungkin terlewatkan
dalam pengujian tradisional.
package middleware
import (
"net/http"
"net/http/httptest"
"testing"
)
Dalam contoh ini, terdapat struktur data testCases yang berisi informasi-informasi uji, seperti
nama kasus, permintaan HTTP, dan kode respons yang diharapkan. Fungsi
TestMiddlewareHandler kemudian mengiterasi melalui testCases, menjalankan
middleware pada setiap kasus, dan memverifikasi apakah hasilnya sesuai dengan yang
diharapkan.
Contoh berikut menggunakan bahasa pemrograman Go (Golang) dan framework web Echo
untuk menggambarkan proses menulis failing test untuk middleware. Anggaplah kita memiliki
middleware sederhana yang memeriksa apakah pengguna yang mengakses suatu route
memiliki token autentikasi yang valid. Berikut adalah contoh kode:
// middleware/auth.go
package middleware
import (
"net/http"
"github.com/labstack/echo/v4"
)
func AuthMiddleware(next echo.HandlerFunc) echo.HandlerFunc {
return func(c echo.Context) error {
// Pemeriksaan token autentikasi (dummy implementation)
token := c.Request().Header.Get("Authorization")
if token != "valid_token" {
return c.JSON(http.StatusUnauthorized,
map[string]string{"error": "Unauthorized"})
}
return next(c)
}
}
Middleware ini mengecek header Authorization dari permintaan dan memastikan bahwa token
yang diberikan adalah "valid_token". Sekarang, mari tulis test yang gagal untuk middleware ini:
// middleware/auth_test.go
package middleware_test
import (
"net/http"
"net/http/httptest"
"testing"
"github.com/labstack/echo/v4"
"github.com/stretchr/testify/assert"
"github.com/your-package/middleware"
)
// Menjalankan middleware
handler := middleware.AuthMiddleware(func(c echo.Context)
error {
return c.String(http.StatusOK, "Authorized")
})
err := handler(c)
// Menggunakan assert dari testify untuk memastikan bahwa
middleware mengembalikan Unauthorized
assert.Equal(t, http.StatusUnauthorized, rec.Code)
assert.Contains(t, rec.Body.String(), "Unauthorized")
assert.Error(t, err)
}
Test ini dimulai dengan membuat instance Echo, menyiapkan request dengan token autentikasi
yang tidak valid, dan menjalankan middleware. Setelah itu, kita menggunakan asserst dari
package testify untuk memastikan bahwa middleware mengembalikan status Unauthorized dan
pesan yang sesuai.
Selanjutnya, langkah selanjutnya setelah test ini adalah memperbaiki implementasi middleware
sehingga test ini berhasil (passing test). Dengan melakukan ini, kita dapat memiliki keyakinan
bahwa middleware kita berfungsi sebagaimana mestinya.
Contoh di bawah ini akan memberikan gambaran tentang bagaimana memulai pengujian
interaksi dengan database menggunakan GORM dalam pengaturan server web Echo di
lingkungan Golang. Pertama, kita harus membuat model data untuk entitas yang akan diuji. Mari
anggap kita memiliki model User yang akan disimpan di database.
package models
import "gorm.io/gorm"
type User struct {
gorm.Model
Name string
Email string
}
Selanjutnya, kita akan membuat pengaturan GORM dan Echo untuk koneksi dan pengaturan
server web.
package main
import (
"gorm.io/driver/sqlite"
"gorm.io/gorm"
"github.com/labstack/echo/v4"
)
var (
db *gorm.DB
)
func init() {
var err error
db, err = gorm.Open(sqlite.Open("test.db"), &gorm.Config{})
if err != nil {
panic("Failed to connect to database")
}
func main() {
e := echo.New()
e.Start(":8080")
}
Dengan konfigurasi di atas, kita telah membuat dan menghubungkan database SQLite untuk
digunakan dalam pengujian. Sekarang, kita dapat membuat fungsi pengujian untuk memastikan
interaksi dengan database berjalan dengan benar. Contoh fungsi pengujian dapat dilihat di
bawah ini.
package main_test
import (
"net/http"
"net/http/httptest"
"testing"
"github.com/labstack/echo/v4"
"github.com/stretchr/testify/assert"
"your-app/models"
)
// Assertions
assert.NoError(t, err)
assert.Equal(t, http.StatusOK, rec.Code)
Dalam contoh ini, fungsi pengujian TestCreateUser melakukan pengujian terhadap endpoint
yang bertanggung jawab untuk membuat pengguna baru. Fungsi ini memastikan bahwa operasi
membuat pengguna berjalan dengan sukses dan dapat mengambil entitas pengguna dari
database untuk memverifikasi hasilnya. Dengan mengisolasi interaksi dengan database,
pengujian dapat dilakukan tanpa mengubah data produksi dan memberikan kepastian bahwa
fungsionalitas yang diuji berjalan sesuai harapan.
Pertama, mari buat model untuk entitas yang akan kita simpan di dalam database. Misalnya,
kita akan membuat model User:
// models/user.go
package models
import (
"gorm.io/gorm"
)
Selanjutnya, kita akan menulis kode untuk fungsi CRUD dan unit tests untuk setiap fungsi
tersebut. Berikut adalah contoh kode Golang dan Echo untuk melakukan operasi CRUD pada
entitas User:
// handlers/user_handler.go
package handlers
import (
"net/http"
"github.com/labstack/echo/v4"
"gorm.io/gorm"
"yourapp/models"
)
user := models.User{
Username: c.FormValue("username"),
Email: c.FormValue("email"),
}
userID := c.Param("id")
userID := c.Param("id")
userID := c.Param("id")
return c.NoContent(http.StatusNoContent)
}
Setelah membuat fungsi CRUD, kita perlu menulis unit tests untuk memastikan bahwa
fungsi-fungsi tersebut beroperasi dengan benar. Berikut adalah contoh unit tests menggunakan
library testing dan testify/assert:
// handlers/user_handler_test.go
package handlers
import (
"net/http"
"net/http/httptest"
"strings"
"testing"
"github.com/labstack/echo/v4"
"github.com/stretchr/testify/assert"
"gorm.io/gorm"
"yourapp/models"
)
Pada setiap tes, kita membuat objek request HTTP, menyusun context Echo, dan memanggil
fungsi yang diuji. Dengan menggunakan library assert dari testify, kita dapat melakukan
asertasi untuk memastikan bahwa fungsi-fungsi tersebut mengembalikan hasil yang
diharapkan.
Penting untuk diingat bahwa unit tests ini hanya memberikan pandangan awal tentang
bagaimana memulai pengujian CRUD dengan GORM. kita mungkin perlu menyesuaikan unit
tests ini dengan kebutuhan dan struktur aplikasi kita yang spesifik.
Pertama, perlu memastikan bahwa struktur database testing dipisahkan dari lingkungan
produksi untuk mencegah pengaruh saling mempengaruhi antara pengujian dan data produksi.
Ini dapat dicapai dengan membuat konfigurasi khusus untuk lingkungan pengujian. Berikut
adalah contoh konfigurasi database testing menggunakan GORM dan Echo:
package main
import (
"fmt"
"log"
"os"
"github.com/jinzhu/gorm"
"github.com/labstack/echo"
"github.com/labstack/echo/middleware"
_ "github.com/mattn/go-sqlite3"
)
var db *gorm.DB
var err error
func main() {
// Konfigurasi Database
db, err = gorm.Open("sqlite3", "test.db")
if err != nil {
fmt.Println(err)
panic("Gagal terhubung ke database")
}
defer db.Close()
// Inisialisasi Echo
e := echo.New()
// Middleware untuk logging
e.Use(middleware.Logger())
// Route
e.GET("/users/:id", getUser)
// Jalankan server
e.Start(":8080")
}
Dalam contoh di atas, kita menggunakan database SQLite untuk keperluan pengujian. Selain itu,
terdapat fungsi main yang berfungsi untuk membuat koneksi database dan melakukan migrasi
otomatis struktur model. Kemudian, terdapat route untuk mengambil informasi pengguna
berdasarkan ID.
Untuk pengujian, kita dapat membuat file terpisah, misalnya test_main.go, yang
menyediakan konfigurasi database testing dan skenario pengujian. Contoh pengujian
menggunakan package github.com/stretchr/testify/assert dapat terlihat seperti ini:
package main
import (
"net/http"
"net/http/httptest"
"testing"
"github.com/labstack/echo"
"github.com/stretchr/testify/assert"
)
// Handler
if assert.NoError(t, getUser(c)) {
assert.Equal(t, http.StatusOK, rec.Code)
assert.Contains(t, rec.Body.String(), "Username")
assert.Contains(t, rec.Body.String(), "Email")
}
}
Penting untuk dicatat bahwa implementasi pengujian dapat bervariasi tergantung pada
kebutuhan aplikasi dan struktur database yang digunakan. Selain itu, prinsip-prinsip yang
diterapkan dalam contoh di atas dapat diadaptasi untuk berbagai ORM dan framework web
dalam lingkungan pengembangan Go.
import (
"testing"
"github.com/labstack/echo/v4"
"gorm.io/gorm"
"gorm.io/driver/sqlite"
)
Selanjutnya, kita akan membuat model sederhana untuk entitas dalam database, misalnya,
entitas User
type User struct {
gorm.Model
Username string
Email string
}
Sekarang, mari kita lihat contoh fungsi pengujian yang menggunakan transaksi database:
Pada contoh di atas, kita membuat fungsi createUser yang menggunakan transaksi untuk
memastikan bahwa pembuatan user dilakukan secara atomik. Fungsi pengujian
TestUserCreation kemudian memanggil fungsi ini dan melakukan verifikasi terhadap hasil
pembuatan user.
Penting untuk dicatat bahwa menggunakan transaksi dalam pengujian database dapat
membantu mencegah perubahan data yang tidak diinginkan dan memberikan kepastian bahwa
setiap pengujian dilakukan dalam konteks transaksi yang terisolasi.
Mocking
Duration: 90:00
Mocking umumnya melibatkan pembuatan objek palsu atau tiruan yang menggantikan objek
aktual dalam pengujian. Objek palsu ini, yang disebut "mocks" atau "mock objects," diprogram
untuk memberikan respons tertentu ketika dipanggil, seringkali meniru perilaku objek yang
sebenarnya. Dengan cara ini, pengembang dapat mengontrol kondisi uji dan mengamati
bagaimana sistem berinteraksi dengan objek palsu tersebut. Hal ini memungkinkan pengujian
yang lebih akurat dan mendalam terhadap unit atau modul tertentu tanpa memperkenalkan
kompleksitas dari komponen lain yang mungkin belum siap atau dapat mengakibatkan hasil
pengujian yang tidak konsisten.
Pentingnya mocking dalam pengujian perangkat lunak terletak pada kemampuannya untuk
meningkatkan isolasi, pengulangan, dan keandalan pengujian. Dengan menggantikan objek
nyata dengan objek palsu, pengembang dapat menciptakan skenario uji yang lebih terkendali
dan dapat diulang, memungkinkan deteksi dini kesalahan dan perubahan perilaku sistem. Oleh
karena itu, mocking menjadi komponen integral dalam praktik pengujian otomatis, membantu
memastikan bahwa perangkat lunak yang dikembangkan memenuhi standar kualitas dan dapat
berfungsi sebagaimana mestinya.
Manual Mocks adalah pendekatan paling dasar dalam mocking di mana pengembang secara
manual membuat objek palsu untuk menggantikan komponen sistem yang sebenarnya. Ini
melibatkan penulisan kode secara langsung untuk membuat objek tiruan yang mensimulasikan
perilaku dari komponen yang sebenarnya. Manual Mocks memberikan tingkat kontrol yang
tinggi kepada pengembang, namun dapat menjadi tugas yang membosankan dan memakan
waktu terutama dalam skala proyek yang besar. Contohnya dalam Golang dapat dilihat pada
contoh berikut:
Code Generation adalah metode mocking di mana kode mock secara otomatis dibuat oleh alat
atau skrip. Sebagai contoh, dengan menggunakan alat seperti Mockito dalam lingkungan Java,
pengembang dapat menentukan mock dengan menyatakan hanya antarmuka atau kelas yang
akan dimock, dan alat tersebut akan menghasilkan kode mock secara otomatis. Code
Generation mengurangi beban kerja manual dan mempercepat proses mocking, tetapi dapat
menghasilkan kode yang sulit dibaca dan dipahami. Contoh Golang menggunakan alat seperti
mockery:
# Install mockery
go get github.com/vektra/mockery/v2/.../
Dynamic Mocks adalah pendekatan di mana mock diciptakan secara dinamis selama runtime.
Dalam kasus ini, framework mocking secara otomatis menciptakan objek palsu saat diperlukan
selama eksekusi pengujian. Dynamic Mocks memberikan fleksibilitas tinggi karena
memungkinkan pengembang untuk membuat mock dengan mudah tanpa perlu menulis kode
tambahan secara manual. Namun, kelemahannya termasuk kurangnya dukungan penuh
terhadap kontrol manual dan kecenderungan untuk menghasilkan mock yang kurang stabil jika
ada perubahan pada kode yang sebenarnya. Contoh Golang menggunakan testify/mock
Pada contoh di atas, mockgen digunakan untuk menghasilkan mock otomatis dari antarmuka
Database. Dengan menggunakan pendekatan ini, mocking dapat dilakukan secara dinamis saat
runtime.
Setiap pendekatan memiliki kelebihan dan kekurangan. Manual Mocks memberikan kontrol
penuh kepada pengembang, tetapi bisa memakan waktu dan kurang praktis untuk kasus yang
kompleks. Code Generation dapat menghemat waktu, tetapi memerlukan alat tambahan dan
memerlukan pemeliharaan. Dynamic Mocks memberikan fleksibilitas, tetapi dapat
mempengaruhi kinerja aplikasi dan memerlukan dependensi pustaka mocking. Pemilihan
metode tergantung pada kebutuhan spesifik dan preferensi pengembang.
Hands-On: Introduction to Mockgen
Mocking merupakan strategi yang umum digunakan dalam pengujian perangkat lunak untuk
mengisolasi dan memeriksa komponen-komponen tertentu tanpa bergantung pada
implementasi penuh dari komponen lainnya. Mockgen adalah alat yang sangat berguna dalam
Golang yang memungkinkan pengembang untuk secara otomatis menghasilkan implementasi
mocks dari antarmuka atau objek tertentu.
Contoh di bawah ini menggunakan framework web Golang yang populer, Echo, untuk
mendemonstrasikan penggunaan Mockgen. Pertama, kita mendefinisikan sebuah antarmuka
(interface) yang akan di-mock:
// userService.go
package main
Kemudian, kita dapat menggunakan Mockgen untuk membuat mock dari antarmuka ini. Berikut
adalah contoh penggunaan Mockgen untuk membuat mock UserService
// user_test.go
package main
import (
"errors"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/mock"
"your/package/path/mocks"
)
mockUserService.AssertExpectations(t)
}
mockUserService.AssertExpectations(t)
}
Dalam contoh ini, kita membuat dua pengujian untuk metode GetUserByID dan CreateUser
dari UserService. Kita menggunakan mock yang telah dibuat untuk memeriksa bagaimana
logika bisnis kita berinteraksi dengan UserService, dan memastikan bahwa metode yang
diharapkan dipanggil dengan argumen yang benar. Dengan cara ini, kita dapat mengisolasi dan
menguji komponen kita tanpa harus tergantung pada implementasi sebenarnya dari
UserService.
Melalui pengenalan praktis ini, pengembang dapat memahami cara menggunakan Mockgen
dalam skenario nyata dengan mudah, meningkatkan kualitas dan keandalan kode yang
dikembangkan.
Membangun unit testing dengan mocking adalah suatu pendekatan yang penting dalam
pengembangan perangkat lunak untuk memastikan bahwa setiap komponen atau unit dari
sebuah sistem dapat berfungsi sesuai yang diharapkan secara terisolasi. Unit testing adalah
praktik pengujian perangkat lunak di tingkat terkecil, yaitu pada level unit atau komponen
individual. Untuk mengisolasi unit tersebut dari dependensi eksternal dan memastikan bahwa
pengujian berfokus pada fungsionalitas yang diinginkan, mocking digunakan.
Mocking melibatkan penggunaan objek palsu atau tiruan (mock objects) yang mensimulasikan
perilaku dari komponen yang sebenarnya. Ini memungkinkan pengembang untuk
mengendalikan input dan output dari unit yang diuji, menciptakan lingkungan pengujian yang
terkontrol. Dengan cara ini, unit testing dapat dilakukan tanpa ketergantungan pada komponen
eksternal yang mungkin belum siap atau dapat menyebabkan kesalahan yang tidak terduga.
Proses membangun unit testing dengan mocking dimulai dengan identifikasi unit atau
komponen yang akan diuji. Setelah itu, objek mock dibuat untuk mensimulasikan dependensi
eksternal dan menggantikan objek sebenarnya selama pengujian. Selanjutnya, skenario
pengujian dan asumsi pengujian didefinisikan, dan unit testing dilaksanakan dengan
menggunakan framework pengujian seperti JUnit, NUnit, atau pytest.
Keuntungan dari menggunakan mocking dalam unit testing mencakup isolasi unit yang lebih
baik, eksekusi pengujian yang lebih cepat, dan kemampuan untuk mengidentifikasi dan
memperbaiki kesalahan dengan lebih efisien. Selain itu, mocking memungkinkan pengembang
untuk menguji unit secara terus-menerus tanpa harus menunggu ketersediaan semua
dependensi eksternal. Dengan demikian, membangun unit testing dengan mocking menjadi
praktik yang krusial dalam mencapai pengembangan perangkat lunak yang handal, mudah
dipelihara, dan dapat berkembang dengan baik seiring waktu.
/ File: user_service.go
package services
import "fmt"
// File: user_repository.go
package services
// File: user_service_test.go
package services_test
import (
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/mock"
"your_project/services"
)
// Assert
assert.NoError(t, err)
assert.Equal(t, "User: John Doe", result)
Mocking melibatkan pembuatan implementasi pengganti untuk fungsi atau metode yang
mungkin memiliki efek samping, seperti melakukan panggilan API eksternal, mengakses basis
data, atau melakukan operasi I/O berkas. Dengan menggantikan implementasi nyata ini dengan
versi mock, pengembang dapat mensimulasikan skenario berbeda dan memastikan bahwa
kode yang diuji bereaksi seperti yang diharapkan tanpa memengaruhi sumber daya eksternal.
// main.go
package main
import (
"net/http"
"github.com/labstack/echo/v4"
)
func main() {
e := echo.New()
e.GET("/user/:id", getUserHandler)
e.Start(":8080")
}
func getUserHandler(c echo.Context) error {
// Anggap fungsi ini mengambil data pengguna dari basis data
userID := c.Param("id")
userData := fetchUserDataFromDatabase(userID)
return c.JSON(http.StatusOK, userData)
}
Selanjutnya, mari buat file pengujian untuk melakukan mocking interaksi basis data dalam
fungsi getUserHandler.
// main_test.go
package main
import (
"net/http"
"net/http/httptest"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/mock"
)
// Periksa tanggapan
assert.Equal(t, http.StatusOK, rec.Code)
assert.Equal(t, "Data pengguna yang dimock untuk ID "+userID,
rec.Body.String())
Dalam contoh ini, kita telah membuat tipe MockDB yang menyematkan
github.com/stretchr/testify/mock.Mock. Kemudian, kita mendefinisikan metode
fetchUserDataFromDatabase yang mewakili interaksi basis data dalam kode asli. Dalam
fungsi pengujian TestGetUserHandler, kita membuat instansi basis data mock, menetapkan
ekspektasi untuk metode mock, dan menggunakannya dalam pengujian getUserHandler.
Akhirnya, kita memastikan bahwa penangan berperilaku seperti yang diharapkan dan bahwa
metode mock dipanggil dengan parameter yang benar.
Pendekatan ini memungkinkan kita untuk menguji fungsi getUserHandler secara terisolasi,
memastikan bahwa fungsi tersebut berfungsi seperti yang diinginkan tanpa melibatkan basis
data sesungguhnya. Mocking adalah teknik yang kuat untuk pengujian unit yang efektif,
terutama ketika berurusan dengan fungsi atau metode yang memiliki efek samping.
Contoh berikut menggunakan bahasa pemrograman Golang, framework web Echo, dan ORM
(Object-Relational Mapping) Gorm. Sebelumnya, pastikan kita telah menginstal
package-package yang diperlukan dengan menjalankan go get untuk Echo dan Gorm:
go get -u github.com/labstack/echo
go get -u gorm.io/gorm
go get -u gorm.io/driver/sqlite
import "gorm.io/gorm"
Fungsi-Fungsi Database
package database
import (
"gorm.io/driver/sqlite"
"gorm.io/gorm"
)
var DB *gorm.DB
func InitDB() {
db, err := gorm.Open(sqlite.Open("test.db"), &gorm.Config{})
if err != nil {
panic("Failed to connect to database")
}
DB = db
DB.AutoMigrate(&models.User{})
}
func GetUserByEmail(email string) (*models.User, error) {
var user models.User
result := DB.Where("email = ?", email).First(&user)
return &user, result.Error
}
import (
"net/http"
"github.com/labstack/echo/v4"
"gorm-mocking-example/database"
"gorm-mocking-example/models"
)
func main() {
database.InitDB()
e := echo.New()
e.GET("/user/:email", GetUserHandler)
e.POST("/user", CreateUserHandler)
e.Start(":8080")
}
if err != nil {
return c.JSON(http.StatusInternalServerError,
map[string]string{"error": "Failed to fetch user"})
}
err := database.CreateUser(&user)
if err != nil {
return c.JSON(http.StatusInternalServerError,
map[string]string{"error": "Failed to create user"})
}
import (
"net/http"
"net/http/httptest"
"strings"
"testing"
"github.com/labstack/echo/v4"
"github.com/stretchr/testify/assert"
"gorm-mocking-example/database"
"gorm-mocking-example/models"
)
// Mock Database
database.DB = &MockDB{}
// Assertions
if assert.NoError(t, GetUserHandler(c)) {
assert.Equal(t, http.StatusOK, rec.Code)
assert.Contains(t, rec.Body.String(), "johndoe@example.com")
}
}
// Mock Database
database.DB = &MockDB{}
// Assertions
if assert.NoError(t, CreateUserHandler(c)) {
assert.Equal(t, http.StatusCreated, rec.Code)
assert.Contains(t, rec.Body.String(), "johndoe@example.com")
}
}
Mocking Best Practices
Mocking adalah teknik yang umum digunakan dalam pengujian perangkat lunak untuk
mensimulasikan perilaku objek atau fungsi tertentu yang tidak dapat diuji secara langsung.
Dalam pengembangan perangkat lunak, modul mocking menjadi kritis karena membantu
pengembang untuk mengisolasi unit-unit kecil dari kode dan memastikan bahwa fungsi atau
objek yang diuji berperilaku sesuai dengan harapan.
Salah satu praktik terbaik dalam menggunakan modul mocking adalah fokus pada kejelasan
dan kohesi pengujian. Mocking seharusnya tidak hanya digunakan untuk melintasi jalan yang
sulit diuji, tetapi juga untuk memastikan bahwa unit pengujian tetap terorganisir dan mudah
dimengerti. Oleh karena itu, penting untuk menghindari over-mocking, di mana pengembang
menggunakan terlalu banyak mock object yang dapat menyebabkan kompleksitas yang sulit
dipelihara.
Selain itu, memahami konteks dan ruang lingkup pengujian sangat penting. Sebaiknya, mock
hanya diterapkan pada dependensi luar yang memang perlu diisolasi, sementara interaksi
dengan komponen internal seharusnya tetap menggunakan kode asli. Ini membantu
memastikan bahwa pengujian lebih fokus dan relevan dengan perilaku sebenarnya dari modul
yang diuji.
Penting juga untuk memperhatikan performa dalam penggunaan modul mocking. Seiring
dengan meningkatnya kompleksitas proyek, penggunaan mock object yang berlebihan atau
tidak efisien dapat menyebabkan pengujian yang lambat. Oleh karena itu, pemilihan modul
mocking dan strategi pengujian harus diterapkan dengan bijak untuk memastikan bahwa proses
pengujian tetap efisien dan efektif.
Terakhir, dokumentasi yang baik dari penggunaan mock object juga merupakan bagian integral
dari praktik terbaik. Dokumentasi yang jelas dan lengkap membantu pengembang lain untuk
memahami niat dan tujuan penggunaan mock object dalam pengujian, mempercepat proses
pengembangan dan pemeliharaan kode di masa mendatang.
Dengan mematuhi praktik terbaik ini, penggunaan modul mocking dapat meningkatkan kualitas
pengujian unit, mempercepat pengembangan, dan membuat kode lebih mudah dimengerti dan
dipelihara.
Di sisi lain, benchmarking adalah proses membandingkan kinerja suatu sistem atau komponen
dengan standar yang telah ditetapkan atau dengan produk serupa dari pesaing. Dalam konteks
modul test coverage dan benchmarking, ini berarti mengevaluasi seberapa baik modul
pengujian melibatkan atau mencakup berbagai aspek fungsionalitas yang diinginkan dan sejauh
mana modul tersebut memenuhi standar kualitas yang ditetapkan. Benchmarking dapat
membantu dalam menilai efektivitas dan efisiensi modul tersebut, memberikan gambaran yang
jelas tentang sejauh mana modul tersebut memenuhi ekspektasi dan dapat memberikan kinerja
yang optimal.
Untuk menganalisis test coverage dalam Go, kita dapat menggunakan perintah go test
dengan opsi -cover. Misalnya, jalankan perintah go test -cover pada direktori proyek
untuk melihat laporan test coverage. Hasilnya akan menunjukkan persentase kode yang
tercakup oleh pengujian unit, serta baris kode yang belum tercakup. Ini memberikan wawasan
yang berharga untuk memastikan bahwa setiap bagian kode telah diuji secara memadai.
Selain itu, untuk melakukan benchmarking dalam Go, kita dapat menambahkan fungsi
benchmark ke file pengujian kita dengan menggunakan kata kunci Benchmark. Misalnya,
sebuah fungsi benchmark dapat terlihat seperti ini:
Selanjutnya, untuk menjalankan benchmark, gunakan perintah go test -bench.. Ini akan
menjalankan semua benchmark yang ditemukan dalam proyek kita. Hasilnya akan memberikan
informasi kinerja tentang fungsi-fungsi yang di-benchmark, termasuk waktu eksekusi dan
alokasi memori.
Dengan kombinasi analisis test coverage dan benchmarking, pengembang dapat memastikan
bahwa kode mereka teruji dengan baik dan memiliki kinerja yang memadai. Hasil test coverage
membantu mengidentifikasi area-area yang belum diuji, sementara benchmarking memberikan
informasi tentang kinerja untuk mengoptimalkan kode. Kedua aspek ini bersama-sama
membentuk strategi yang kokoh untuk pengujian dan perbaikan kualitas kode dalam
pengembangan perangkat lunak dengan Go.
Penting juga untuk melakukan benchmarking terhadap modul yang diuji guna membandingkan
hasil pengujian dengan standar kualitas yang telah ditetapkan. Hal ini dapat melibatkan
perbandingan cakupan kode, jumlah dan jenis kasus uji yang telah dijalankan, serta tingkat
keberhasilan pengujian. Dengan demikian, benchmarking dapat memberikan pemahaman yang
lebih mendalam tentang sejauh mana modul telah diuji dan apakah kualitas pengujian sudah
sesuai dengan harapan.
Pertama, pastikan untuk menuliskan unit test yang komprehensif. Gunakan package bawaan
testing dalam Go untuk membuat tes unit yang mencakup semua fungsi dan metode dalam
modul tersebut. Gunakan asertasi untuk memastikan bahwa output dari fungsi sesuai dengan
yang diharapkan.
package yourmodule
import (
"testing"
)
Selanjutnya, manfaatkan coverage tooling yang disediakan oleh Go. Gunakan perintah go
test -cover untuk melihat laporan test coverage. Pastikan bahwa setiap fungsi dan
statement dalam kode sumber tercakup oleh tes.
go test -cover
Penting untuk memahami bahwa high test coverage tidak hanya mengukur jumlah kode yang
tercakup, tetapi juga seberapa baik kode tersebut diuji terhadap skenario yang berbeda. Oleh
karena itu, pastikan untuk membuat tes untuk skenario khusus dan edge case yang mungkin
muncul dalam penggunaan modul tersebut.
Selain itu, gunakan benchmarking untuk mengidentifikasi dan memperbaiki kinerja kode. Go
menyediakan package testing yang dapat digunakan untuk membuat benchmark. Contoh:
package yourmodule
import (
"testing"
)
Dengan menggunakan alat profil, pengembang dapat mengukur waktu eksekusi, konsumsi
memori, dan frekuensi panggilan fungsi, sehingga mendapatkan wawasan mendalam tentang
perilaku aplikasi. Test coverage, di sisi lain, adalah metrik yang mengukur sejauh mana kode
program tercakup oleh rangkaian tes. Dengan meningkatkan test coverage, pengembang dapat
memastikan bahwa setiap bagian kode diuji secara memadai, meningkatkan keandalan aplikasi.
Test Coverage
Test coverage merujuk pada sejauh mana kode sumber diuji oleh unit test. Golang menyediakan
alat bawaan seperti go test untuk mengukur cakupan pengujian. Dengan menjalankan tes
dan menghasilkan laporan cakupan, pengembang dapat melihat bagian mana dari kode yang
telah diuji dan mana yang belum. Untuk mengoptimalkan cakupan, pengembang dapat menulis
lebih banyak tes unit untuk memastikan semua cabang dan kondisi dijalankan.
import "fmt"
func main() {
result := Add(3, 5)
fmt.Println(result)
}
Benchmarking
Benchmarking membantu mengukur kinerja fungsi atau bagian tertentu dari kode. Golang
menyediakan pustaka testing dan perintah go test dengan opsi -bench untuk melakukan
benchmarking. Pengembang dapat menetapkan fungsi benchmark untuk mengukur waktu
eksekusi dan alokasi memori. Hasil benchmark membantu mengidentifikasi area yang
memerlukan optimasi.
import (
"testing"
)
Optimization
Optimasi, pada dasarnya, melibatkan serangkaian tindakan untuk meningkatkan efisiensi dan
kinerja suatu sistem. Dalam konteks ini, optimasi dapat berfokus pada perbaikan kode yang
diidentifikasi melalui profil, sehingga meningkatkan waktu respon aplikasi dan mengurangi
konsumsi sumber daya. Sementara itu, benchmarking adalah proses perbandingan kinerja
aplikasi atau sistem terhadap standar atau aplikasi sejenis, yang memungkinkan pengembang
untuk mengevaluasi sejauh mana implementasi mereka bersaing dengan solusi lain.