Go Testing & Benchmark Skill
Table-driven test and fuzz scaffolding for agents - a cookbook-style Agent Skill for Go 1.26 test generation from acceptance criteria.
What This Skill Does
Produces test scaffolds: table-driven t.Run subtests, httptest handler cases, fuzz F.Add seeds, and benchmark stubs - with go test verification commands, not production code rewrites.
When to Invoke
- Agent asked to "add tests" without structure
- New package needs table-driven coverage from AC bullets
- Fuzz target needed for string/JSON parsers
- Benchmark stub before perf work (pairs with Go Performance Profiling Skill)
- PR lacks subtests for error branches
Inputs
| Input | Why |
|---|---|
| Acceptance criteria | Maps to test case names |
| Package under test | Import path, exported API |
| Error types / sentinels | errors.Is expectations |
| HTTP handlers? | httptest.NewRecorder scaffold |
| Concurrency in code? | Add -race to verification |
Outputs
*_test.goscaffold with table slice and subtests- Optional
FuzzXxxwith corpus seeds - Optional
BenchmarkXxxwithb.Loop(Go 1.24+ style) or classic loop - Commands:
go test ./pkg/...,go test -race,go test -fuzz=FuzzParse -fuzztime=30s
Guardrails
- Test behavior, not unexported helpers unless same-package white-box policy allows.
- No
time.Sleepfor synchronization - use channels,synctest, or interfaces. - Parallel subtests only when isolated -
t.Parallel()documented per case. - Fuzz only pure parsers - no network/DB in fuzz target without heavy setup.
- Run tests before delivering scaffold - must compile.
- Golden files - update with explicit
-updateflag pattern per team policy.
Recipe
Quick-reference recipe card - copy-paste ready.
func TestParseID(t *testing.T) {
tests := []struct {
name string
in string
want string
wantErr bool
}{
{name: "valid", in: "abc-123", want: "abc-123"},
{name: "empty", in: "", wantErr: true},
{name: "spaces", in: " ", wantErr: true},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := ParseID(tt.in)
if tt.wantErr {
if err == nil {
t.Fatal("expected error")
}
return
}
if err != nil {
t.Fatalf("unexpected: %v", err)
}
if got != tt.want {
t.Fatalf("got %q want %q", got, tt.want)
}
})
}
}go test ./internal/parse/... -count=1
go test -race ./internal/parse/... # if package uses goroutinesWhen to reach for this skill:
- PM AC list needs mapped subtests before merge
- Agent produced one giant
TestEverythingfunction - Parser package needs fuzz before security review
Working Example
Step 0 - Map AC to cases
AC:
- Valid UUID returns normalized form
- Invalid UUID returns ErrInvalid
- Empty input returns ErrEmptyEach bullet becomes t.Run name and table row.
Step 1 - Handler test with httptest
func TestGetUserHandler(t *testing.T) {
srv := httptest.NewServer(http.HandlerFunc(getUser))
t.Cleanup(srv.Close)
resp, err := http.Get(srv.URL + "/users/1")
if err != nil {
t.Fatal(err)
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
t.Fatalf("status %d", resp.StatusCode)
}
}- Prefer testing handler via
httptest.NewRecorderfor unit scope - See httptest for Handler Integration Tests
Step 2 - Fuzz scaffold
func FuzzParseID(f *testing.F) {
f.Add("abc-123")
f.Add("")
f.Fuzz(func(t *testing.T, in string) {
_, _ = ParseID(in) // must not panic; document invariants
})
}go test -fuzz=FuzzParseID -fuzztime=30s ./internal/parse/Step 3 - Benchmark stub
func BenchmarkParseID(b *testing.B) {
for b.Loop() {
_, _ = ParseID("abc-123")
}
}go test -bench=BenchmarkParseID -benchmem ./internal/parse/ -count=5Deep Dive
** testify / cmp**: if team uses github.com/stretchr/testify, skill imports require per ADR.
Otherwise stdlib-only assertions keep deps minimal.
Examples (ExampleXxx): for godoc-verified snippets - see Examples as Executable Documentation.
Coverage: go test -cover ./... for report; skill does not chase 100% on generated scaffold.
Build tags: integration tests behind //go:build integration - skill separates fast unit table from slow IT.
Gotchas
- Subtest name with
/breaks-runregex - use safe names. - Loop variable in parallel subtests - capture
tt := ttbeforet.Parallel(). - Fuzz on functions that mutate global state - isolate or skip fuzz.
- Benchmark without resetting input - skews alloc measurements.
Alternatives
| Approach | When |
|---|---|
| Manual TDD | Experienced author pair programming |
| Code coverage gates only | Misses edge cases |
| Property-based (rapid) | Complex invariants |
| This skill | Fast AC-to-subtest mapping with agents |
FAQs
Should the skill delete existing tests?
No. Extend with table cases or new files. Flag duplication instead of replacing human-written tests.
When to add testify?
Only when team ADR or sibling packages already use it. Default stdlib testing for new scaffolds.
Does skill write integration tests against real DB?
Unit scaffolds only unless inputs specify testcontainers ADR. Link to team IT doc instead.
Related
- Agent Skills Basics - SKILL.md structure
- Go Testing Basics - testing intro
- Table-Driven Tests and Subtests - patterns
- Fuzz Testing with testing.F - fuzz reference
- Benchmarks with testing.B and b.Loop - benchmarks
- testify, httptest, and Test Doubles - doubles
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).