Go cookbook · Beginner
Write table-driven tests with subtests
Tests for a function with many cases are long, repetitive and stop at the first failure.
Approach
List cases in a slice of structs, run each with t.Run (and t.Parallel() when independent), and report
differences clearly. Include edge cases and error cases.
Solution
package pricing
import (
"errors"
"testing"
)
var ErrInvalidQuantity = errors.New("invalid quantity")
func Total(unitCents, quantity int, coupon string) (int, error) {
if quantity < 1 {
return 0, ErrInvalidQuantity
}
total := unitCents * quantity
if coupon == "TEN" {
total -= total / 10
}
return total, nil
}
func TestTotal(t *testing.T) {
tests := []struct {
name string
unit int
quantity int
coupon string
want int
wantErr error
}{
{name: "single item", unit: 2400, quantity: 1, want: 2400},
{name: "several items", unit: 850, quantity: 3, want: 2550},
{name: "coupon takes ten percent", unit: 1000, quantity: 2, coupon: "TEN", want: 1800},
{name: "unknown coupon ignored", unit: 1000, quantity: 1, coupon: "NOPE", want: 1000},
{name: "zero quantity", unit: 1000, quantity: 0, wantErr: ErrInvalidQuantity},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
got, err := Total(tt.unit, tt.quantity, tt.coupon)
if !errors.Is(err, tt.wantErr) {
t.Fatalf("error = %v, want %v", err, tt.wantErr)
}
if got != tt.want {
t.Errorf("Total(%d, %d, %q) = %d, want %d", tt.unit, tt.quantity, tt.coupon, got, tt.want)
}
})
}
}
Pitfalls
- Test names that don't describe the case, making failures hard to read.
- Sharing mutable state between parallel subtests.
- Only testing happy paths.
Variations
- Run one case with
go test -run 'TestTotal/coupon'. github.com/google/go-cmp/cmp.Diffproduces readable diffs for structs.
Further reading: Subtests
Reference: https://pkg.go.dev/testing#hdr-Subtests_and_Sub_benchmarks
More Go recipes
- Benchmark code with testing.B.Loop
- Build a small, secure container image for a Go service
- Decode and validate JSON request bodies safely
- Embed static files and templates in the binary
- Load and validate configuration from environment variables
- Log structured JSON with slog and redact secrets
- Process jobs concurrently with a bounded worker pool
- Profile a running service with pprof