agentsclimarketplace

Go testing

Skill muratmirgun/gophers/skills/go-testing

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.From its SKILL.md

Install
npx -y skills add muratmirgun/gophers --skill go-testing

Assembled from the repository path, not quoted from the project. Check it against their README if it does not work.

One thing to look at

  • 8 stars8 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.

What its file declares

Copied from the file, not written here

The file declares its own license as MIT. That is the author’s claim about this one file, and it is not the same thing as the license GitHub reports for the repository, which is listed with the other numbers below.

SKILL.md

9.3 KB, ~2.3k tokens by cl100k_base, as published. Nobody here has run it

Go Testing

Tests 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.

Core Rules

  1. 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.
  2. 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.
  3. Test observable behaviour, not implementation details. If a refactor that preserves behaviour breaks the test, the test was wrong.
  4. Each test runs independently. No execution-order dependencies, no shared global state without t.Cleanup.
  5. t.Parallel() whenever the test is safe to run in parallel. Most are.
  6. 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.

"Useful Failures" Format

The failure message is the test's user interface. The canonical shape:

FunctionUnderTest(input) = got, want want
// Good
t.Errorf("Add(2, 3) = %d, want %d", got, 5)

// Bad — no function, no inputs, reversed
t.Errorf("expected %d but got %d", 5, got)

Always print got before want. With cmp.Diff(want, got), the diff shows (-want +got) — echo that direction in your message.

"No Asserts" Philosophy

Standard-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.

  • Defaultif got != want { t.Errorf("...") } plus cmp.Diff for structs, slices, maps, protos.
  • Permittedtestify if the team agrees and testifylint is enabled. require only when continuing is meaningless.
  • Avoid — mixing styles within the same package.

For protocol buffers, add protocmp.Transform() as a cmp option. Don't diff serialised JSON strings — decode and cmp.Diff instead.

t.Error vs t.Fatal

Use 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.

Read references/assertions-and-helpers.md when designing helpers, custom comparers, or migrating between stdlib testing and testify.

Table-Driven Tests

func TestCalculatePrice(t *testing.T) {
    tests := []struct {
        name      string
        quantity  int
        unitPrice float64
        want      float64
    }{
        {"single item",   1,   10.0, 10.0},
        {"bulk discount", 100, 10.0, 900.0},
        {"zero quantity", 0,   10.0, 0.0},
    }
    for _, tt := range tests {
        t.Run(tt.name, func(t *testing.T) {
            t.Parallel()
            got := CalculatePrice(tt.quantity, tt.unitPrice)
            if got != tt.want {
                t.Errorf("CalculatePrice(%d, %.2f) = %.2f, want %.2f",
                    tt.quantity, tt.unitPrice, got, tt.want)
            }
        })
    }
}

Every 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.

Helpers, Cleanup, Parallel

func setupTestDB(t *testing.T) *sql.DB {
    t.Helper()
    db, err := sql.Open("sqlite3", ":memory:")
    if err != nil { t.Fatalf("open db: %v", err) }
    t.Cleanup(func() { _ = db.Close() })
    return db
}

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.

HTTP Handlers

Use httptest with table-driven cases. See references/http-and-fakes.md for request/response body, header, and status assertions.

Goroutine Leaks: goleak

Wire go.uber.org/goleak into every package that spawns goroutines:

import "go.uber.org/goleak"

func TestMain(m *testing.M) { goleak.VerifyTestMain(m) }

Per-test: defer goleak.VerifyNone(t). Exclusions go to goleak.IgnoreTopFunction(...) — avoid IgnoreAnyFunction.

Deterministic Time: testing/synctest

For timer/context/deadline tests, testing/synctest (Go 1.25+) gives reproducible ordering. Synthetic time advances only when every goroutine in the bubble is blocked:

synctest.Test(t, func(t *testing.T) {
    ctx, cancel := context.WithTimeout(t.Context(), 5*time.Second)
    defer cancel()
    time.Sleep(5 * time.Second)
    synctest.Wait()
    if !errors.Is(ctx.Err(), context.DeadlineExceeded) {
        t.Fatalf("got %v, want DeadlineExceeded", ctx.Err())
    }
})

Use 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.

Fuzzing and Benchmarks

Native 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.

Benchmarks 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.

Read references/fuzz-synctest-bench.md when wiring fuzz corpora into CI, designing synctest-based timer tests, or writing comparable benchmark suites with b.Loop.

Integration Tests

Separate by build tag so go test ./... stays fast:

//go:build integration

package mypackage_test

Run with go test -tags=integration ./.... Integration tests own their fixtures (containers, schemas, fixtures) via t.Cleanup.

Read references/http-and-fakes.md when writing HTTP handler tests, mocking via consumer-owned interfaces, or stubbing time.

Anti-Patterns

Anti-patternDo this instead
t.Errorf("got %d", got) without the function or wanted valueFunctionUnderTest(input) = got, want want
Comparing error strings (err.Error() == "...")errors.Is / errors.As
Calling t.Fatal from a spawned goroutineSend via channel; main goroutine calls t.Fatal
Tables of cases with conditional setup per rowSplit into separate test functions
Subtests without t.Run(tt.name, ...)Always name and t.Run
Mocking concrete types from another packageDefine a small interface in the consumer; pass a fake
time.Sleep to wait for "the goroutine to do its thing"synctest.Test or explicit synchronisation
Snapshot tests that compare serialised JSONDecode then cmp.Diff
Mutating os.Args/env/flag.CommandLine without t.CleanupSave and restore in t.Cleanup

Verification Checklist

  • Every failure message includes function, inputs, got, and want, in that order
  • cmp.Diff calls use (-want +got) order and echo it in the message
  • Table-driven cases have name fields and use t.Run
  • Helpers call t.Helper() and use t.Cleanup for teardown
  • Parallel-safe tests call t.Parallel(); paralleltest is clean
  • Packages that spawn goroutines wire goleak.VerifyTestMain or per-test VerifyNone
  • Timer/deadline tests use testing/synctest, not time.Sleep
  • Integration tests are gated by a build tag and own their fixtures
  • go test -race ./... is clean; fuzz corpora are checked into testdata/fuzz/...

References

What ships with it: 4 files

21.5 KB alongside SKILL.md

Keep looking

Skills are one crate of 326,861. Ordering is by how many stacks a row turns up in, so the top of any crate is what has actually been picked rather than what has the most stars.