← GophersCONTENT HISTORYWHAT CHANGED · RULE-BASED ANALYSIS
Update to Gophers
Snapshot Sep 30, 2026 · 23:14 UTC · version 0.1.0
Collection source: not recorded for this historical snapshot.
First saved snapshot
No earlier snapshot is available to establish a change.
Compare saved observations
Download comparison JSONFull technical diff · 0 changed fields
Full snapshot data
{
"description": "Use when writing or fixing Go tests — table-driven cases, parallel safety, helpers, fakes, fuzzing, deterministic time (testing/synctest), goroutine leak detection (goleak), HTTP handlers. Apply proactively when a function gets a new test or a test is flaky. Benchmark methodology: see go-benchmark.",
"included_files": [
{
"relative_path": "agents/openai.yaml",
"size_in_bytes": 204
},
{
"relative_path": "references/assertions-and-helpers.md",
"size_in_bytes": 5018
},
{
"relative_path": "references/fuzz-synctest-bench.md",
"size_in_bytes": 6012
},
{
"relative_path": "references/goleak-and-flakes.md",
"size_in_bytes": 5032
},
{
"relative_path": "references/http-and-fakes.md",
"size_in_bytes": 5908
}
],
"name": "go-testing",
"skill_md_contents": "---\nname: go-testing\ndescription: \"Use when writing or fixing Go tests — table-driven cases, parallel safety, helpers, fakes, fuzzing, deterministic time (testing/synctest), goroutine leak detection (goleak), HTTP handlers. Apply proactively when a function gets a new test or a test is flaky. Benchmark methodology: see go-benchmark.\"\nlicense: MIT\ncompatibility: \"Designed for Claude Code or similar AI coding agents. Targets Go 1.21+. Uses Go 1.25+ testing/synctest and Go 1.24+ b.Loop() where relevant.\"\nallowed-tools: Read Edit Write Glob Grep Bash(go:*) Bash(golangci-lint:*)\n---\n\n# Go Testing\n\nTests are executable specifications. Their job is to **fail usefully** when behaviour regresses — and to keep failing in the same way until the bug is fixed. Tests that are passing-or-flaky teach the team to ignore them, which is worse than no test at all.\n\n## Core Rules\n\n1. **Failures must be diagnosable from the log alone.** Every `t.Errorf` includes the function under test, the inputs, what we got, and what we wanted, in that order.\n2. **No assertion libraries by default.** Use the standard `t.Errorf` / `t.Fatalf` plus `go-cmp` for structural comparison. `testify` is acceptable when adopted consistently — pick one and stick with it.\n3. **Test observable behaviour, not implementation details.** If a refactor that preserves behaviour breaks the test, the test was wrong.\n4. **Each test runs independently.** No execution-order dependencies, no shared global state without `t.Cleanup`.\n5. **`t.Parallel()` whenever the test is safe to run in parallel.** Most are.\n6. **`t.Helper()` is the first line of any helper function** that calls `t.Errorf`/`t.Fatalf`. Reserve `t.Fatal` for \"next line is meaningless without this value\"; everything else uses `t.Error`. Never call `t.Fatal`/`t.FailNow` from a non-test goroutine — send the failure back via channel.\n\n## \"Useful Failures\" Format\n\nThe failure message is the test's user interface. The canonical shape:\n\n```\nFunctionUnderTest(input) = got, want want\n```\n\n```go\n// Good\nt.Errorf(\"Add(2, 3) = %d, want %d\", got, 5)\n\n// Bad — no function, no inputs, reversed\nt.Errorf(\"expected %d but got %d\", 5, got)\n```\n\nAlways print **got before want**. With `cmp.Diff(want, got)`, the diff shows `(-want +got)` — echo that direction in your message.\n\n## \"No Asserts\" Philosophy\n\nStandard-library testing with `if` + `t.Errorf` reads as plain Go and produces messages you control. Assertion libraries shorten call sites but trade away message quality and reorder the `got`/`want` convention.\n\n- **Default** — `if got != want { t.Errorf(\"...\") }` plus `cmp.Diff` for structs, slices, maps, protos.\n- **Permitted** — `testify` if the team agrees and `testifylint` is enabled. `require` only when continuing is meaningless.\n- **Avoid** — mixing styles within the same package.\n\nFor protocol buffers, add `protocmp.Transform()` as a `cmp` option. Don't diff serialised JSON strings — decode and `cmp.Diff` instead.\n\n## t.Error vs t.Fatal\n\nUse `t.Error` by default; reserve `t.Fatal` for \"the next line is meaningless without this value\" (failed setup, failed decode before use). **Never** call `t.Fatal`/`t.FailNow` from a goroutine other than the test goroutine — it does not stop the test. Send the failure back via channel.\n\n> Read [references/assertions-and-helpers.md](references/assertions-and-helpers.md) when designing helpers, custom comparers, or migrating between stdlib testing and `testify`.\n\n## Table-Driven Tests\n\n```go\nfunc TestCalculatePrice(t *testing.T) {\n tests := []struct {\n name string\n quantity int\n unitPrice float64\n want float64\n }{\n {\"single item\", 1, 10.0, 10.0},\n {\"bulk discount\", 100, 10.0, 900.0},\n {\"zero quantity\", 0, 10.0, 0.0},\n }\n for _, tt := range tests {\n t.Run(tt.name, func(t *testing.T) {\n t.Parallel()\n got := CalculatePrice(tt.quantity, tt.unitPrice)\n if got != tt.want {\n t.Errorf(\"CalculatePrice(%d, %.2f) = %.2f, want %.2f\",\n tt.quantity, tt.unitPrice, got, tt.want)\n }\n })\n }\n}\n```\n\nEvery case has a `name` used in `t.Run`; failure messages include inputs, not the row index. When cases need different mocks or assertion shapes, stop using a table and write separate functions.\n\n## Helpers, Cleanup, Parallel\n\n```go\nfunc setupTestDB(t *testing.T) *sql.DB {\n t.Helper()\n db, err := sql.Open(\"sqlite3\", \":memory:\")\n if err != nil { t.Fatalf(\"open db: %v\", err) }\n t.Cleanup(func() { _ = db.Close() })\n return db\n}\n```\n\n`t.Helper()` is the first line of any helper that may fail; `t.Cleanup` runs after the test (and subtests) in LIFO order. Call `t.Parallel()` inside the subtest function. The `paralleltest` linter catches missing calls and the pre-1.22 loop-variable trap.\n\n## HTTP Handlers\n\nUse `httptest` with table-driven cases. See [references/http-and-fakes.md](references/http-and-fakes.md) for request/response body, header, and status assertions.\n\n## Goroutine Leaks: goleak\n\nWire `go.uber.org/goleak` into every package that spawns goroutines:\n\n```go\nimport \"go.uber.org/goleak\"\n\nfunc TestMain(m *testing.M) { goleak.VerifyTestMain(m) }\n```\n\nPer-test: `defer goleak.VerifyNone(t)`. Exclusions go to `goleak.IgnoreTopFunction(...)` — avoid `IgnoreAnyFunction`.\n\n## Deterministic Time: testing/synctest\n\nFor timer/context/deadline tests, `testing/synctest` (Go 1.25+) gives reproducible ordering. Synthetic time advances only when every goroutine in the bubble is blocked:\n\n```go\nsynctest.Test(t, func(t *testing.T) {\n ctx, cancel := context.WithTimeout(t.Context(), 5*time.Second)\n defer cancel()\n time.Sleep(5 * time.Second)\n synctest.Wait()\n if !errors.Is(ctx.Err(), context.DeadlineExceeded) {\n t.Fatalf(\"got %v, want DeadlineExceeded\", ctx.Err())\n }\n})\n```\n\nUse `synctest.Test` on Go 1.25+ and 1.26+. The Go 1.24 `GOEXPERIMENT=synctest` `synctest.Run` API is only for modules still on 1.24.\n\n## Fuzzing and Benchmarks\n\nNative fuzzing finds inputs you would never write by hand. Seed with `f.Add`, then `f.Fuzz`; check fuzzer-discovered corpora (`testdata/fuzz/...`) into git as regression tests.\n\nBenchmarks use `for b.Loop()` on Go 1.24+ (the legacy `b.N` loop only when targeting older versions). Sub-benchmarks across sizes use `b.Run(fmt.Sprintf(\"n=%d\", n), ...)`. For methodology, `benchstat`, and CI regression detection, see a dedicated `go-benchmark` skill — not this one.\n\n> Read [references/fuzz-synctest-bench.md](references/fuzz-synctest-bench.md) when wiring fuzz corpora into CI, designing `synctest`-based timer tests, or writing comparable benchmark suites with `b.Loop`.\n\n## Integration Tests\n\nSeparate by build tag so `go test ./...` stays fast:\n\n```go\n//go:build integration\n\npackage mypackage_test\n```\n\nRun with `go test -tags=integration ./...`. Integration tests own their fixtures (containers, schemas, fixtures) via `t.Cleanup`.\n\n> Read [references/http-and-fakes.md](references/http-and-fakes.md) when writing HTTP handler tests, mocking via consumer-owned interfaces, or stubbing time.\n\n## Anti-Patterns\n\n| Anti-pattern | Do this instead |\n|---|---|\n| `t.Errorf(\"got %d\", got)` without the function or wanted value | `FunctionUnderTest(input) = got, want want` |\n| Comparing error strings (`err.Error() == \"...\"`) | `errors.Is` / `errors.As` |\n| Calling `t.Fatal` from a spawned goroutine | Send via channel; main goroutine calls `t.Fatal` |\n| Tables of cases with conditional setup per row | Split into separate test functions |\n| Subtests without `t.Run(tt.name, ...)` | Always name and `t.Run` |\n| Mocking concrete types from another package | Define a small interface in the consumer; pass a fake |\n| `time.Sleep` to wait for \"the goroutine to do its thing\" | `synctest.Test` or explicit synchronisation |\n| Snapshot tests that compare serialised JSON | Decode then `cmp.Diff` |\n| Mutating `os.Args`/env/`flag.CommandLine` without `t.Cleanup` | Save and restore in `t.Cleanup` |\n\n## Verification Checklist\n\n- [ ] Every failure message includes function, inputs, got, and want, in that order\n- [ ] `cmp.Diff` calls use `(-want +got)` order and echo it in the message\n- [ ] Table-driven cases have `name` fields and use `t.Run`\n- [ ] Helpers call `t.Helper()` and use `t.Cleanup` for teardown\n- [ ] Parallel-safe tests call `t.Parallel()`; `paralleltest` is clean\n- [ ] Packages that spawn goroutines wire `goleak.VerifyTestMain` or per-test `VerifyNone`\n- [ ] Timer/deadline tests use `testing/synctest`, not `time.Sleep`\n- [ ] Integration tests are gated by a build tag and own their fixtures\n- [ ] `go test -race ./...` is clean; fuzz corpora are checked into `testdata/fuzz/...`\n\n## References\n\n- [references/assertions-and-helpers.md](references/assertions-and-helpers.md) — useful failures, `cmp.Diff`, helpers, `testify` interop\n- [references/http-and-fakes.md](references/http-and-fakes.md) — `httptest`, consumer-owned fakes, time stubs\n- [references/fuzz-synctest-bench.md](references/fuzz-synctest-bench.md) — fuzzing, `testing/synctest`, `b.Loop` benchmarks\n- [references/goleak-and-flakes.md](references/goleak-and-flakes.md) — leak detection, flake diagnosis, race triage\n"
}SHA-256 of public snapshot: bd26379c701ef258a97f9411e5df6f101f432f36c6fd56580140c792f54f1c46