Helpers, fixtures and golden files¶
Everything around the assertion: shared setup that reports failures usefully, temporary files that clean themselves up, and comparing against a recorded expected output.
func mustTempFile(t *testing.T, content string) string {
t.Helper()
p := filepath.Join(t.TempDir(), "f.txt")
if err := os.WriteFile(p, []byte(content), 0o644); err != nil {
t.Fatalf("writing temp file: %v", err)
}
return p
}
t.Helper fixes the reported line number¶
Without it, a failure inside a helper is reported at the line in the
helper, which is the same line for every caller and tells you nothing.
t.Helper() marks the function so the failure is attributed to the
caller instead.
Call it as the first statement, and in every helper — including ones that only call other helpers.
A helper should take *testing.T as its first parameter and fail with
t.Fatalf rather than returning an error. That is the one place the
usual "return errors" rule is inverted: a test helper failing means the
test cannot continue, and making every caller write if err != nil
buries the actual test.
t.TempDir¶
Returns a fresh directory and registers its removal automatically:
It is per-test — a subtest calling it gets its own, deleted when that subtest ends:
var saved string
t.Run("inner", func(t *testing.T) {
saved = t.TempDir() // exists here
})
// by now it is gone
That makes it safe with t.Parallel, where a shared directory would
have cases stepping on each other. Never use a fixed path like
/tmp/mytest; parallel runs and -count=2 will collide.
t.Cleanup runs last-in-first-out¶
t.Cleanup(func() { order = append(order, "first registered") })
t.Cleanup(func() { order = append(order, "second registered") })
// cleanup order: [second registered first registered]
Same ordering as defer, but it is attached to the test rather than
to the enclosing function — so a helper can register cleanup for work it
started, which a defer inside that helper could not.
It runs on failure and on panic too, which defer in the test body also
does. The reason to prefer t.Cleanup is the helper case and
compatibility with t.Parallel.
testdata/¶
The go tool ignores any directory named testdata, so fixture files
live there and are never compiled or treated as a package:
Paths in a test are relative to the package directory, because
go test runs each package's tests with that as the working directory.
So os.ReadFile("testdata/input.json") works regardless of where you
invoked go test from.
Golden files¶
When the expected output is large — rendered HTML, a formatted report, a JSON document — inlining it makes the test unreadable. Record it in a file and compare:
var update = flag.Bool("update", false, "rewrite golden files")
func assertGolden(t *testing.T, name, got string) {
t.Helper()
p := filepath.Join("testdata", name+".golden")
if *update {
if err := os.WriteFile(p, []byte(got), 0o644); err != nil {
t.Fatalf("updating golden: %v", err)
}
return
}
want, err := os.ReadFile(p)
if err != nil {
t.Fatalf("reading golden (run with -update to create): %v", err)
}
if got != string(want) {
t.Errorf("golden mismatch for %s\n got: %q\nwant: %q", name, got, string(want))
}
}
Regenerate after an intentional change:
Then read the diff before committing it. That is the whole risk of
golden files: -update makes any change pass, so an unreviewed
regeneration silently blesses a bug. The files must be in version
control and the diff must be part of review.
A flag.Bool at package level in a test file is how you add a flag to
go test; the testing package parses it along with its own.
Two more rules. Golden files must be deterministic — no timestamps, no map iteration order, no random ids. Normalise those before comparing or you get a test that fails on Tuesdays. And keep them readable: a golden diff is only useful if a human can see what changed.
When a plain comparison is not enough¶
== works for strings and comparable structs. For maps and slices, use
slices.Equal and maps.Equal; reflect.DeepEqual also works but
treats a nil slice and an empty one as different, which is rarely what
a test means — the point made in
XML, CSV and reflection.
For JSON, compare the decoded values rather than the text. Key order and whitespace are not part of what you are testing.
From Python:
t.TempDiris thetmp_pathfixture andt.Cleanupisaddfinalizer, but there is no fixture injection — helpers are ordinary functions you call.t.Helperis the equivalent of__tracebackhide__. Golden files arepytest-regressions, hand-rolled in fifteen lines.
Quick reference¶
| Task | Form |
|---|---|
| shared setup | a helper taking *testing.T first |
| correct failure line | t.Helper() as the first statement |
| a scratch directory | t.TempDir() — per test, auto-removed |
| teardown | t.Cleanup(fn) — LIFO, survives helpers and t.Parallel |
| fixture files | testdata/, ignored by the go tool |
| working directory | the package directory, always |
| large expected output | a .golden file under testdata/ |
| regenerate | a package-level flag.Bool("update", ...) |
| the danger | review the golden diff; -update makes anything pass |
| compare collections | slices.Equal / maps.Equal |