go-testing — Go testing
Deterministic backstop: go test -race ./... (always, in CI), go test -bench, go test -fuzz.
Rules
- Table-driven tests: a named-case slice +
t.Run(tc.name, func(t *testing.T){ … }). Since Go 1.22 thetc := tccopy is unnecessary — drop it (modernize/copyloopvarflag it). t.Parallel()on independent tests to cut wall-clock; watch for shared mutable state and loop-var capture in the parallel body.- Process-global helpers are incompatible with
t.Parallel()—t.Setenv(Go 1.17),t.Chdir(1.24), andcryptotest.SetGlobalRandom(1.26) all mutate process state, so they fail in a parallel test or one with a parallel ancestor. A table whose cases need env or cwd stays serial; pass config explicitly instead where possible. Theusetestinglinter pushesos.Setenv/os.Chdirin tests towards thet.*forms (which restore state viaCleanup). t.Context()(Go 1.24) for any test needing actx— it is cancelled just before the test'sCleanupfunctions run, so goroutines under test shut down before teardown asserts on them. Use it overcontext.Background(); thetestingcontextmodernizer rewrites the old form. Do not use it for a fixture whose lifetime spans tests (a shared server or container started inTestMain) — that needs its own context.t.TempDir()for scratch,t.ArtifactDir()(Go 1.26) for evidence.TempDiris removed at test end;ArtifactDirgives each test a unique directory for output files worth keeping — rendered output, protocol dumps, failure snapshots — retained whengo test -artifactsis passed. Don't hand-roll paths underos.TempDir().t.Output()(Go 1.25) is anio.Writerinto the test log — wire asloghandler or a subprocess's stdout into it so output interleaves correctly witht.Logunder-raceand parallel tests, instead offmt.Printlnescaping to raw stdout.t.Attr(1.25) emits structured key/value metadata intogo test -jsonoutput.- Benchmarks:
for b.Loop() { … }(Go 1.24) — it handles timer reset and run scaling; replacesfor i := 0; i < b.N; i++plus manualb.ResetTimer(). -raceis non-negotiable for any code touching goroutines; wire it into CI.- Goroutine-leak detection:
go.uber.org/goleak—goleak.VerifyTestMain(m)or per-testdefer goleak.VerifyNone(t). testing/synctest(stable since Go 1.25) is the default for time/concurrency tests — timeouts, tickers, retries,contextcancellation. It runs the bubble on a fake clock with deterministic scheduling, so "5-second" waits complete in microseconds and flakiness disappears. Wrap withsynctest.Test(t, func(t *testing.T){ … });synctest.Wait()blocks until every goroutine in the bubble is durably blocked. Reach for it instead oftime.Sleep-based polling. (Alwayssynctest.Test— the pre-stablesynctest.Runno longer exists.) Go 1.27 (released 2026-08-19, https://go.dev/dl/) addssynctest.Sleep(time.Sleep+Waitin one) andhttptest.NewTestServer(t, handler)— signaturefunc NewTestServer(t testing.TB, handler http.Handler) *Server, note thetesting.TBfirst argument thathttptest.NewServerdoes not take — an in-memory server usable inside a bubble. Sources: https://go.dev/doc/go1.27, https://pkg.go.dev/net/http/httptest#NewTestServer.- Fuzzing (
func FuzzX(f *testing.F)) for parsers, codecs, and anything consuming untrusted bytes. Golden files (an-updateflag writingtestdata/*.golden) for large structured output. A golden pins shape, not behaviour — when it records something another system executes (SQL, wire requests, rendered configs), pair it with at least one test that executes the artefact for real; a snapshot can be stable and wrong. (Go 1.27) Never assert on compressed bytes verbatim —compress/flate's encoder changed, sogzip/zip/zlib/PNG output differs byte-for-byte from 1.26 even though decompression is unaffected; compare decompressed content or a stable digest of it instead. Source: https://go.dev/doc/go1.27. - Deterministic crypto tests (Go 1.26):
testing/cryptotest.SetGlobalRandom(t, seed)pins a deterministic randomness source for the test's duration — reach for it instead of hand-injecting a customio.Readerwhen testing code that draws fromcrypto/rand. It's process-global, so it can't run inside at.Parallel()test (or one with a parallel ancestor). - Failure messages must diagnose without a debugger: name the call, the input, the result, and
the expectation —
t.Errorf("Parse(%q) = %v, want %v", in, got, want)— never a baret.Error("failed"). For structs and slices print a diff (cmp.Diff(want, got)), not two blobs. - Example functions are runnable documentation.
func ExampleParse()in a_test.gofile shows ingo docand on pkg.go.dev, andgo testcompiles it; end it with a// Output:comment andgo testalso runs it and compares stdout, so the example cannot rot. Name themExampleT,ExampleT_Method,ExampleF_suffix(lowercase suffix) —go vet(tests) rejects a malformed name. Style Decisions asks for one where feasible — for the entry points a reader meets first — as advice, not a per-export rule. - Name the fields in table-case literals when a case spans many lines, when adjacent fields share
a type, or when zero-value fields are left out —
{input: "a,b", sep: ",", want: 2}reads on its own;{"a,b", ",", 2}has to be decoded against the struct. - Compare stable results. Output whose exact bytes belong to a package the repo does not own —
json.Marshal, a formatted string, map iteration order — can change under a dependency bump. Parse it back and compare values; sort map-derived slices first (slices.Sorted(maps.Keys(m))); compare structs withcmp.Diff, notreflect.DeepEqualon their text form. - Helpers set up; the test body asserts. Call
t.Helper()so a failure points at the caller's line, and prefer a helper that returns a value orerrorover one that fails internally — assertion logic belongs where the case's context is visible.t.Fatalin a setup helper is fine; in a goroutine uset.Error(only the test's own goroutine may callFatal). Stdlib plus small helpers is usually enough;testifyis fine — match the repo, don't mix styles.
Sources
- synctest — https://go.dev/blog/synctest;
testing.B.Loop— https://go.dev/blog/testing-b-loop testingpackage (T.Context,T.Chdir,T.Output,T.Attr,T.ArtifactDir) — https://pkg.go.dev/testing; Go 1.22/1.24/1.25/1.26 release notes — https://go.dev/doc/go1.26- Code Review Comments (Useful Test Failures) — https://go.dev/wiki/CodeReviewComments; Google Go Style Decisions (Examples, Compare stable results, Useful test failures) — https://google.github.io/styleguide/go/decisions; Best Practices (Tests, Use field names in struct literals,
t.Errorvst.Fatal) — https://google.github.io/styleguide/go/best-practices - Example functions — https://go.dev/blog/examples;
go vettestsanalyzer — https://pkg.go.dev/cmd/vet testing/cryptotest(Go 1.26) — https://pkg.go.dev/testing/cryptotestgo.uber.org/goleak— https://pkg.go.dev/go.uber.org/goleak
Decomposition inspired by samber/cc-skills-golang (MIT © 2026 Samuel Berthe); rules grounded in the sources above.