Testing, Table-Driven Tests & Benchmarks in Go 🧪
Go includes a comprehensive, built-in testing framework right in the standard library through the testing package and the go test command.
1. Idiomatic Table-Driven Tests (t.Run)
The standard convention in the Go community is Table-Driven Testing: defining a slice of test cases and iterating over them with t.Run():
// calc_test.go
package calc_test
import (
"testing"
)
func Add(a, b int) int {
return a + b
}
func TestAdd(t *testing.T) {
// Define the test case table
tests := []struct {
name string
a, b int
expected int
}{
{name: "positive numbers", a: 2, b: 3, expected: 5},
{name: "with zero", a: 10, b: 0, expected: 10},
{name: "negative numbers", a: -5, b: -3, expected: -8},
}
// Run each test as a subtest
for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
result := Add(tc.a, tc.b)
if result != tc.expected {
t.Errorf("Add(%d, %d) = %d; want %d", tc.a, tc.b, result, tc.expected)
}
})
}
}# Run tests with verbose output:
go test -v ./...2. Testing HTTP Handlers with net/http/httptest
You can test HTTP handlers directly without opening actual network ports using httptest.NewRecorder:
package handler_test
import (
"net/http"
"net/http/httptest"
"testing"
)
func HealthCheckHandler(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusOK)
w.Write([]byte("healthy"))
}
func TestHealthCheckHandler(t *testing.T) {
// 1. Create simulated request
req := httptest.NewRequest(http.MethodGet, "/healthz", nil)
// 2. Create ResponseRecorder to capture output
rr := httptest.NewRecorder()
// 3. Call handler directly
HealthCheckHandler(rr, req)
// 4. Assert response
if rr.Code != http.StatusOK {
t.Errorf("expected status 200, got %d", rr.Code)
}
if rr.Body.String() != "healthy" {
t.Errorf("expected body 'healthy', got %s", rr.Body.String())
}
}3. Writing Benchmarks (b.N)
Go provides first-class support for benchmarking CPU performance and memory allocations:
// strings_test.go
package main
import (
"strings"
"testing"
)
func BenchmarkStringConcat(b *testing.B) {
// b.N is dynamically adjusted by Go until results are statistically stable
for i := 0; i < b.N; i++ {
var s strings.Builder
for j := 0; j < 100; j++ {
s.WriteString("test")
}
_ = s.String()
}
}# Run benchmarks with memory allocation metrics:
go test -bench=. -benchmem
# BenchmarkStringConcat-8 500000 2400 ns/op 512 B/op 1 allocs/opAcross the Wiki
- Unit Testing Concepts — Testing best practices.
- Test Doubles, Mocks & Stubs — Mocking dependencies in tests.
- Go Interfaces — Designing interfaces for testability.