Go Testing Rules
Table-driven tests, subtests, testify, integration tests, coverage.
# CLAUDE.md — Go Testing Rules
## Layout
- Test files live next to the code: `foo.go` + `foo_test.go`.
- One package per directory. Use `package foo_test` (external test) when you only want to test the public API.
- Helpers live in `*_test.go` files; they're not exported to consumers.
## Table-driven tests
```go
func TestParse(t *testing.T) {
cases := []struct {
name string
input string
want Result
wantErr bool
}{
{"empty", "", Result{}, true},
{"simple", "a=1", Result{A: 1}, false},
}
for _, tc := range cases {
t.Run(tc.name, func(t *testing.T) {
got, err := Parse(tc.input)
if (err != nil) != tc.wantErr {
t.Fatalf("err = %v, wantErr %v", err, tc.wantErr)
}
if got != tc.want {
t.Errorf("got %v, want %v", got, tc.want)
}
})
}
}
```
- Subtests via `t.Run` give clean names and let you target one (`go test -run TestParse/simple`).
- `t.Fatalf` for setup failures, `t.Errorf` for assertions where you can keep going.
## Assertions
- Standard library by default. Reach for **testify** (`require`, `assert`) when boilerplate dominates.
- `require.NoError(t, err)` instead of `if err != nil { t.Fatal(err) }` is fine — it's the only `if` you write 100 times.
- Avoid `reflect.DeepEqual` for floats — use a tolerance.
## Test helpers
- Mark helpers with `t.Helper()` so failure lines point to the caller, not the helper.
- Helpers take `*testing.T` first. Cleanup with `t.Cleanup(...)` — runs even if the test fails.
## Integration tests
- Build tag for integration tests: `//go:build integration` at the top of the file. Run with `go test -tags=integration ./...`.
- Spin up real Postgres / Redis with **testcontainers-go** or a local docker-compose.
- Reset state between tests — truncate tables in `t.Cleanup`.
## Concurrency tests
- `-race` in CI. Always. Don't merge without it.
- For ordering, use `testing/synctest` (Go 1.24+) or carefully designed channels — not `time.Sleep`.
## Coverage
- `go test -cover`. Aim for high coverage on services and parsing, not for 100%.
- `-coverprofile=cover.out` + `go tool cover -html=cover.out` to find untested branches.
## Benchmarks
- `func BenchmarkX(b *testing.B)` next to the function.
- Use `b.ReportAllocs()` to surface allocations.
- Compare with `benchstat` after a change. A single run is meaningless.
## Don't
- Don't use `time.Sleep` to "wait for" something async. Use channels, `sync.WaitGroup`, or polling with a deadline.
- Don't share mutable state across `t.Parallel()` tests.
- Don't disable tests with `t.Skip` and leave them. Either fix them or delete them.
- Don't rely on the order of map iteration. It's randomized.
Other Go templates
Idiomatic Go Rules
Effective Go: error handling, interfaces, goroutines, project layout.
Go HTTP Server (net/http + chi)
Production HTTP services with chi router, middleware, and graceful shutdown.
Go + sqlx + Postgres
Database access with sqlx, pgx, and migration discipline.
Go gRPC Services
gRPC + protobuf: service definitions, interceptors, streaming, errors.