Anda di halaman 1dari 73

Modul 12 -

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

Last Updated: 2023-09-13

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.

Materi Teks Interaktif


Penyajian materi dalam bentuk teks yang mudah dipahami dan dilengkapi dengan elemen
interaktif seperti pecahan kode, penjelasan grafis, dan banyak lagi. Ini membantu kamu agar
dapat mudah diaplikasikan dalam proses pembelajaran kamu.

Kuis Interaktif dan Latihan


Menyediakan kuis interaktif atau latihan mandiri yang membantu kamu menguji pemahaman
mereka dan memperkuat konsep yang telah dipelajari. Materi yang akan digunakan sesuai yang
kamu pelajari sebelumnya. Walaupun kamu belum berhasil menjawab pertanyaan yang
diberikan, kamu dapat mengulang sehingga mendapatkan hasil yang terbaik

Proyek dan Tugas Praktis


Ini adalah step akhir yang perlu kamu selesaikan agar bisa menyelesaikan kursus/learning path
yang telah diberikan. Disini akan menyajikan tugas praktis atau proyek yang memungkinkan
kamu mengimplementasikan konsep yang telah kamu pelajari yang sesuai dengan
permasalahan yang diberikan sebagai panduan kamu dalam mengerjakan proyek tersebut.

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

Bentuk Implementasi Testing


Pengenalan Testing untuk Test Driven Development (TDD) pada Golang melibatkan berbagai
bentuk implementasi testing untuk memastikan kualitas dan keberlanjutan kode. Proses TDD
dimulai dengan menulis tes sebelum mengimplementasikan fungsionalitas, dan Golang
memiliki alat bawaan yang mendukung pendekatan ini.

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.

Mengenal package untuk implementasi testing


Dalam ekosistem bahasa pemrograman Go (Golang), terdapat beberapa package yang sangat
berguna untuk implementasi pengujian (testing) dalam paradigma pengembangan perangkat
lunak yang dikenal sebagai Test Driven Development (TDD). Package utama yang digunakan
untuk tujuan ini adalah "testing" dan "testing/quick".
Pertama-tama, package "testing" menyediakan fasilitas pengujian bawaan di Golang. Dengan
menggunakan package ini, pengembang dapat membuat unit test, benchmark, dan contoh
penggunaan kode secara lebih terstruktur. Fungsi utama dalam package "testing" adalah
"testing.T" yang digunakan untuk menuliskan laporan pengujian dan mengelola keberhasilan
atau kegagalan tes. Dengan adanya fungsi ini, pengembang dapat membuat tes yang eksplisit
dan mudah dipahami.

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.

Selain itu, untuk pengujian performa, package "testing" menyediakan package


"testing/benchmark." Dengan menggunakan package ini, pengembang dapat membuat
benchmark untuk mengukur kinerja kode mereka. Benchmark dapat memberikan wawasan
yang berharga tentang sejauh mana implementasi kode dapat menangani beban kerja tertentu
dan membantu dalam mengidentifikasi potensi area perbaikan untuk meningkatkan efisiensi.

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.

Mempelajari Prinsip Test Driven Development


Test Driven Development (TDD) adalah pendekatan pengembangan perangkat lunak yang
menempatkan pengujian di pusat proses pengembangan. Prinsip utama TDD melibatkan siklus
pengembangan singkat yang dimulai dengan menulis tes sebelum kode yang sesuai, kemudian
mengimplementasikan kode untuk memenuhi persyaratan tes, dan akhirnya melakukan refaktor
untuk meningkatkan kualitas kode tanpa mengubah fungsionalitas. Penerapan TDD di Golang,
bahasa pemrograman yang semakin populer untuk pengembangan aplikasi berkinerja tinggi,
membutuhkan pemahaman mendalam tentang prinsip-prinsip ini.

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.

Kemudian, dalam langkah implementasi, pengembang menghasilkan kode yang memenuhi


persyaratan tes yang telah ditentukan. Penting untuk mencoba meminimalkan jumlah kode yang
ditulis untuk memenuhi persyaratan tes, sehingga mendorong fokus pada kebutuhan
sebenarnya.

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.

Unit Testing dengan Go


Duration: 90:00

Pengenalan pada testing package dalam Go


Dalam konteks Golang, pengujian dilakukan melalui package testing. Package ini
menyediakan fasilitas untuk menulis dan menjalankan unit test, integrasi test, dan fungsional
test. Untuk menerapkan TDD di Golang, langkah pertama adalah membuat file test terpisah
untuk setiap file sumber yang ingin diuji. Sebagai contoh, mari kita anggap kita memiliki file
sumber calculator.go yang berisi fungsi sederhana untuk penambahan:

// calculator.go

package calculator

func Add(a, b int) int {


return a + b
}

Selanjutnya, kita akan membuat file calculator_test.go untuk menguji fungsi Add tersebut:

// calculator_test.go

package calculator

import "testing"

func TestAdd(t *testing.T) {


result := Add(2, 3)
expected := 5

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.

Memahami bagaimana menulis test functions dalam Go


Dalam bahasa pemrograman Go (Golang), menulis test functions menjadi bagian integral dari
proses TDD. Test functions dalam Go dibuat menggunakan package bawaan "testing," dan
langkah-langkah berikut membantu memahami cara menulis test functions secara
komprehensif.

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.

Kemudian, pengembang dapat menggunakan fungsi-fungsi pengujian dengan format khusus,


yaitu fungsi yang diawali dengan "Test" dan menerima parameter tipe data pointer "testing.T."
Contoh fungsi pengujian dapat terlihat seperti berikut:

func TestTambah(t *testing.T) {


hasil := Tambah(2, 3)
if hasil != 5 {
t.Errorf("Harapnya 2 + 3 = 5, tetapi hasilnya %d", hasil)
}
}

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.

Selanjutnya, pengembang dapat menggunakan fungsi t.Run untuk mengelompokkan


serangkaian pengujian terkait. Ini membantu memisahkan logika pengujian dan memberikan
output yang lebih terstruktur. Contoh penggunaan t.Run dapat terlihat seperti berikut:

func TestMain(t *testing.T) {


t.Run("TestTambah", TestTambah)
t.Run("TestKurang", TestKurang)
}

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.

Eksplorasi testing package functions dengan testing.T


Dalam konteks Golang, testing adalah package bawaan yang menyediakan fasilitas untuk
menulis tes unit. Fungsi-fungsi yang diuji biasanya diberikan parameter objek *testing.T,
yang digunakan untuk melaporkan kegagalan atau kesalahan yang terjadi selama pengujian.
Saat melakukan eksplorasi testing package functions, pengembang Golang dapat
mengidentifikasi berbagai fitur dan metode yang disediakan oleh testing.T untuk menguji
fungsionalitas yang berbeda.

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.

Contoh implementasi Table-Driven Tests dalam Go mungkin terlihat seperti ini:

package mypackage

import (
"testing"
)

func Add(a, b int) int {


return a + b
}

func TestAdd(t *testing.T) {


testCases := []struct {
a, b, expected int
}{
{1, 2, 3},
{-1, 1, 0},
{0, 0, 0},
{5, -5, 0},
}

for _, tc := range testCases {


result := Add(tc.a, tc.b)
if result != tc.expected {
t.Errorf("Add(%d, %d) = %d, expected %d", tc.a,
tc.b, result, tc.expected)
}
}
}

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.

Keuntungan dari pendekatan Table-Driven Tests adalah memudahkan penambahan atau


perubahan kasus uji, serta meningkatkan keterbacaan dan pemeliharaan kode pengujian.
Dengan menggunakan struktur data yang terorganisir, pengembang dapat dengan jelas melihat
setiap kasus uji dan hasil yang diharapkan, memudahkan identifikasi dan perbaikan bug dalam
kode.

Parameterized tests pada Golang


Parameterized tests di Golang merujuk pada kemampuan untuk menjalankan serangkaian tes
dengan input yang bervariasi tanpa perlu menulis fungsi tes terpisah untuk setiap kasus uji.
Fitur ini memungkinkan pengembang untuk membuat tes yang lebih dinamis dan mudah
dikelola, terutama dalam konteks Test Driven Development (TDD). Dengan menggunakan
parameterized tests, kita dapat menyederhanakan struktur tes dan meningkatkan keterbacaan
kode, sekaligus memudahkan pengelolaan dan pemeliharaan.

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"
)

// Fungsi yang akan diuji


func Add(a, b int) int {
return a + b
}

// Struktur untuk menyimpan data input dan hasil yang diharapkan


type testData struct {
a, b, expected int
}

// Fungsi parameterized test untuk penambahan


func TestAdd(t *testing.T) {
// Daftar kasus uji
testCases := []testData{
{1, 2, 3},
{0, 0, 0},
{-1, 1, 0},
{10, -5, 5},
}

// Iterasi melalui setiap kasus uji


for _, tc := range testCases {
// Menjalankan tes untuk setiap kasus uji
t.Run(fmt.Sprintf("Add(%d, %d)", tc.a, tc.b), func(t
*testing.T) {
result := Add(tc.a, tc.b)
// Membandingkan hasil dengan nilai yang diharapkan
assert.Equal(t, tc.expected, result, "Hasil tidak
sesuai dengan yang diharapkan")
})
}
}

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

Perbedaan antara Unit Tests dan Integration Tests


Pada pengembangan perangkat lunak dengan Go dan menerapkan Test Driven Development
(TDD), Integration Testing menjadi langkah krusial untuk memastikan bahwa berbagai
komponen perangkat lunak berinteraksi dengan benar. Unit Tests dan Integration Tests adalah
dua jenis pengujian yang berbeda yang memiliki peran masing-masing dalam proses ini.

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:

// Example unit test file: math_test.go

package main

import (
"testing"
)

func TestAdd(t *testing.T) {


result := Add(2, 3)
expected := 5
if result != expected {
t.Errorf("Expected %d, but got %d", expected, result)
}
}

// Example implementation file: math.go

package main

func Add(a, b int) int {


return a + b
}

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:

// Example integration test file: integration_test.go

package main
import (
"testing"
)

func TestMathOperationsIntegration(t *testing.T) {


result := MathOperations(2, 3)
expected := 6 // Angka ini adalah hasil penjumlahan dari
TestAdd dan hasil perkalian dari TestMultiply
if result != expected {
t.Errorf("Expected %d, but got %d", expected, result)
}
}

Pada contoh di atas, TestMathOperationsIntegration menguji integrasi antara Add dan


fungsi lain, seperti Multiply, untuk memastikan bahwa seluruh operasi matematika berjalan
dengan benar.

Implementasi Contoh Fungsi


Berikut adalah implementasi sederhana fungsi Multiply yang digunakan dalam contoh di atas:

// Example implementation file: math.go

package main

func Multiply(a, b int) int {


return a * b
}

// Example updated integration test file: integration_test.go

func TestMathOperationsIntegration(t *testing.T) {


result := MathOperations(2, 3)
expected := 6 // Angka ini adalah hasil penjumlahan dari
TestAdd dan hasil perkalian dari TestMultiply
if result != expected {
t.Errorf("Expected %d, but got %d", expected, result)
}
}
Dalam contoh ini, Integration Test TestMathOperationsIntegration memastikan bahwa
hasil penjumlahan dan perkalian dari fungsi Add dan Multiply berjalan dengan benar ketika
digabungkan, menunjukkan bahwa integrasi antara keduanya berfungsi seperti yang diharapkan.

Dengan demikian, Unit Tests dan Integration Tests saling melengkapi dalam TDD, memastikan
setiap komponen berfungsi secara terpisah dan juga bersama-sama saat diintegrasikan.

Membuat Test Cases untuk Multiple Components Interactions


Integration testing dalam pengembangan perangkat lunak sangat penting untuk memastikan
bahwa berbagai komponen sistem dapat berinteraksi dengan benar dan menghasilkan output
yang diharapkan. Dalam konteks pengembangan dengan bahasa pemrograman Go (Golang),
pendekatan yang umum digunakan adalah Test Driven Development (TDD), di mana test cases
dibuat sebelum implementasi sebenarnya. Untuk membuat test cases yang komprehensif untuk
multiple components interactions, kita dapat mengikuti langkah-langkah berikut dengan contoh
implementasi kode.

Pertama, identifikasi komponen-komponen yang akan diintegrasikan dan tentukan skenario


interaksinya. Misalnya, kita memiliki dua komponen: ComponentA dan ComponentB, dan kita
ingin menguji interaksi antara keduanya.

Membuat Test Case Skeleton


Buat file integration_test.go untuk menyimpan test cases. Gunakan package testing
bawaan Go.
package integration_test

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()

// Simulasikan interaksi antara komponen


result := componentA.InteractWithB(componentB)

// Periksa apakah hasilnya sesuai dengan yang diharapkan


expected := "ExpectedResult"
if result != expected {
t.Errorf("Got %s, expected %s", result, expected)
}
}

Implementasi Komponen
Implementasikan komponen-komponen dan fungsinya. Misalnya:
type ComponentA struct{}

func NewComponentA() *ComponentA {


return &ComponentA{}
}

func (a *ComponentA) InteractWithB(b *ComponentB) string {


// Logika interaksi antara ComponentA dan ComponentB
return "ExpectedResult"
}

type ComponentB struct{}

func NewComponentB() *ComponentB {


return &ComponentB{}
}
Naming Conventions
Naming conventions dalam integration testing dengan Go untuk Test Driven Development (TDD)
adalah suatu aturan penamaan yang digunakan untuk memberikan struktur dan keterbacaan
pada unit-unit pengujian yang berfokus pada integrasi komponen dalam sebuah aplikasi yang
dikembangkan dengan bahasa pemrograman Go. Penamaan yang konsisten dan deskriptif
sangat penting dalam TDD untuk memudahkan pemahaman fungsionalitas dan tujuan setiap
tes.

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"
)

// TestUserIntegration adalah suatu tes integrasi untuk


memastikan
// bahwa komponen user dan auth dapat berinteraksi dengan baik.
func TestUserIntegration(t *testing.T) {
// Inisialisasi objek atau komponen yang diperlukan untuk
pengujian.
userManager := user.NewUserManager()
authService := auth.NewAuthService()

// Langkah-langkah pengujian integrasi, contoh:


// 1. Membuat pengguna baru menggunakan komponen user.
// 2. Memverifikasi keberhasilan pembuatan pengguna.
// 3. Melakukan otentikasi pengguna menggunakan komponen
auth.
// 4. Memastikan otentikasi berhasil.

// 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)
}

if createdUser.Name != "John Doe" {


t.Errorf("Nama pengguna tidak sesuai. Got: %s,
Expected: John Doe", createdUser.Name)
}

// 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)
}
}

Pada contoh di atas, TestUserIntegration mencakup langkah-langkah pengujian integrasi


yang mencakup pembuatan pengguna, verifikasi, otentikasi, dan memastikan bahwa hasil
otentikasi sesuai dengan yang diharapkan. Nama fungsi tersebut mencerminkan tujuan dan
fungsionalitas pengujian integrasi secara jelas sesuai dengan konvensi penamaan yang
digunakan dalam TDD dengan Go.

Mengorganisasi Test Files dan Packages


Mengorganisir test files dan packages dalam integration testing dengan Go untuk Test Driven
Development (TDD) memainkan peran kunci dalam memastikan kualitas dan keberlanjutan
kode. Dalam Golang, struktur direktori dan file sangat penting untuk memudahkan pembacaan
dan pemeliharaan test code. Pertama-tama, kita dapat memulai dengan membuat struktur
direktori dasar seperti ini:

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"
)

func TestIntegrationFunctionality(t *testing.T) {


// Setup test environment if needed

result := mypackage.MyModuleFunction() // Replace with the


actual function you want to test

// Assertions based on expected results


if result != expectedValue {
t.Errorf("Integration test failed. Expected: %v, Got:
%v", expectedValue, result)
}

// Clean up test environment if needed


}

Dalam contoh di atas, TestIntegrationFunctionality adalah fungsi pengujian integrasi


yang mencakup penggunaan modul atau fungsi dari package mypackage. Dalam blok
pengujian, kita dapat menyiapkan lingkungan pengujian, memanggil fungsi yang ingin diuji, dan
memverifikasi hasilnya menggunakan pernyataan asertif. Pastikan untuk menyertakan import
yang sesuai untuk package dan file yang akan diuji.

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.

Handling Test Dependencies


Dalam pengembangan perangkat lunak, uji integrasi memainkan peran penting untuk
memastikan bahwa berbagai komponen sistem dapat berinteraksi secara benar dan efektif.
Dalam pengembangan berbasis TDD menggunakan bahasa pemrograman Go, manajemen
dependensi uji integrasi menjadi kunci untuk memastikan keandalan dan konsistensi pengujian.

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"

// Fungsi ini akan digunakan untuk membuat instance server HTTP

yang disimulasikan.

func setupTestServer() *httptest.Server {

return httptest.NewServer(http.HandlerFunc(func(w

http.ResponseWriter, r *http.Request) {

// Implementasi logika server simulasi


w.WriteHeader(http.StatusOK)

w.Write([]byte("Hello, Test!"))

}))

// Fungsi ini adalah contoh implementasi fungsi bisnis yang akan

diuji.

func fetchDataFromServer(url string) (string, error) {

resp, err := http.Get(url)

if err != nil {

return "", err

defer resp.Body.Close()

// Logika pemrosesan respons dari server

// (di sini bisa menjadi parsing JSON atau operasi

lainnya).

return "Data from server", nil

// Pengujian integrasi yang menggantungkan dependensi pada

server simulasi.

func TestFetchDataFromServerIntegration(t *testing.T) {

// Setup server simulasi

server := setupTestServer()

defer server.Close()
// URL server simulasi

serverURL := server.URL

// Panggil fungsi bisnis yang akan diuji

data, err := fetchDataFromServer(serverURL)

// Periksa hasil pengujian

assert.NoError(t, err)

assert.Equal(t, "Data from server", data)

Penjelasan:

● Fungsi setupTestServer digunakan untuk membuat server HTTP yang disimulasikan


menggunakan httptest package. Server ini akan digunakan sebagai dependensi uji
integrasi.
● Fungsi fetchDataFromServer adalah contoh implementasi fungsi bisnis yang
berinteraksi dengan server HTTP. Fungsi ini menerima URL server sebagai parameter
dan mengembalikan data dari server.
● Fungsi pengujian TestFetchDataFromServerIntegration menunjukkan cara
menggantungkan dependensi uji integrasi pada server simulasi yang dibuat sebelumnya.
Penggunaan defer untuk menutup server setelah pengujian selesai adalah praktik yang
baik agar sumber daya dibersihkan.
● Penggunaan package pihak ketiga, seperti
github.com/stretchr/testify/assert, membantu dalam membuat pernyataan
pengujian yang jelas dan mudah dibaca.

Testing pada HTTP Handler


Duration: 90:00

TDD Workflow dengan Echo


Test Driven Development (TDD) adalah pendekatan pengembangan perangkat lunak yang
mendasarkan proses pengembangan pada siklus pengujian. TDD membantu memastikan
bahwa setiap fitur atau fungsi yang ditambahkan ke dalam kode telah diuji dengan baik,
sehingga meningkatkan kualitas dan keandalan perangkat lunak. Dalam konteks
pengembangan web dengan Golang menggunakan framework Echo, TDD dapat diterapkan pada
pengujian HTTP handler.

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()

// Menggunakan middleware Echo untuk menyertakan fungsi


// Logger dan Recovery
e.Use(middleware.Logger())
e.Use(middleware.Recover())

// Menetapkan route untuk handler yang akan diuji


e.GET("/hello", HelloHandler)

// Mulai server Echo


e.Start(":8080")
}

// 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"
)

func TestHelloHandler(t *testing.T) {


// Membuat instance Echo untuk pengujian
e := echo.New()

// Membuat request HTTP GET ke endpoint /hello


req := httptest.NewRequest(http.MethodGet, "/hello", nil)
rec := httptest.NewRecorder()

// Menjalankan HTTP handler yang akan diuji


c := e.NewContext(req, rec)
err := HelloHandler(c)

// Memastikan tidak ada kesalahan saat mengeksekusi handler


assert.NoError(t, err)

// Memeriksa bahwa status response adalah 200 OK


assert.Equal(t, http.StatusOK, rec.Code)

// Memeriksa bahwa body response sesuai dengan yang diharapkan


assert.Equal(t, "Hello, World!", rec.Body.String())
}
Dalam contoh ini, kita mendefinisikan HTTP handler (HelloHandler) dan menguji handler
tersebut dengan membuat instance Echo untuk pengujian. Tes dilakukan dengan membuat
permintaan HTTP palsu ke endpoint /hello dan memeriksa apakah respons yang diterima
sesuai dengan yang diharapkan.

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.

Hands-On: Menulis tests untuk API endpoints


Test Driven Development (TDD) adalah pendekatan pengembangan perangkat lunak di mana
pengujian unit dibuat sebelum implementasi fungsionalitas. Pada Golang, HTTP handler
seringkali digunakan untuk menangani permintaan API. Oleh karena itu, untuk memastikan
kualitas dan keandalan kode, menulis tes untuk API endpoints sangat penting.

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"
)

type User struct {


ID int `json:"id"`
Name string `json:"name"`
}

// GetUserHandler mengembalikan HTTP handler untuk mendapatkan


informasi pengguna berdasarkan ID.
func GetUserHandler(w http.ResponseWriter, r *http.Request) {
id := r.URL.Query().Get("id")
if id == "" {
http.Error(w, "ID parameter is required",
http.StatusBadRequest)
return
}

// Kembalikan data pengguna dalam format JSON


user := User{ID: 1, Name: "John Doe"}
jsonResponse, err := json.Marshal(user)
if err != nil {
http.Error(w, "Internal Server Error",
http.StatusInternalServerError)
return
}

w.Header().Set("Content-Type", "application/json")
w.Write(jsonResponse)
}

Selanjutnya, kita dapat menulis tes untuk handler ini:

// user_handler_test.go

package handlers

import (
"net/http"
"net/http/httptest"
"testing"
)

func TestGetUserHandler(t *testing.T) {


// Persiapkan request
req, err := http.NewRequest("GET", "/user?id=1", nil)
if err != nil {
t.Fatal(err)
}

// Persiapkan response recorder untuk merekam response HTTP


rr := httptest.NewRecorder()

// Panggil handler dengan request dan response recorder


http.HandlerFunc(GetUserHandler).ServeHTTP(rr, req)

// Periksa kode status HTTP


if status := rr.Code; status != http.StatusOK {
t.Errorf("Handler mengembalikan kode status %v, bukan
%v", status, http.StatusOK)
}
// Periksa konten response
expected := `{"id":1,"name":"John Doe"}`
if rr.Body.String() != expected {
t.Errorf("Handler mengembalikan hasil yang tidak sesuai.
Got %v, want %v", rr.Body.String(), expected)
}
}

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.

Strategi untuk Testing HTTP handlers


Testing HTTP handlers merupakan bagian penting dari Test Driven Development (TDD) pada
bahasa pemrograman Golang. Strategi untuk menguji HTTP handlers melibatkan pendekatan
yang cermat untuk memastikan bahwa fungsi-fungsi yang berhubungan dengan penanganan
permintaan HTTP dapat beroperasi dengan benar. Dalam TDD, pengujian dimulai sebelum
pengembangan aktual, dan untuk HTTP handlers, ini melibatkan pengujian apakah handler
dapat menanggapi permintaan dengan benar, menangani kesalahan dengan baik, dan
memberikan respon yang diharapkan.

Salah satu pendekatan yang umum digunakan adalah menggunakan package


net/http/httptest untuk membuat tes HTTP terisolasi tanpa memerlukan server HTTP
nyata. Berikut adalah contoh implementasi strategi pengujian HTTP handlers dalam Golang:

package main

import (
"net/http"
"net/http/httptest"
"testing"
)

// Handler fungsi yang akan diuji


func MyHandler(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusOK)
w.Write([]byte("Hello, World!"))
}

// TestMyHandler adalah fungsi pengujian untuk MyHandler


func TestMyHandler(t *testing.T) {
// Membuat permintaan palsu untuk diuji
req, err := http.NewRequest("GET", "/path", nil)
if err != nil {
t.Fatal(err)
}

// Membuat ResponseRecorder untuk merekam respon HTTP


rr := httptest.NewRecorder()

// Menetapkan handler dan menangani permintaan


handler := http.HandlerFunc(MyHandler)
handler.ServeHTTP(rr, req)

// Memeriksa apakah status kode yang dihasilkan benar


if status := rr.Code; status != http.StatusOK {
t.Errorf("Kode status yang diterima: %v, seharusnya: %v",
status, http.StatusOK)
}

// Memeriksa apakah respon yang dihasilkan sesuai dengan yang


diharapkan
expected := "Hello, World!"
if rr.Body.String() != expected {
t.Errorf("Respon yang diterima: %v, seharusnya: %v",
rr.Body.String(), expected)
}
}

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.

Hands-On: Menggunakan net/http/httptest untuk HTTP responses


Pada pengembangan perangkat lunak, Test Driven Development (TDD) adalah pendekatan di
mana pengujian ditulis sebelum implementasi kode. Dalam bahasa pemrograman Go (Golang),
package net/http/httptest menyediakan alat yang sangat berguna untuk menguji HTTP
handler. Pengujian ini dapat membantu memastikan bahwa handler HTTP berperilaku sesuai
dengan harapan dan memberikan respons yang benar.

Dalam menggunakan net/http/httptest untuk pengujian HTTP handler, langkah pertama


adalah membuat instance httptest.ResponseRecorder. Recorder ini berfungsi sebagai
penyimpanan respons HTTP yang akan dihasilkan oleh handler. Selanjutnya, dibuat instance
http.Request yang merepresentasikan permintaan HTTP yang akan disimulasikan selama
pengujian. Setelah itu, handler HTTP dijalankan dengan menggunakan metode ServeHTTP
yang ada pada handler tersebut, dengan menyertakan recorder dan permintaan simulasi
sebagai parameter.

package main

import (
"net/http"
"net/http/httptest"
"testing"
)

// Handler yang akan diuji


func helloHandler(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusOK)
w.Write([]byte("Hello, World!"))
}

func TestHelloHandler(t *testing.T) {


// Membuat instance ResponseRecorder dan Request
recorder := httptest.NewRecorder()
request := httptest.NewRequest(http.MethodGet, "/hello", nil)

// Menjalankan handler dengan menggunakan ServeHTTP


helloHandler(recorder, request)

// Memeriksa respons yang dihasilkan


if recorder.Code != http.StatusOK {
t.Errorf("Expected status code %d, got %d",
http.StatusOK, recorder.Code)
}

expectedBody := "Hello, World!"


actualBody := recorder.Body.String()
if actualBody != expectedBody {
t.Errorf("Expected response body %s, got %s",
expectedBody, actualBody)
}
}

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.

Testing pada Middleware


Duration: 90:00

Testing Echo middleware functions


Testing Echo middleware dalam Test Driven Development (TDD) pada Golang melibatkan
proses pengujian yang komprehensif terhadap middleware yang digunakan dalam aplikasi
berbasis Echo. Middleware merupakan lapisan perangkat lunak yang berfungsi sebagai
perantara antara permintaan (request) dan tanggapan (response) dalam aplikasi web. Untuk
memastikan bahwa middleware berfungsi dengan benar, TDD menjadi pendekatan yang sangat
bermanfaat.

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.

Menentukan Kasus Pengujian (Test Case)


Pertama, kita menentukan kasus pengujian yang mencakup skenario pengujian berbagai kondisi
yang mungkin terjadi saat middleware dijalankan. Sebagai contoh, kita akan membuat
middleware yang menambahkan header kustom ke setiap tanggapan.

// file: middleware_test.go

package main

import (
"net/http"
"net/http/httptest"
"testing"

"github.com/labstack/echo/v4"
"github.com/stretchr/testify/assert"
)

func TestCustomHeaderMiddleware(t *testing.T) {


// Setup Echo
e := echo.New()

// Set up a test route with the middleware


e.Use(CustomHeaderMiddleware("X-Custom-Header"))
e.GET("/", func(c echo.Context) error {
return c.String(http.StatusOK, "Hello, World!")
})

// Create a request and recorder for testing


req := httptest.NewRequest(http.MethodGet, "/", nil)
rec := httptest.NewRecorder()
c := e.NewContext(req, rec)

// Call the handler (middleware will be executed)


if assert.NoError(t, e.ServeHTTP(c.Response(), c.Request())) {
// Assert the custom header is set in the response
assert.Equal(t, http.StatusOK, rec.Code)
assert.Equal(t, "Hello, World!", rec.Body.String())
assert.Equal(t, "1", rec.Header().Get("X-Custom-Header"))
}
}

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"

// CustomHeaderMiddleware adalah middleware yang menambahkan header


kustom ke setiap tanggapan.
func CustomHeaderMiddleware(headerName string) echo.MiddlewareFunc
{
return func(next echo.HandlerFunc) echo.HandlerFunc {
return func(c echo.Context) error {
// Eksekusi handler berikutnya
err := next(c)

// Tambahkan header kustom ke tanggapan


c.Response().Header().Set(headerName, "1")

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 untuk Middleware


Property-based testing adalah suatu metode pengujian yang berfokus pada properti atau
karakteristik umum yang seharusnya dimiliki oleh suatu sistem atau komponen. Dalam konteks
Middleware pada Test Driven Development (TDD) di Golang, Property-based testing dapat
menjadi pendekatan yang kuat untuk memastikan bahwa Middleware berperilaku sesuai dengan
harapan, terutama dalam hal fungsionalitas, keandalan, dan performa.

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
}

// Lanjutkan ke handler selanjutnya jika otorisasi


berhasil
next.ServeHTTP(w, r)
})
}

AuthorizeMiddleware adalah fungsi middleware yang memeriksa header Authorization


pada permintaan HTTP. Jika token tidak valid, server akan memberikan respons
Unauthorized, jika valid, permintaan akan diteruskan ke handler selanjutnya.

Fungsi Properti: propertyAuthorizeMiddleware


// Fungsi untuk properti: Setiap permintaan yang dilewatkan ke
handler harus memiliki header Authorization yang valid.
func propertyAuthorizeMiddleware(token string) bool {
req, _ := http.NewRequest("GET", "/", nil)
req.Header.Set("Authorization", token)

handler := AuthorizeMiddleware(http.HandlerFunc(func(w
http.ResponseWriter, r *http.Request) {}))

// Jalankan permintaan melalui middleware


recorder := httptest.NewRecorder()
handler.ServeHTTP(recorder, req)

// Periksa kode status yang diharapkan berdasarkan


keberhasilan otorisasi
if recorder.Code == http.StatusUnauthorized {
return token != "valid_token"
}
return token == "valid_token"
}

propertyAuthorizeMiddleware adalah fungsi yang digunakan dalam property-based


testing. Fungsi ini membuat permintaan HTTP palsu dengan token yang diberikan,
menjalankannya melalui middleware, dan memeriksa apakah hasilnya sesuai dengan properti
yang diharapkan.
Fungsi Pengujian: TestAuthorizeMiddleware_Properties
func TestAuthorizeMiddleware_Properties(t *testing.T) {
// Uji properti untuk AuthorizeMiddleware menggunakan
testing/quick
if err := quick.Check(propertyAuthorizeMiddleware, nil); err
!= nil {
t.Error(err)
}
}

TestAuthorizeMiddleware_Properties adalah fungsi pengujian yang menggunakan


testing/quick untuk menjalankan property-based testing pada AuthorizeMiddleware.
Jika properti tidak terpenuhi, pengujian ini akan menghasilkan kesalahan dan memberikan
informasi lebih lanjut tentang kesalahan tersebut.

Dalam contoh di atas, kita membuat sebuah middleware otorisasi sederhana


(AuthorizeMiddleware) yang memeriksa header Authorization pada permintaan HTTP.
Kemudian, kita menggunakan property-based testing dengan menggunakan library
testing/quick untuk memverifikasi bahwa setiap permintaan yang dilewatkan ke
middleware memiliki header Authorization yang valid.

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.

Table-driven tests untuk Middleware


Table-driven tests merupakan pendekatan dalam pengujian perangkat lunak di mana data uji
dan hasil yang diharapkan disimpan dalam struktur data yang terstruktur, seperti tabel.
Pendekatan ini sangat berguna dalam pengujian middleware pada pengembangan berbasis uji
(Test-Driven Development/TDD) di Golang. Middleware adalah perangkat lunak yang
menyediakan layanan atau fungsi khusus di antara aplikasi atau komponen lainnya.

Dalam TDD, pengembang menciptakan tes sebelum mengimplementasikan fungsionalitas


sebenarnya. Untuk middleware di Golang, table-driven tests dapat membantu menyederhanakan
proses ini dengan menyediakan cara terstruktur untuk menguji berbagai kasus yang mungkin
terjadi.

package middleware

import (
"net/http"
"net/http/httptest"
"testing"
)

func TestMiddlewareHandler(t *testing.T) {


testCases := []struct {
name string
inputRequest *http.Request
expectedCode int
}{
{
name: "Test Case 1",
inputRequest: httptest.NewRequest("GET",
"/api/resource", nil),
expectedCode: http.StatusOK,
},
{
name: "Test Case 2",
inputRequest: httptest.NewRequest("POST",
"/api/resource", nil),
expectedCode: http.StatusMethodNotAllowed,
},
// Add more test cases as needed
}

for _, tc := range testCases {


t.Run(tc.name, func(t *testing.T) {
// Create a response recorder to capture the HTTP
response
recorder := httptest.NewRecorder()

// Create the middleware instance


middleware :=
MiddlewareHandler(http.HandlerFunc(dummyHandler))

// Serve the HTTP request with the middleware


middleware.ServeHTTP(recorder, tc.inputRequest)

// Check if the response code matches the expected


result
if recorder.Code != tc.expectedCode {
t.Errorf("Expected response code %d, but got
%d", tc.expectedCode, recorder.Code)
}
})
}
}

// Dummy handler for testing purposes


func dummyHandler(w http.ResponseWriter, r *http.Request) {
// This can be a placeholder for the actual logic inside the
handler
w.WriteHeader(http.StatusOK)
}

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.

Middleware tersebut diimplementasikan dalam fungsi MiddlewareHandler, dan fungsi


dummyHandler berfungsi sebagai placeholder untuk logika yang sebenarnya dalam handler.
Pengujian dapat diperluas dengan menambahkan lebih banyak kasus uji sesuai kebutuhan
proyek. Pendekatan ini membantu mengelola dan menyederhanakan pengujian middleware
dalam pengembangan berbasis uji di Golang.

Hands-On: Menulis Failing Test untuk Middleware


Dalam konteks pengujian perangkat lunak, menulis test untuk middleware sangat penting untuk
memastikan bahwa fungsi-fungsi ini beroperasi sesuai yang diharapkan. Pembuatan test yang
gagal (failing test) adalah langkah awal dalam pengembangan berbasis tes (test-driven
development) yang dapat membantu memastikan bahwa middleware berfungsi dengan benar
sebelum implementasi selesai.

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"
)

func TestAuthMiddlewareInvalidToken(t *testing.T) {


// Setup
e := echo.New()
req := httptest.NewRequest(http.MethodGet, "/protected", nil)
rec := httptest.NewRecorder()
c := e.NewContext(req, rec)

// Pengaturan token yang tidak valid


req.Header.Set("Authorization", "invalid_token")

// 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.

Testing pada Database


Duration: 90:00

Memulai Database Testing Interactions dalam Isolasi


Memulai pengujian interaksi dengan database dalam isolasi merupakan langkah krusial dalam
mengembangkan perangkat lunak yang handal dan efisien. Saat menggunakan GORM, sebuah
ORM (Object-Relational Mapping) yang populer dalam lingkungan pengembangan aplikasi
berbasis Golang, kita dapat meningkatkan kualitas pengujian kita dengan mengisolasi interaksi
dengan database. Ini dapat dilakukan dengan cara membuat dan menggunakan instance
database khusus untuk pengujian, yang berjalan di lingkungan terpisah agar tidak
mempengaruhi data produksi. Dengan isolasi ini, kita dapat dengan aman menjalankan
pengujian tanpa khawatir tentang efek samping pada data utama.

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")
}

// Migrate the schema


db.AutoMigrate(&models.User{})
}

func main() {
e := echo.New()

// Define your routes and handlers here

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"
)

func TestCreateUser(t *testing.T) {


// Setup
e := echo.New()
req := httptest.NewRequest(http.MethodPost, "/users", nil)
rec := httptest.NewRecorder()
c := e.NewContext(req, rec)

// Your route handler to create a user


handler := func(c echo.Context) error {
user := models.User{Name: "John Doe", Email:
"john.doe@example.com"}
db.Create(&user)
return c.String(http.StatusOK, "User created
successfully")
}

// Execute the handler


err := handler(c)

// Assertions
assert.NoError(t, err)
assert.Equal(t, http.StatusOK, rec.Code)

// Perform additional assertions based on your use case


// For example, check if the user was actually created in the
database
var savedUser models.User
db.First(&savedUser, "name = ?", "John Doe")
assert.Equal(t, "john.doe@example.com", savedUser.Email)
}

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.

Menulis Unit Tests untuk CRUD operations dengan Database


Menulis unit tests untuk operasi CRUD (Create, Read, Update, Delete) dengan database dalam
pengujian menggunakan GORM memastikan bahwa fungsionalitas basis data berfungsi
sebagaimana mestinya. GORM adalah ORM (Object-Relational Mapping) untuk Go yang
menyederhanakan akses dan manipulasi data di dalam database. Dalam pengujian database
dengan GORM, unit tests umumnya mencakup operasi penambahan data, pembacaan data,
pembaruan data, dan penghapusan data.

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"
)

type User struct {


gorm.Model
Username string
Email string
}

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"
)

// CreateUser menambahkan pengguna baru ke dalam database


func CreateUser(c echo.Context) error {
db := c.Get("db").(*gorm.DB)

user := models.User{
Username: c.FormValue("username"),
Email: c.FormValue("email"),
}

if err := db.Create(&user).Error; err != nil {


return c.JSON(http.StatusInternalServerError,
map[string]string{"error": err.Error()})
}

return c.JSON(http.StatusCreated, user)


}

// GetUser membaca informasi pengguna dari database berdasarkan ID


func GetUser(c echo.Context) error {
db := c.Get("db").(*gorm.DB)

userID := c.Param("id")

var user models.User


if err := db.First(&user, userID).Error; err != nil {
return c.JSON(http.StatusNotFound,
map[string]string{"error": "User not found"})
}

return c.JSON(http.StatusOK, user)


}

// UpdateUser memperbarui informasi pengguna di dalam database


berdasarkan ID
func UpdateUser(c echo.Context) error {
db := c.Get("db").(*gorm.DB)

userID := c.Param("id")

var user models.User


if err := db.First(&user, userID).Error; err != nil {
return c.JSON(http.StatusNotFound,
map[string]string{"error": "User not found"})
}
user.Username = c.FormValue("username")
user.Email = c.FormValue("email")

if err := db.Save(&user).Error; err != nil {


return c.JSON(http.StatusInternalServerError,
map[string]string{"error": err.Error()})
}

return c.JSON(http.StatusOK, user)


}

// DeleteUser menghapus pengguna dari database berdasarkan ID


func DeleteUser(c echo.Context) error {
db := c.Get("db").(*gorm.DB)

userID := c.Param("id")

var user models.User


if err := db.First(&user, userID).Error; err != nil {
return c.JSON(http.StatusNotFound,
map[string]string{"error": "User not found"})
}

if err := db.Delete(&user).Error; err != nil {


return c.JSON(http.StatusInternalServerError,
map[string]string{"error": err.Error()})
}

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"
)

func TestCreateUser(t *testing.T) {


// Setup Echo framework
e := echo.New()
req := httptest.NewRequest(http.MethodPost, "/users",
strings.NewReader("username=test&email=test@example.com"))
req.Header.Set(echo.HeaderContentType,
echo.MIMEApplicationForm)
rec := httptest.NewRecorder()
c := e.NewContext(req, rec)

// Setup GORM database connection (mock or actual database)


// ...

// Set database connection to Echo context


c.Set("db", db)

// Call the handler


if assert.NoError(t, CreateUser(c)) {
assert.Equal(t, http.StatusCreated, rec.Code)
// Additional assertions if needed
}
}

// Similar tests for GetUser, UpdateUser, and DeleteUser functions

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.

Menyiapkan Database Testing Terpisah untuk Integration Testing


Menyiapkan database testing terpisah untuk integration testing dalam pengujian pada database
dengan GORM melibatkan beberapa langkah kunci untuk memastikan keberlanjutan dan
keandalan pengujian perangkat lunak. Dengan mengintegrasikan GORM, yang merupakan ORM
(Object-Relational Mapping) untuk Go, dan menggunakan framework web Echo sebagai contoh,
pengembang dapat membangun sistem pengujian yang handal dan terisolasi.

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"
)

// Model struct untuk entitas dalam database


type User struct {
gorm.Model
Username string `json:"username"`
Email string `json:"email"`
}

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()

// Auto Migrate - Migrasi otomatis untuk struktur model


db.AutoMigrate(&User{})

// Inisialisasi Echo
e := echo.New()
// Middleware untuk logging
e.Use(middleware.Logger())

// Route
e.GET("/users/:id", getUser)

// Jalankan server
e.Start(":8080")
}

// Handler untuk mendapatkan pengguna berdasarkan ID


func getUser(c echo.Context) error {
userID := c.Param("id")

var user User


db.First(&user, userID)

return c.JSON(200, user)


}

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"
)

func TestGetUser(t *testing.T) {


// Setup
e := echo.New()
req := httptest.NewRequest(http.MethodGet, "/users/1", nil)
rec := httptest.NewRecorder()
c := e.NewContext(req, rec)

// 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")
}
}

Dalam contoh pengujian di atas, kita menggunakan package


github.com/stretchr/testify/assert untuk melakukan assercions terhadap respon
dari endpoint /users/1. Dengan cara ini, kita dapat memastikan bahwa pengujian berjalan
pada database testing terpisah dan memberikan hasil yang konsisten.

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.

Hands-On: Handling database transactions dalam tests


Dalam pengujian perangkat lunak, terutama yang melibatkan manipulasi database, pengelolaan
transaksi menjadi kritis untuk memastikan konsistensi data dan integritas sistem. Dalam
konteks pengujian database menggunakan GORM (sebuah Object-Relational Mapping untuk
Go), pengujian dapat dienhance dengan mengimplementasikan transaksi database. Hands-On
ini membahas bagaimana menangani transaksi database secara efektif dalam pengujian
menggunakan GORM, dengan contoh kode dalam bahasa pemrograman Go dan framework web
Echo.

Pertama, kita perlu mengimpor package-package yang diperlukan:

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:

func TestUserCreation(t *testing.T) {


// Inisialisasi database (gunakan SQLite untuk contoh ini)
db, err := gorm.Open(sqlite.Open(":memory:"), &gorm.Config{})
if err != nil {
t.Fatal(err)
}
defer db.Close()

// Migrate model ke database


db.AutoMigrate(&User{})

// Inisialisasi Echo framework (untuk memanfaatkan HTTP


handler)
e := echo.New()

// Fungsi untuk menangani pembuatan user


createUser := func(username, email string) error {
return db.Transaction(func(tx *gorm.DB) error {
user := User{Username: username, Email: email}
if err := tx.Create(&user).Error; err != nil {
return err
}
return nil
})
}

// Pengujian pembuatan user dalam transaksi


t.Run("CreateUser", func(t *testing.T) {
err := createUser("john_doe", "john@example.com")
if err != nil {
t.Fatal(err)
}

// Verifikasi hasil pembuatan user


var count int64
db.Model(&User{}).Count(&count)
if count != 1 {
t.Errorf("Expected 1 user, got %d", count)
}
})
}

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

Mengenal Mocking dalam Testing


Mocking dalam konteks pengujian perangkat lunak merupakan teknik yang digunakan untuk
mensimulasikan atau meniru perilaku objek atau komponen sistem yang sebenarnya. Tujuan
utama dari mocking adalah untuk mengisolasi unit atau modul yang sedang diuji sehingga
dapat dievaluasi secara independen tanpa tergantung pada bagian lain dari sistem. Dengan
menggunakan teknik ini, pengembang dapat membuat objek tiruan yang meniru objek nyata,
memungkinkan pengujian fungsionalitas tanpa harus bergantung pada komponen eksternal
yang mungkin belum sepenuhnya terimplementasi atau tidak tersedia selama fase
pengembangan.

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, Code Generation, and Dynamic Mocks


Terdapat beberapa pendekatan dalam melakukan mocking, termasuk Manual Mocks, Code
Generation, dan Dynamic Mocks.

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:

// Objek yang ingin di-mock


type Database interface {
GetData() string
}

// Manual Mock untuk objek Database


type MockDatabase struct{}

func (m *MockDatabase) GetData() string {


return "Mocked data"
}

// Fungsi yang menggunakan objek Database


func UseDatabase(db Database) string {
return db.GetData()
}

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/.../

# Generate mock dari antarmuka Database


mockery --name=Database

# Hasilnya adalah file mock_database.go

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

//go:generate mockgen -destination=mock_database_test.go


-package=mocks . Database

// Objek yang ingin di-mock


type Database interface {
GetData() string
}

// Fungsi yang menggunakan objek Database


func UseDatabase(db Database) string {
return db.GetData()
}

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

type UserService interface {


GetUserByID(userID int) (string, error)
CreateUser(name string) (int, error)
}

Kemudian, kita dapat menggunakan Mockgen untuk membuat mock dari antarmuka ini. Berikut
adalah contoh penggunaan Mockgen untuk membuat mock UserService

mockgen -destination=mocks/mock_user_service.go -package=mocks


your/package/path UserService

Hasilnya akan menjadi file mock_user_service.go dalam direktori mocks dengan


implementasi mock untuk UserService. Sekarang, kita dapat membuat pengujian
menggunakan mock ini

// user_test.go
package main

import (
"errors"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/mock"
"your/package/path/mocks"
)

func TestGetUserByID(t *testing.T) {


mockUserService := &mocks.UserService{}
mockUserService.On("GetUserByID", 1).Return("John Doe", nil)
mockUserService.On("GetUserByID", 2).Return("",
errors.New("User not found"))

// ... melakukan pengujian terhadap logika bisnis yang


menggunakan UserService ...

mockUserService.AssertExpectations(t)
}

func TestCreateUser(t *testing.T) {


mockUserService := &mocks.UserService{}
mockUserService.On("CreateUser", "Alice").Return(1, nil)
mockUserService.On("CreateUser", "Bob").Return(0,
errors.New("Failed to create user"))

// ... melakukan pengujian terhadap logika bisnis yang


menggunakan UserService ...

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

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"

type UserService struct {


UserRepository UserRepository
}

func (us *UserService) GetUserByID(userID int) (string, error) {


user, err := us.UserRepository.FindByID(userID)
if err != nil {
return "", err
}
return fmt.Sprintf("User: %s", user.Name), nil
}

Dalam contoh tersebut, UserService memiliki metode GetUserByID yang menggunakan


UserRepository untuk mendapatkan informasi pengguna berdasarkan ID. Dalam pengujian
unit, kita membuat MockUserRepository yang mengimplementasikan UserRepository,
dan kita menggunakan framework testify/mock untuk melakukan mocking.

// File: user_repository.go
package services

type UserRepository interface {


FindByID(userID int) (*User, error)
}

type User struct {


ID int
Name string
}

Pada bagian TestGetUserByID, kita melakukan setup dengan membuat objek


MockUserRepository, mendefinisikan perilaku yang diharapkan (melalui On dan Return),
dan kemudian memanggil metode GetUserByID dari UserService. Setelah itu, kita
melakukan asertasi terhadap hasil dan memverifikasi bahwa perilaku yang diharapkan
terpenuhi.

// File: user_service_test.go
package services_test

import (
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/mock"
"your_project/services"
)

type MockUserRepository struct {


mock.Mock
}

func (m *MockUserRepository) FindByID(userID int) (*services.User,


error) {
args := m.Called(userID)
return args.Get(0).(*services.User), args.Error(1)
}

func TestGetUserByID(t *testing.T) {


// Arrange
mockRepo := new(MockUserRepository)
userService := services.UserService{UserRepository: mockRepo}

expectedUser := &services.User{ID: 1, Name: "John Doe"}


mockRepo.On("FindByID", 1).Return(expectedUser, nil)
// Act
result, err := userService.GetUserByID(1)

// Assert
assert.NoError(t, err)
assert.Equal(t, "User: John Doe", result)

// Verify that the expected method was called


mockRepo.AssertExpectations(t)
}

Mocking functions dan methods dengan side effects


Mocking functions dan methods dengan side effects merupakan aspek penting dalam
pengujian perangkat lunak, memungkinkan pengembang untuk mengisolasi dan mengontrol
perilaku tertentu guna pengujian yang menyeluruh dan dapat diandalkan. Dalam konteks Golang
dan kerangka kerja Echo, pemahaman tentang cara menggunakan mocking dalam proses
pengujian sangatlah penting.

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.

Dalam Golang, package github.com/stretchr/testify/mock sering digunakan untuk


membuat objek mock. Mari kita pertimbangkan suatu skenario di mana kita memiliki server web
Echo dengan fungsi penangan yang berinteraksi dengan basis data, dan kita ingin menguji
fungsi ini tanpa benar-benar mengakses basis data.

// 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)
}

func fetchUserDataFromDatabase(userID string) string {


// Logika interaksi dengan basis data
// Logika simulasi untuk tujuan demonstrasi
return "Data pengguna untuk ID " + userID
}

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"
)

// Melakukan mocking terhadap interaksi basis data


type MockDB struct {
mock.Mock
}

func (m *MockDB) fetchUserDataFromDatabase(userID string) string {


args := m.Called(userID)
return args.String(0)
}

func TestGetUserHandler(t *testing.T) {


// Buat instansi basis data mock
mockDB := new(MockDB)

// Tentukan ekspektasi untuk metode mock


userID := "123"
mockDB.On("fetchUserDataFromDatabase", userID).Return("Data
pengguna yang dimock untuk ID " + userID)
// Buat permintaan dan perekam
req := httptest.NewRequest(http.MethodGet, "/user/"+userID,
nil)
rec := httptest.NewRecorder()

// Buat instansi Echo


e := echo.New()

// Buat konteks dari permintaan dan perekam tanggapan


c := e.NewContext(req, rec)

// Panggil fungsi penangan dengan basis data mock


getUserHandler(c, mockDB)

// Periksa tanggapan
assert.Equal(t, http.StatusOK, rec.Code)
assert.Equal(t, "Data pengguna yang dimock untuk ID "+userID,
rec.Body.String())

// Periksa bahwa metode mock dipanggil dengan parameter yang


benar
mockDB.AssertExpectations(t)
}

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.

Mocking Interaksi pada Database


Mocking interaksi pada database merupakan praktik yang umum dilakukan dalam
pengembangan perangkat lunak untuk menguji kode yang berinteraksi dengan sistem
penyimpanan data tanpa benar-benar berkomunikasi dengan database sebenarnya. Ini
memungkinkan pengembang untuk membuat unit test yang terisolasi dan dapat diulang tanpa
perlu mengakses database sebenarnya. Modul mocking membantu menciptakan situasi
simulasi di mana fungsi-fungsi yang berinteraksi dengan database dapat diuji secara terpisah
tanpa mempengaruhi integritas atau data yang ada di database produksi.

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

Inisialisasi Struktur Data


package models

import "gorm.io/gorm"

type User struct {


gorm.Model
Name string
Email string
}

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
}

func CreateUser(user *models.User) error {


result := DB.Create(user)
return result.Error
}

Handler dan Router


package main

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")
}

func GetUserHandler(c echo.Context) error {


email := c.Param("email")
user, err := database.GetUserByEmail(email)

if err != nil {
return c.JSON(http.StatusInternalServerError,
map[string]string{"error": "Failed to fetch user"})
}

return c.JSON(http.StatusOK, user)


}
func CreateUserHandler(c echo.Context) error {
var user models.User
if err := c.Bind(&user); err != nil {
return c.JSON(http.StatusBadRequest,
map[string]string{"error": "Invalid request payload"})
}

err := database.CreateUser(&user)
if err != nil {
return c.JSON(http.StatusInternalServerError,
map[string]string{"error": "Failed to create user"})
}

return c.JSON(http.StatusCreated, user)


}

Unit Test dengan Mocking


package main

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"
)

// Mocking Database Functions


type MockDB struct{}

func (m *MockDB) GetUserByEmail(email string) (*models.User, error)


{
// Mock implementation
return &models.User{
Name: "John Doe",
Email: email,
}, nil
}

func (m *MockDB) CreateUser(user *models.User) error {


// Mock implementation
return nil
}

// Unit Test for GetUserHandler


func TestGetUserHandler(t *testing.T) {
// Setup
e := echo.New()
req := httptest.NewRequest(http.MethodGet,
"/user/johndoe@example.com", nil)
rec := httptest.NewRecorder()
c := e.NewContext(req, rec)

// 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")
}
}

// Unit Test for CreateUserHandler


func TestCreateUserHandler(t *testing.T) {
// Setup
e := echo.New()
req := httptest.NewRequest(http.MethodPost, "/user",
strings.NewReader(`{"name": "John Doe", "email":
"johndoe@example.com"}`))
req.Header.Set(echo.HeaderContentType, echo.MIMEApplicationJSON)
rec := httptest.NewRecorder()
c := e.NewContext(req, rec)

// 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.

Test Coverage dan Benchmarking


Duration: 60:00
Memahami Test Coverage dan Benchmarking
Test coverage dan benchmarking adalah dua aspek kritis dalam pengembangan perangkat
lunak yang bertujuan untuk meningkatkan kualitas dan kinerja sistem. Test coverage mengacu
pada sejauh mana kode sumber sebuah program diuji oleh serangkaian tes. Hal ini mencakup
pemahaman terhadap bagian kode mana yang sudah dieksekusi selama proses pengujian,
memastikan bahwa seluruh fungsi dan jalur eksekusi telah tercakup. Test coverage membantu
para pengembang untuk mengidentifikasi area-area yang belum diuji sehingga mereka dapat
meminimalkan risiko bug atau kegagalan sistem.

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.

Dengan menggabungkan test coverage dan benchmarking, pengembang dapat mengidentifikasi


tidak hanya sejauh mana kode diuji, tetapi juga sejauh mana kinerja modul tersebut
dibandingkan dengan standar atau persyaratan yang diinginkan. Ini memungkinkan para
pengembang untuk membuat perbaikan atau peningkatan yang diperlukan pada modul
pengujian, memastikan bahwa modul tersebut mencakup seluruh fungsionalitas yang
diinginkan dan memberikan hasil yang optimal. Dengan demikian, pemahaman yang
komprehensif tentang test coverage dan benchmarking sangat penting dalam menghasilkan
perangkat lunak berkualitas tinggi dan dapat diandalkan.

Menganalisa Hasil Test Coverage dengan go test


Analisis hasil test coverage dengan go test untuk modul test coverage dan benchmarking
sangat penting dalam pengembangan perangkat lunak menggunakan bahasa pemrograman Go.
Test coverage mengukur sejauh mana kode sumber tercakup oleh pengujian, memberikan
pandangan yang mendalam tentang efektivitas pengujian unit.

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:

func BenchmarkMyFunction(b *testing.B) {


for i := 0; i < b.N; i++ {
// Kode yang ingin di-benchmark
result := MyFunction()
_ = result // Untuk memastikan variabel result tidak
dihapus oleh compiler
}
}

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.

Best Practice dalam Mendapatkan High Test Coverage


Mendapatkan high test coverage untuk modul merupakan suatu langkah penting dalam
pengembangan perangkat lunak guna memastikan bahwa sebagian besar kode telah diuji dan
berfungsi sesuai harapan. Beberapa praktik terbaik dapat diterapkan untuk mencapai cakupan
pengujian yang tinggi. Pertama, penting untuk merancang tes unit yang komprehensif yang
mencakup semua kemungkinan jalur eksekusi dalam sebuah modul. Ini dapat dicapai dengan
mengidentifikasi kasus pengujian yang mencakup berbagai kondisi dan skenario. Selanjutnya,
memanfaatkan teknik otomatisasi pengujian juga menjadi faktor kunci dalam meningkatkan
cakupan pengujian. Dengan otomatisasi, dapat memastikan bahwa pengujian dapat dilakukan
secara konsisten dan efisien setiap kali ada perubahan pada kode.

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"
)

func TestSomeFunction(t *testing.T) {


result := SomeFunction()
expected := // expected result
if result != expected {
t.Errorf("Expected %v, but got %v", expected, result)
}
}

// Tuliskan tes unit untuk fungsi lainnya dalam modul kita

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"
)

func BenchmarkSomeFunction(b *testing.B) {


for i := 0; i < b.N; i++ {
SomeFunction()
}
}

Jalankan benchmark dengan perintah go test -bench . untuk mendapatkan laporan


kinerja. Ini dapat membantu kita mengidentifikasi bagian-bagian kode yang memerlukan
perbaikan kinerja.

Profiling dan Optimization pada Test Coverage dan Benchmarking


Profiling dan optimasi dalam konteks modul test coverage dan benchmarking adalah dua aspek
kunci dalam pengembangan perangkat lunak yang bertujuan untuk meningkatkan kualitas,
kehandalan, dan kinerja suatu aplikasi. Profiling merujuk pada proses pemantauan dan analisis
eksekusi program untuk mengidentifikasi bagian-bagian kode yang membutuhkan perbaikan
atau perbaikan performa.

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.

// Contoh kode sumber Golang


package main

import "fmt"

func Add(a, b int) int {


return a + b
}

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.

// Contoh kode sumber Golang untuk benchmarking


package main

import (
"testing"
)

func BenchmarkAdd(b *testing.B) {


for i := 0; i < b.N; i++ {
Add(3, 5)
}
}

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.

Setelah mendapatkan informasi dari profiling dan benchmarking, pengembang dapat


mengoptimalkan kode untuk meningkatkan kinerja. Misalnya, dengan menggunakan struktur
data yang lebih efisien, mengurangi alokasi memori yang tidak perlu, atau menggunakan
algoritma yang lebih cepat. Golang menyediakan profiler bawaan (go tool pprof) yang
dapat digunakan untuk menganalisis waktu eksekusi dan alokasi memori.

Dengan mengintegrasikan profil, test coverage, dan benchmarking, pengembang dapat


membangun pemahaman yang mendalam tentang kekuatan dan kelemahan aplikasi mereka.
Proses ini tidak hanya membantu mengidentifikasi dan memperbaiki bug atau inefficiency,
tetapi juga memastikan bahwa aplikasi berkinerja optimal dan sesuai dengan standar industri.
Keseluruhan, profil, test coverage, dan benchmarking bersinergi untuk memberikan pandangan
holistik terhadap kualitas dan kinerja perangkat lunak, memfasilitasi pengembangan aplikasi
yang tangguh, efisien, dan dapat diandalkan.

Anda mungkin juga menyukai