1. โ Pengenalan Go Testing
Go memiliki testing package bawaan yang sangat powerful โ tidak perlu install framework pihak ketiga! Go testing mendukung unit test, benchmark, test coverage, fuzzing, dan examples.
Fitur Go Testing
| Fitur | Perintah | Penjelasan |
|---|---|---|
| Unit Test | go test | Menjalankan semua test function |
| Verbose | go test -v | Output detail setiap test |
| Specific Test | go test -run TestNama | Menjalankan test tertentu |
| Benchmark | go test -bench=. | Mengukur performa fungsi |
| Coverage | go test -cover | Persentase kode yang ter-test |
| Race Detector | go test -race | Mendeteksi race condition |
| Fuzzing | go test -fuzz=FuzzXxx | Randomized testing (Go 1.18+) |
| Parallel | t.Parallel() | Menjalankan test secara paralel |
Conventions
| Aturan | Penjelasan |
|---|---|
| File naming | *_test.go โ file test harus berakhiran _test.go |
| Function naming | func TestXxx(t *testing.T) โ harus diawali Test + huruf kapital |
| Package | Bisa package foo (white-box) atau package foo_test (black-box) |
| Same directory | File test harus di directory yang sama dengan source code |
| Benchmark naming | func BenchmarkXxx(b *testing.B) |
my-project/
โโโ calculator.go โ Source code
โโโ calculator_test.go โ Test file (sama package)
โโโ math/
โ โโโ math.go โ Source code
โ โโโ math_test.go โ Unit test
โ โโโ math_example_test.goโ Example test
โ โโโ math_bench_test.go โ Benchmark test
โโโ api/
โโโ handler.go
โโโ handler_test.go
// Jalankan semua test:
// go test ./...
// Jalankan test di package tertentu:
// go test ./math/
// Jalankan test dengan pattern:
// go test -run TestTambah ./...
2. Unit Test Dasar
Mari mulai dengan unit test sederhana. Buat file calculator.go dan calculator_test.go.
Source Code
package calculator
import "errors"
var ErrDivByZero = errors.New("pembagian dengan nol")
var ErrNegativeRoot = errors.New("akar dari bilangan negatif")
func Tambah(a, b float64) float64 {
return a + b
}
func Kurang(a, b float64) float64 {
return a - b
}
func Kali(a, b float64) float64 {
return a * b
}
func Bagi(a, b float64) (float64, error) {
if b == 0 {
return 0, ErrDivByZero
}
return a / b, nil
}
func Faktorial(n int) int {
if n <= 1 {
return 1
}
return n * Faktorial(n-1)
}
func IsPrime(n int) bool {
if n < 2 {
return false
}
for i := 2; i*i <= n; i++ {
if n%i == 0 {
return false
}
}
return true
}
Test File
package calculator
import (
"math"
"testing"
)
// Test sederhana
func TestTambah(t *testing.T) {
result := Tambah(2, 3)
expected := 5.0
if result != expected {
t.Errorf("Tambah(2, 3) = %f, expected %f", result, expected)
}
}
// Test dengan tolerance untuk float
func TestTambahFloat(t *testing.T) {
result := Tambah(0.1, 0.2)
expected := 0.3
if math.Abs(result-expected) > 1e-9 {
t.Errorf("Tambah(0.1, 0.2) = %f, expected %f", result, expected)
}
}
// Test error
func TestBagiByZero(t *testing.T) {
_, err := Bagi(10, 0)
if err == nil {
t.Error("Bagi(10, 0) seharusnya mengembalikan error")
}
if err != ErrDivByZero {
t.Errorf("Error = %v, expected %v", err, ErrDivByZero)
}
}
// Test normal case
func TestBagiNormal(t *testing.T) {
result, err := Bagi(10, 3)
if err != nil {
t.Fatalf("Tidak ada error yang diharapkan, got %v", err)
}
expected := 10.0 / 3.0
if math.Abs(result-expected) > 1e-9 {
t.Errorf("Bagi(10, 3) = %f, expected %f", result, expected)
}
}
// Test function dengan banyak cases
func TestFaktorial(t *testing.T) {
// Helper function untuk assertion
assertEqual := func(t *testing.T, got, want int) {
t.Helper() // Tandai sebagai helper (better error location)
if got != want {
t.Errorf("got %d, want %d", got, want)
}
}
assertEqual(t, Faktorial(0), 1)
assertEqual(t, Faktorial(1), 1)
assertEqual(t, Faktorial(5), 120)
assertEqual(t, Faktorial(10), 3628800)
}
// Test IsPrime
func TestIsPrime(t *testing.T) {
primes := []int{2, 3, 5, 7, 11, 13, 17, 19, 23, 29}
nonPrimes := []int{0, 1, 4, 6, 8, 9, 10, 15, 21, 25}
for _, n := range primes {
if !IsPrime(n) {
t.Errorf("IsPrime(%d) = false, expected true", n)
}
}
for _, n := range nonPrimes {
if IsPrime(n) {
t.Errorf("IsPrime(%d) = true, expected false", n)
}
}
}
Menjalankan Test
# Jalankan semua test di package saat ini go test # Verbose output go test -v # === RUN TestTambah # --- PASS: TestTambah (0.00s) # === RUN TestBagiByZero # --- PASS: TestBagiByZero (0.00s) # PASS # ok calc 0.002s # Jalankan test tertentu saja go test -v -run TestTambah # Jalankan semua test di semua package go test ./... # Dengan race detector go test -race ./...
3. Table-Driven Tests
Table-driven tests adalah pattern testing paling populer di Go. Daripada menulis test terpisah untuk setiap case, kita mendefinisikan semua test cases dalam slice of struct.
package calculator
import (
"math"
"testing"
)
func TestTambahTableDriven(t *testing.T) {
tests := []struct {
name string
a, b float64
expected float64
}{
{"positif + positif", 2, 3, 5},
{"positif + negatif", 5, -3, 2},
{"negatif + negatif", -2, -3, -5},
{"dengan nol", 5, 0, 5},
{"nol + nol", 0, 0, 0},
{"desimal", 1.5, 2.5, 4.0},
{"angka besar", 1e10, 1e10, 2e10},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
result := Tambah(tt.a, tt.b)
if math.Abs(result-tt.expected) > 1e-9 {
t.Errorf("Tambah(%f, %f) = %f, want %f",
tt.a, tt.b, result, tt.expected)
}
})
}
}
func TestBagiTableDriven(t *testing.T) {
tests := []struct {
name string
a, b float64
expected float64
expectError bool
errType error
}{
{"normal", 10, 2, 5, false, nil},
{"hasil desimal", 10, 3, 3.333333, false, nil},
{"negatif", -10, 2, -5, false, nil},
{"bagi nol", 10, 0, 0, true, ErrDivByZero},
{"nol dibagi", 0, 5, 0, false, nil},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
result, err := Bagi(tt.a, tt.b)
if tt.expectError {
if err == nil {
t.Error("Expected error, got nil")
}
if err != tt.errType {
t.Errorf("Error type = %v, want %v", err, tt.errType)
}
return
}
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
if math.Abs(result-tt.expected) > 1e-5 {
t.Errorf("Bagi(%f, %f) = %f, want %f",
tt.a, tt.b, result, tt.expected)
}
})
}
}
func TestIsPrimeTableDriven(t *testing.T) {
tests := []struct {
input int
expected bool
}{
{0, false},
{1, false},
{2, true},
{3, true},
{4, false},
{5, true},
{15, false},
{17, true},
{49, false},
{97, true},
{-5, false},
}
for _, tt := range tests {
t.Run(
fmt.Sprintf("IsPrime(%d)", tt.input),
func(t *testing.T) {
got := IsPrime(tt.input)
if got != tt.expected {
t.Errorf("IsPrime(%d) = %v, want %v",
tt.input, got, tt.expected)
}
},
)
}
}
Gunakan t.Run(name, func) untuk subtests yang bisa dijalankan/difilter secara terpisah. Gunakan t.Helper() di helper functions agar error message menunjukkan lokasi test yang sebenarnya, bukan lokasi helper function.
4. Subtests
t.Run() membuat subtests yang bisa dijalankan secara individual dan memiliki output yang terstruktur.
package calculator
import "testing"
func TestKalkulasiLengkap(t *testing.T) {
t.Run("Tambah", func(t *testing.T) {
t.Run("positif", func(t *testing.T) {
if Tambah(2, 3) != 5 {
t.Error("gagal")
}
})
t.Run("negatif", func(t *testing.T) {
if Tambah(-2, -3) != -5 {
t.Error("gagal")
}
})
})
t.Run("Bagi", func(t *testing.T) {
t.Run("normal", func(t *testing.T) {
result, err := Bagi(10, 2)
if err != nil {
t.Fatal(err)
}
if result != 5 {
t.Errorf("got %f, want 5", result)
}
})
t.Run("error_nol", func(t *testing.T) {
_, err := Bagi(10, 0)
if err == nil {
t.Error("expected error")
}
})
})
}
// Jalankan subtest tertentu:
// go test -run TestKalkulasiLengkap/Tambah/positif
// go test -run TestKalkulasiLengkap/Bagi
// Parallel subtests
func TestParallelSubtests(t *testing.T) {
tests := []struct {
name string
input int
}{
{"case_1", 1},
{"case_2", 2},
{"case_3", 3},
}
for _, tt := range tests {
tt := tt // capture loop variable
t.Run(tt.name, func(t *testing.T) {
t.Parallel() // Jalankan secara paralel
result := Faktorial(tt.input)
if result <= 0 {
t.Error("Hasil harus positif")
}
})
}
}
5. Test Fixtures & TestMain
TestMain dijalankan sekali sebelum semua test di package โ cocok untuk setup/teardown seperti database connection atau test fixtures.
package calculator
import (
"fmt"
"os"
"testing"
}
// TestMain โ dijalankan sekali sebelum semua test
func TestMain(m *testing.M) {
fmt.Println("๐ง Setup: Inisialisasi test environment...")
// Setup (misal: buat database test, load fixtures)
setup()
// Jalankan semua test
code := m.Run()
// Teardown (misal: hapus database test)
teardown()
fmt.Println("๐งน Teardown: Cleanup selesai")
os.Exit(code)
}
func setup() {
fmt.Println(" โ
Database test siap")
fmt.Println(" โ
Fixtures loaded")
}
func teardown() {
fmt.Println(" ๐๏ธ Database test dihapus")
}
// Helper functions untuk test
func assertFloat(t *testing.T, got, want float64) {
t.Helper()
diff := got - want
if diff < 0 {
diff = -diff
}
if diff > 1e-9 {
t.Errorf("got %f, want %f", got, want)
}
}
func assertError(t *testing.T, got, want error) {
t.Helper()
if got != want {
t.Errorf("got error %v, want %v", got, want)
}
}
func assertNoError(t *testing.T, err error) {
t.Helper()
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
}
func assertTrue(t *testing.T, val bool, msg string) {
t.Helper()
if !val {
t.Error(msg)
}
}
// Temp files dan directories
func TestWithTempFile(t *testing.T) {
// t.TempDir() โ otomatis dihapus setelah test selesai
dir := t.TempDir()
fmt.Println("Temp dir:", dir)
// Buat file di temp directory
filePath := dir + "/test.txt"
os.WriteFile(filePath, []byte("test data"), 0644)
// File akan otomatis dihapus setelah test selesai
}
6. Mocking & Dependency Injection
Go menggunakan interface untuk mocking โ tidak perlu framework mocking. Ini adalah salah satu keunggulan desain Go: dependency injection melalui interface.
// === Source code ===
// user_service.go
package user
// Interface untuk dependency
type UserRepository interface {
GetByID(id int) (*User, error)
Save(user *User) error
Delete(id int) error
}
type User struct {
ID int
Name string
Email string
}
// Service menggunakan interface (bukan implementasi konkret)
type UserService struct {
repo UserRepository
}
func NewUserService(repo UserRepository) *UserService {
return &UserService{repo: repo}
}
func (s *UserService) GetUserName(id int) (string, error) {
user, err := s.repo.GetByID(id)
if err != nil {
return "", err
}
return user.Name, nil
}
func (s *UserService) Register(name, email string) (*User, error) {
// Validasi
if name == "" {
return nil, errors.New("nama tidak boleh kosong")
}
if !strings.Contains(email, "@") {
return nil, errors.New("email tidak valid")
}
user := &User{Name: name, Email: email}
if err := s.repo.Save(user); err != nil {
return nil, err
}
return user, nil
}
// === Test file ===
// user_service_test.go
package user
import (
"errors"
"testing"
)
// Mock repository
type MockUserRepo struct {
users map[int]*User
saveErr error
getErr error
}
func NewMockRepo() *MockUserRepo {
return &MockUserRepo{
users: map[int]*User{
1: {ID: 1, Name: "Budi", Email: "budi@mail.com"},
2: {ID: 2, Name: "Ani", Email: "ani@mail.com"},
},
}
}
func (m *MockUserRepo) GetByID(id int) (*User, error) {
if m.getErr != nil {
return nil, m.getErr
}
user, ok := m.users[id]
if !ok {
return nil, errors.New("user not found")
}
return user, nil
}
func (m *MockUserRepo) Save(user *User) error {
if m.saveErr != nil {
return m.saveErr
}
user.ID = len(m.users) + 1
m.users[user.ID] = user
return nil
}
func (m *MockUserRepo) Delete(id int) error {
delete(m.users, id)
return nil
}
// === Tests ===
func TestGetUserName(t *testing.T) {
t.Run("success", func(t *testing.T) {
mock := NewMockRepo()
svc := NewUserService(mock)
name, err := svc.GetUserName(1)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if name != "Budi" {
t.Errorf("got %s, want Budi", name)
}
})
t.Run("not_found", func(t *testing.T) {
mock := NewMockRepo()
svc := NewUserService(mock)
_, err := svc.GetUserName(999)
if err == nil {
t.Error("expected error")
}
})
t.Run("repo_error", func(t *testing.T) {
mock := NewMockRepo()
mock.getErr = errors.New("database connection failed")
svc := NewUserService(mock)
_, err := svc.GetUserName(1)
if err == nil {
t.Error("expected error")
}
})
}
func TestRegister(t *testing.T) {
tests := []struct {
name string
userName string
email string
saveErr error
expectError bool
}{
{"success", "Dimas", "dimas@mail.com", nil, false},
{"empty_name", "", "test@mail.com", nil, true},
{"invalid_email", "Sari", "invalid-email", nil, true},
{"save_error", "Rina", "rina@mail.com", errors.New("db error"), true},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
mock := NewMockRepo()
mock.saveErr = tt.saveErr
svc := NewUserService(mock)
user, err := svc.Register(tt.userName, tt.email)
if tt.expectError {
if err == nil {
t.Error("expected error")
}
return
}
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if user.Name != tt.userName {
t.Errorf("name = %s, want %s", user.Name, tt.userName)
}
})
}
}
7. HTTP Handler Testing
Go menyediakan httptest package untuk menguji HTTP handler tanpa perlu menjalankan server sebenarnya.
// === Source: handler.go ===
package api
import (
"encoding/json"
"net/http"
)
type Response struct {
Status string `json:"status"`
Data interface{} `json:"data,omitempty"`
Error string `json:"error,omitempty"`
}
func HealthHandler(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(Response{Status: "ok"})
}
func GetUserHandler(w http.ResponseWriter, r *http.Request) {
id := r.PathValue("id")
if id == "" {
w.WriteHeader(http.StatusBadRequest)
json.NewEncoder(w).Encode(Response{Error: "ID diperlukan"})
return
}
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(Response{
Status: "ok",
Data: map[string]string{"id": id, "name": "Budi"},
})
}
// === Test: handler_test.go ===
package api
import (
"encoding/json"
"net/http"
"net/http/httptest"
"testing"
)
func TestHealthHandler(t *testing.T) {
// Buat request
req := httptest.NewRequest(http.MethodGet, "/health", nil)
// Buat ResponseRecorder
w := httptest.NewRecorder()
// Panggil handler
HealthHandler(w, req)
// Assert status code
if w.Code != http.StatusOK {
t.Errorf("status = %d, want %d", w.Code, http.StatusOK)
}
// Parse response
var resp Response
json.NewDecoder(w.Body).Decode(&resp)
if resp.Status != "ok" {
t.Errorf("status = %s, want ok", resp.Status)
}
}
func TestGetUserHandler(t *testing.T) {
tests := []struct {
name string
path string
wantStatus int
wantName string
}{
{"valid_id", "/api/users/1", http.StatusOK, "Budi"},
{"another_id", "/api/users/2", http.StatusOK, "Budi"},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
req := httptest.NewRequest(http.MethodGet, tt.path, nil)
req.SetPathValue("id", tt.path[len("/api/users/"):])
w := httptest.NewRecorder()
GetUserHandler(w, req)
if w.Code != tt.wantStatus {
t.Errorf("status = %d, want %d", w.Code, tt.wantStatus)
}
var resp Response
json.NewDecoder(w.Body).Decode(&resp)
if data, ok := resp.Data.(map[string]interface{}); ok {
if name, ok := data["name"].(string); ok {
if name != tt.wantName {
t.Errorf("name = %s, want %s", name, tt.wantName)
}
}
}
})
}
}
// Test dengan Gin (jika pakai Gin)
// func TestGinHandler(t *testing.T) {
// gin.SetMode(gin.TestMode)
// r := gin.New()
// r.GET("/health", func(c *gin.Context) {
// c.JSON(200, gin.H{"status": "ok"})
// })
//
// req := httptest.NewRequest("GET", "/health", nil)
// w := httptest.NewRecorder()
// r.ServeHTTP(w, req)
//
// if w.Code != 200 {
// t.Errorf("expected 200, got %d", w.Code)
// }
// }
8. Benchmarking
Benchmark mengukur performa fungsi dalam nanoseconds per operation. Sangat penting untuk mengoptimasi kode kritis.
package calculator
import (
"fmt"
"strings"
"testing"
)
// Benchmark sederhana
func BenchmarkTambah(b *testing.B) {
for i := 0; i < b.N; i++ {
Tambah(2.5, 3.7)
}
}
func BenchmarkKali(b *testing.B) {
for i := 0; i < b.N; i++ {
Kali(2.5, 3.7)
}
}
func BenchmarkFaktorial(b *testing.B) {
for i := 0; i < b.N; i++ {
Faktorial(20)
}
}
func BenchmarkIsPrime(b *testing.B) {
for i := 0; i < b.N; i++ {
IsPrime(97)
}
}
// Benchmark dengan data berbeda
func BenchmarkFaktorialSizes(b *testing.B) {
sizes := []int{5, 10, 20, 50}
for _, size := range sizes {
b.Run(fmt.Sprintf("n=%d", size), func(b *testing.B) {
for i := 0; i < b.N; i++ {
Faktorial(size)
}
})
}
}
// Benchmark string concatenation methods
func BenchmarkStringConcat(b *testing.B) {
b.Run("Plus", func(b *testing.B) {
for i := 0; i < b.N; i++ {
s := ""
for j := 0; j < 100; j++ {
s += "x"
}
}
})
b.Run("Builder", func(b *testing.B) {
for i := 0; i < b.N; i++ {
var sb strings.Builder
for j := 0; j < 100; j++ {
sb.WriteString("x")
}
_ = sb.String()
}
})
b.Run("Join", func(b *testing.B) {
for i := 0; i < b.N; i++ {
parts := make([]string, 100)
for j := 0; j < 100; j++ {
parts[j] = "x"
}
_ = strings.Join(parts, "")
}
})
}
// Benchmark map operations
func BenchmarkMapVsSlice(b *testing.B) {
b.Run("Map_Lookup", func(b *testing.B) {
m := make(map[int]bool)
for i := 0; i < 1000; i++ {
m[i] = true
}
b.ResetTimer()
for i := 0; i < b.N; i++ {
_ = m[500]
}
})
b.Run("Slice_Search", func(b *testing.B) {
s := make([]int, 1000)
for i := 0; i < 1000; i++ {
s[i] = i
}
b.ResetTimer()
for i := 0; i < b.N; i++ {
for _, v := range s {
if v == 500 {
break
}
}
}
})
}
Menjalankan Benchmark
# Jalankan semua benchmark go test -bench=. # Verbose output go test -bench=. -benchmem # BenchmarkTambah-8 1000000000 0.25 ns/op 0 B/op 0 allocs/op # BenchmarkFaktorial-8 20000000 58.5 ns/op 0 B/op 0 allocs/op # Jalankan benchmark tertentu go test -bench=BenchmarkTambah -benchmem # Set jumlah iterasi (count) go test -bench=. -count=5 # Memory profiling go test -bench=. -benchmem -memprofile=mem.out go tool pprof mem.out # CPU profiling go test -bench=. -cpuprofile=cpu.out go tool pprof cpu.out # Output penjelasan: # BenchmarkXxx-8 1000000000 0.25 ns/op 0 B/op 0 allocs/op # โ โ โ โ โ โ # โ โ โ โ โ โโ allocs per op # โ โ โ โ โโ bytes per op # โ โ โ โโ nanoseconds per operation # โ โ โโ total iterations # โ โโ GOMAXPROCS # โโ benchmark name
9. Test Coverage
Test coverage menunjukkan persentase kode yang dijalankan oleh test. Targetkan minimal 70-80% untuk production code.
# Lihat coverage percentage
go test -cover
# PASS
# coverage: 85.7% of statements
# ok calculator 0.003s
# Coverage untuk semua package
go test -cover ./...
# Generate coverage profile
go test -coverprofile=coverage.out
# Lihat coverage di browser (HTML report)
go tool cover -html=coverage.out
# Lihat detail per function
go test -coverprofile=coverage.out
go tool cover -func=coverage.out
# calculator.go:12: Tambah 100.0%
# calculator.go:16: Kurang 100.0%
# calculator.go:20: Kali 100.0%
# calculator.go:24: Bagi 85.7%
# calculator.go:32: Faktorial 100.0%
# calculator.go:38: IsPrime 66.7%
# total: (statements) 87.5%
# Coverage dengan race detector
go test -cover -race ./...
# Coverage threshold (script)
# Cek apakah coverage di atas 80%
coverage=$(go test -cover ./... | grep -oP 'coverage: \K[0-9.]+')
if (( $(echo "$coverage < 80" | bc -l) )); then
echo "โ Coverage $coverage% di bawah threshold 80%"
exit 1
fi
echo "โ
Coverage $coverage%"
Coverage 100% tidak menjamin kode bebas bug. Yang lebih penting adalah kualitas test โ apakah test menguji edge cases, error handling, dan behavior yang benar? Fokus pada test yang bermakna, bukan sekadar angka coverage.
10. Best Practices & Tips
// 1. Gunakan t.Helper() di helper functions
func assertEqual(t *testing.T, got, want interface{}) {
t.Helper()
if got != want {
t.Errorf("got %v, want %v", got, want)
}
}
// 2. Gunakan t.Fatal() untuk error yang menghentikan test
func TestSomething(t *testing.T) {
db, err := connectDB()
if err != nil {
t.Fatalf("Cannot connect to DB: %v", err) // Stop test
}
defer db.Close()
// ... lanjut test
}
// 3. Gunakan t.Cleanup() untuk teardown
func TestWithCleanup(t *testing.T) {
dir := t.TempDir()
file := createTempFile(dir)
t.Cleanup(func() {
// Otomatis dijalankan setelah test selesai
fmt.Println("Cleanup:", dir)
})
// Test logic di sini...
}
// 4. Test error messages yang deskriptif
// โ Buruk:
if result != expected {
t.Error("wrong result")
}
// โ
Bagus:
if result != expected {
t.Errorf("Tambah(2, 3) = %f; want %f", result, expected)
}
// 5. Gunakan t.Parallel() untuk test independen
func TestParallel(t *testing.T) {
t.Parallel()
// Test ini bisa berjalan paralel dengan test lain
}
// 6. Jangan test implementation details โ test behavior
// โ Buruk: Mengecek internal state
// โ
Bagus: Mengecek output dan behavior
// 7. Setiap test harus independen dan bisa dijalankan sendiri
// โ Buruk: Test B bergantung pada test A
// โ
Bagus: Setiap test membuat state sendiri
// 8. Gunakan t.Skip() untuk conditional tests
func TestNeedsDatabase(t *testing.T) {
if os.Getenv("DATABASE_URL") == "" {
t.Skip("DATABASE_URL not set, skipping")
}
// ... test database
}
// 9. Example functions โ auto-generated docs + test
func ExampleTambah() {
fmt.Println(Tambah(2, 3))
// Output: 5
}
func ExampleBagi() {
result, _ := Bagi(10, 3)
fmt.Printf("%.2f\n", result)
// Output: 3.33
}
// 10. Fuzzing โ randomized testing (Go 1.18+)
func FuzzIsPrime(f *testing.F) {
f.Add(2) // Seed corpus
f.Add(15)
f.Add(97)
f.Fuzz(func(t *testing.T, n int) {
result := IsPrime(n)
// Tidak boleh panic, regardless of input
if n < 2 && result {
t.Errorf("IsPrime(%d) = true, want false for n < 2", n)
}
})
}
// Jalankan: go test -fuzz=FuzzIsPrime -fuzztime=30s
11. Quiz: Uji Pemahamanmu!
Setelah membaca tutorial di atas, jawablah 5 pertanyaan berikut untuk menguji pemahamanmu tentang Go Testing: