Testing Best Practices
Test pyramid guidance for Go services and libraries.
Search across all documentation pages
Test pyramid guidance for Go services and libraries.
These rules keep Go tests fast, readable, and trustworthy from local go test through CI merge gates.
go test -race ./... and golangci-lint on the same packages you gate in CI.t.Run.*_test.go. Same module, same refactor, same compile errors when APIs move.t.Helper() on shared assertion helpers. Failures point at the test line, not utility internals.package foo_test when modeling consumers. Internal tests stay in package foo for unexported helpers.go test ./... in seconds to low minutes on laptops. Split integration suites with build tags or separate modules when needed.time.Sleep for synchronization. Use channels, sync.WaitGroup, or controllable httptest handlers.go test -count=1 when debugging flakes. Cached passes hide ordering bugs until CI reruns.b.Loop(). Measure the hot path, not fixture construction.httptest.NewRecorder for handler unit tests. Reserve NewServer when the client stack must run for real.resp.Body after every client call in tests. Leaks fail under -count=100 and load-like CI.r.Context() through handlers in tests. Cancellation and deadline middleware only work when handlers respect context.go test stays offline.-race on packages that spawn goroutines or share caches. Accept the CPU cost on Linux CI agents.testdata/. Treat diffs as regression fixtures requiring human review.-fuzztime) nightly or weekly. Replay corpora on every PR without open-ended fuzz in merge pipelines.Example functions for copy-paste public API usage. Keep // Output: comments accurate so godoc stays verified.go test fails when docs drift.errors.Is / errors.As in error table rows. Wrapped errors break == comparisons.require vs assert policy per team. require for preconditions; assert for independent checks.pprof on realistic workloads after benchstat shows a win.Enough to prove wiring (DB, auth, one HTTP path).
Most cases stay in fast unit and handler tests.
Optional on core packages.
Never substitute percent for meaningful assertions.
When large struct comparisons and JSONEq improve failure readability.
Stdlib remains fine for small packages.
Extract logic into testable functions or use os/exec to run a built binary with testdata args.
Usually no - use nightly benchstat unless the change is explicitly performance-critical.
//go:build integration on slow tests keeps default go test fast.
Document the tag in README and CI.
Inject io.Writer or use log/slog with a bytes.Buffer handler.
Avoid golden logs with timestamps.
Only when subtests do not share mutable package state.
Race detector catches mistakes.
Tables and fakes work the same - instantiate types in rows or use typed helpers.
Keep them in a separate pipeline or nightly job.
Unit and handler tests remain the merge gate.
Stack versions: This page was written for Go 1.26.x (Green Tea GC default, go fix modernizers - verify patch at build), chi (latest - verify at build), gin (latest - verify at build), echo (latest - verify at build), google.golang.org/grpc (latest - verify at build), sigs.k8s.io/controller-runtime (latest - verify at build), kubebuilder (latest - verify at build), tinygo (latest - verify board targets at build), wazero (latest - verify at build), and golangci-lint (latest - verify linter set at build).
Reviewed by Chris St. John·Last updated Jul 18, 2026