Coverage, Race Detector & Golden Files
Cover profiles, -race in CI, and snapshot testing.
Summary
Coverage tells you what code ran; the race detector tells you if concurrent access was unsafe; golden files snapshot expected output for regressions.
Together they strengthen CI when paired with meaningful assertions, not as vanity metrics alone.
Recipe
Quick-reference recipe card - copy-paste ready.
go test -race -coverprofile=coverage.out ./...
go tool cover -func=coverage.out
go test -update ./... # pattern: custom flag updates golden filesfunc TestRenderGolden(t *testing.T) {
got := Render()
want, err := os.ReadFile("testdata/render.out")
if err != nil {
t.Fatal(err)
}
if diff := cmp.Diff(string(want), got); diff != "" {
t.Fatalf("diff:\n%s", diff)
}
}When to reach for this:
- CI needs a merge gate on test plus race plus coverage trend
- Renderer or CLI output should not change accidentally
- Concurrent code uses shared maps, caches, or lazy singletons
- Refactors must prove behavior unchanged via golden diffs
- Operators want HTML coverage maps for untested error branches
Working Example
package report
import (
"flag"
"os"
"path/filepath"
"strconv"
"sync"
"testing"
)
var updateGolden = flag.Bool("update", false, "update golden files")
var cache = struct {
mu sync.Mutex
m map[string]int
}{m: make(map[string]int)}
func Inc(key string) {
cache.mu.Lock()
cache.m[key]++
cache.mu.Unlock()
}
func FormatReport(keys []string) string {
out := ""
for _, k := range keys {
cache.mu.Lock()
n := cache.m[k]
cache.mu.Unlock()
out += k + ":" + strconv.Itoa(n) + "\n"
}
return out
}
func TestIncRaceSafe(t *testing.T) {
var wg sync.WaitGroup
for i := 0; i < 100; i++ {
wg.Add(1)
go func() {
defer wg.Done()
Inc("x")
}()
}
wg.Wait()
}
func TestFormatReportGolden(t *testing.T) {
Inc("a")
Inc("a")
Inc("b")
got := FormatReport([]string{"a", "b"})
path := filepath.Join("testdata", "report.golden")
if *updateGolden {
if err := os.WriteFile(path, []byte(got), 0644); err != nil {
t.Fatal(err)
}
}
want, err := os.ReadFile(path)
if err != nil {
t.Fatal(err)
}
if string(want) != got {
t.Fatalf("golden mismatch\n--- want\n%s--- got\n%s", want, got)
}
}What this demonstrates:
go test -raceexercisesTestIncRaceSafewith instrumentation- Golden file under
testdata/captures formatted output -updateflag rewrites goldens intentionally after review- Mutex usage keeps race detector quiet on shared
cache
Deep Dive
How It Works
- Coverage records statement execution counts per run via
-coverprofile. go tool cover -htmlcolors lines green/red for manual gap analysis.- Race detector instruments memory accesses; cost is roughly 2-10x CPU.
- Golden tests compare output bytes; diffs highlight unintended renderer changes.
Coverage modes
| Flag | Effect |
|---|---|
-cover | Summary percent per package |
-coverprofile=file | Write profile for go tool cover |
-covermode=atomic | Accurate counts under parallelism |
-coverpkg=./... | Include non-test packages in profile |
Race detector in CI
go test -race -timeout=10m ./...Run on Linux CI agents; macOS/Windows support exists but Linux is the common default.
Go Notes
import "github.com/google/go-cmp/cmp"
if diff := cmp.Diff(want, got); diff != "" {
t.Fatalf("mismatch (-want +got):\n%s", diff)
}go-cmp produces readable golden diffs for structs and JSON.
Gotchas
- Chasing 100% coverage - tests assert nothing meaningful. Fix: Gate on critical packages; require assertions on error paths.
- Skipping -race because CI is slow - races ship to production. Fix: Race at least packages with goroutines; shard tests.
- Committing goldens without review -
-updatehides bugs. Fix: Treat golden diffs like code review material. - Nondeterministic goldens - timestamps and map order flake. Fix: Normalize output; sort keys; fake clocks.
- Golden files outside testdata - pollutes package root. Fix: Use
testdata/by convention. - Coverage without -covermode=atomic in parallel tests - inaccurate counts. Fix: Use atomic mode when tests use
t.Parallel().
Alternatives
| Alternative | Use When | Don't Use When |
|---|---|---|
| Structured assertions only | Small stable JSON APIs | Large CLI transcripts |
httptest body string compare | HTTP snapshots | Binary output |
| Mutation testing (go-mutesting) | Test quality audit | Everyday CI cost |
| Production tracing | Real concurrency bugs | Pre-merge unit signal |
FAQs
What is a good coverage target?
No universal percent - track trend on core packages.
Require tests for new exported APIs and error branches.
Does -race catch all concurrency bugs?
No - it catches data races, not deadlocks or logic bugs.
Combine with stress tests and timeouts.
How do I exclude generated code from coverage?
Use build tags on generated files or filter profiles in CI scripts.
Do not skip testing wrappers that contain logic.
Can I merge coverage from shards?
Yes - go tool covdata (Go 1.20+) merges profiles from parallel CI jobs.
Where do golden files live?
testdata/ is conventional and excluded from normal binaries.
Subdirs per test are fine.
How do I update goldens in CI?
Do not auto-update in CI - fail and let developers run locally with -update.
Prevents silent acceptance of bad output.
Does race work with cgo?
Mostly yes with caveats.
Pure Go race coverage is more predictable; test cgo packages explicitly.
How do fuzz corpora relate to goldens?
Both live under testdata/.
Fuzz files reproduce crashes; goldens reproduce expected output.
What timeout for race CI?
Increase from default when integration tests spawn many goroutines.
-timeout=10m is a common starting point.
Can I see uncovered lines in VS Code?
Use go tool cover -html or gopls coverage integration when enabled.
HTML report is the portable baseline.
Should handlers use goldens or JSONEq?
Small JSON responses - decode and assert fields.
Large HTML templates - golden files with reviewed diffs.
How does this tie to linting-quality CI?
See ci-quality-gates-lint-test-race-vuln for ordering race, vuln, and lint with tests.
Related
- Go Testing Basics -
-racesmoke intro - Fuzz Testing with testing.F - corpus under testdata
- Table-Driven Tests & Subtests - cases before snapshots
- testify, httptest & Test Doubles - JSON assertions vs goldens
- CI Quality Gates: Lint, Test, Race, Vuln - pipeline template
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).