Go includes testing, benchmarking, fuzzing, and coverage support in its standard toolchain. Tests live in files ending with _test.go and run with go test ./....
Start with behavior
func TestAdd(t *testing.T) {
got := Add(2, 3)
if got != 5 {
t.Fatalf("Add(2, 3) = %d; want 5", got)
}
}
Failure messages should show the operation, actual result, and expected result.
Use table-driven tests
func TestNormalize(t *testing.T) {
tests := []struct {
name string
in string
want string
}{
{"trims", " Go ", "go"},
{"empty", "", ""},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := Normalize(tt.in); got != tt.want {
t.Fatalf("got %q; want %q", got, tt.want)
}
})
}
}
Named subtests make failures easy to locate. Add cases that represent meaningful behavior boundaries, not every imaginable value.
Test HTTP handlers
req := httptest.NewRequest(http.MethodGet, "/health", nil)
rec := httptest.NewRecorder()
handler.ServeHTTP(rec, req)
if rec.Code != http.StatusOK {
t.Fatalf("status = %d; want %d", rec.Code, http.StatusOK)
}
Assert status, important headers, and decoded response data. Avoid brittle comparison of irrelevant JSON formatting.
Replace external boundaries with fakes
Define a small consumer-owned interface, then provide a fake repository or client with explicit results. This keeps unit tests deterministic without abstracting every internal type.
Run race, coverage, and fuzz checks
go test -race ./...
go test -cover ./...
go test -fuzz=FuzzParse ./...
Coverage finds unexecuted code; it does not measure assertion quality. Use it to discover gaps, not as the sole definition of correctness.
Benchmark after correctness
func BenchmarkParse(b *testing.B) {
for i := 0; i < b.N; i++ {
Parse(sample)
}
}
Run benchmarks in a stable environment and compare results statistically. Optimize only costs that matter in representative workloads.
A maintainable test suite is fast, deterministic, behavior-focused, and strongest at boundaries where mistakes are expensive.