Skip to main content
skill CX Codex

go-test-quality — Claude Code skill for Go testing

Get guidance on Go test design, coverage checks, and concurrency tests. Use it when adding tests, reviewing existing ones, or investigating flaky behavior.

Install into Claude Code · Skills CLI (Vercel) All methods and platforms ↓
npx skills add eduardo-sl/go-agent-skills --skill go-test-quality -a claude-code
Open on GitHub

What go-test-quality does

The skill focuses on checking observable behavior and organizing scenarios with t.Run. It covers test helpers, fixtures, golden files, and choosing among httptest, testcontainers, and fuzz tests. For small interfaces, it recommends handwritten mocks; pure functions and standard-library code should be tested directly.

Coverage guidance includes go test -race -coverprofile=coverage.out -coverpkg=./... ./... and viewing the report with go tool cover -html=coverage.out. It also discusses parallel tests and testing/synctest. Docker is required for container-based integration tests. Time-dependent code using synctest requires Go 1.25+.

What it helps with

  • choose a test approach for an HTTP handler or database
  • organize test scenarios with t.Run
  • create test helpers and clean up resources with t.Cleanup
  • locate untested code in a Go coverage report
  • investigate flaky tests involving concurrent code

Example prompts for the agent

  • Add tests for this Go code and check its behavior rather than its internal calls.
  • Review this package's coverage and suggest cases for branches that lack tests.
  • Investigate this flaky goroutine test and suggest synchronization without time.Sleep.

How to install go-test-quality

1Skills CLI (Vercel)

npx skills add eduardo-sl/go-agent-skills --skill go-test-quality -a claude-code

2Manually: put the files in place

~/.claude/skills/go-test-quality/SKILL.md

Copy the whole skill folder; for one project use .claude/skills/go-test-quality/

Step by step, with commands for each platform: How to install skills in Claude Code: five ways

Questions about go-test-quality

How do I install go-test-quality in Claude Code?

Run npx skills add eduardo-sl/go-agent-skills --skill go-test-quality -a claude-code. The skill file is available at ~/.claude/skills/go-test-quality/SKILL.md.

What does go-test-quality do?

It guides Go test design, including scenario structure, helpers, mocks, coverage analysis, and tests for concurrent code.

What is required for testcontainers integration tests?

Docker is required for container-based integration tests. Using testing/synctest with time-dependent code requires Go 1.25+.

Source text

SKILL.md · MIT
---
name: go-test-quality
description: >
  Go testing patterns for production-grade code: subtests, test helpers,
  fixtures, golden files, httptest, testcontainers, fuzz testing, and
  testing/synctest. Covers mocking strategies, test isolation, coverage
  analysis, and test design philosophy. Use when writing tests, improving
  coverage, reviewing test quality, setting up test infrastructure, or
  choosing a testing approach. Trigger examples: "add tests", "improve
  coverage", "write tests for this", "test helpers", "mock this dependency",
  "integration test", "fuzz test", "flaky test", "synctest", "goroutine leak
  in tests".
  Not for: benchmarking methodology (go-performance-review), security
  testing (go-security-audit), table-driven patterns (go-test-table-driven).
user-invocable: true
license: MIT
compatibility: Designed for Claude Code or similar AI coding agents working on Go projects. Requires the Go toolchain. Container-based integration tests require Docker.
allowed-tools: Read Edit Write Glob Grep Bash(go:*) Bash(gofmt:*)
metadata:
  author: eduardo-sl
  version: "1.5.1"
---

# Go Test Quality

Tests are production code. They run in CI on every commit, they document behavior,
and they're the first thing you read when a function breaks at 3am.
Write them with the same care you'd give to code that handles money.

> Community default. A project skill or agent-config rule that explicitly
> supersedes `go-test-quality` takes precedence over this one.

Detailed reference material, loaded on demand:

- `references/helpers-and-fixtures.md` — test helpers, factory functions
  with options, `t.Cleanup`, golden files, mock implementations.
- `references/integration-testing.md` — httptest recorder and server,
  testcontainers, build tags, `TestMain`, fuzz testing.
- `references/concurrency-and-benchmarks.md` — `synctest`, `goleak`,
  `t.Context()`, `b.Loop()` benchmarks, `t.Attr` and artifacts.

Read a reference file only when the summary below is not enough.

## 1. Test Design Philosophy

### Test behavior, not implementation

```go
// ✅ Good — tests what the function DOES
func TestTransferFunds_InsufficientBalance(t *testing.T) {
    from := NewAccount("alice", 100)
    to := NewAccount("bob", 0)

    err := TransferFunds(from, to, 150)

    require.ErrorIs(t, err, ErrInsufficientFunds)
    assert.Equal(t, 100, from.Balance(), "sender balance should be unchanged")
    assert.Equal(t, 0, to.Balance(), "receiver balance should be unchanged")
}

// ❌ Bad — tests HOW the function does it
// asserts debit() was called before credit(), rollback() was called,
// internal mutex was locked — breaks on every refactor
```

### One assertion per logical concept

Multiple `assert` calls are fine when they verify different facets of the
SAME behavior (both accounts after a transfer). A test that checks creation
AND update AND deletion is three tests pretending to be one.

### Name tests like bug reports

When the test fails, the name alone should say what broke:

```go
// ✅ Good — reads like a sentence
func TestOrderService_Cancel_RefundsPartiallyShippedItems(t *testing.T) { ... }
func TestParseConfig_ReturnsErrorOnMissingRequiredField(t *testing.T) { ... }

// ❌ Bad — says nothing useful
func TestCancel(t *testing.T) { ... }
func TestRateLimiter_Success(t *testing.T) { ... }
```

### Layout

- One `_test.go` per source file (`parser.go` → `parser_test.go`), tests
  in the order of the functions they cover — tools and reviewers navigate
  by file.
- Public-API tests live in the `pkg_test` package so they can only use
  what callers can; white-box tests stay in `pkg`.
- Integration tests sit behind `//go:build integration`.

## 2. Subtests for Organized Scenarios

Use `t.Run` to group related scenarios under a parent test. Each subtest
gets its own setup, its own failure, and its own name in CI output:

```go
func TestUserService_Create(t *testing.T) {
    svc := setupUserService(t)

    t.Run("succeeds with valid input", func(t *testing.T) {
        user, err := svc.Create(ctx, CreateUserInput{Name: "Alice", Email: "[email protected]"})
        require.NoError(t, err)
        assert.NotEmpty(t, user.ID)
    })

    t.Run("rejects duplicate email", func(t *testing.T) {
        _, _ = svc.Create(ctx, CreateUserInput{Name: "Alice", Email: "[email protected]"})
        _, err := svc.Create(ctx, CreateUserInput{Name: "Bob", Email: "[email protected]"})
        require.ErrorIs(t, err, ErrDuplicateEmail)
    })
}
```

## 3. Test Helper Rules

1. **Always call `t.Helper()`** in test utilities so failures point to the
   caller, not the helper.
2. **Factory functions with functional options** for complex test objects —
   defaults with per-test overrides, never a 15-parameter constructor.
3. **Prefer `t.Cleanup` over `defer`** — it runs even after `t.FailNow()`
   and is scoped to the test, not the function.

Full implementations in `references/helpers-and-fixtures.md`.

## 4. Choosing the Test Type

| Situation | Approach | Details |
|---|---|---|
| Pure function, 3+ data cases | Table-driven test | go-test-table-driven skill |
| HTTP handler in isolation | `httptest.NewRecorder` + mock store | `references/integration-testing.md` |
| Full routing/middleware stack | `httptest.NewServer` | `references/integration-testing.md` |
| Real database behavior | testcontainers + build tags | `references/integration-testing.md` |
| Complex output (JSON, HTML, SQL) | Golden files in `testdata/` | `references/helpers-and-fixtures.md` |
| Parser/validator on untrusted input | Fuzz test | `references/integration-testing.md` |

## 5. Mocking Rules

- **Interface-based hand-written mocks** for small interfaces (≤3 methods):
  a struct with function fields plus recorded calls.
- **Function injection** for simple seams (`now func() time.Time`).
- **Do NOT mock:** value objects, pure functions, the standard library, or
  your own code in the same package. Test the real thing.
- If you mock everything, you're testing your mocks, not your code.

## 6. Parallelism and Coverage

```go
func TestSlugify(t *testing.T) {
    t.Parallel() // safe: pure function, no shared state
    // ...
}
```

Do NOT use `t.Parallel()` when tests share mutable state, databases,
files, or process-level state — `t.Setenv` and `t.Chdir` panic in a
parallel test for exactly that reason.

```bash
go test -race -coverprofile=coverage.out -coverpkg=./... ./...
go tool cover -html=coverage.out   # read the red lines, not the percentage
```

Coverage finds untested code; it does not measure whether the assertions
are any good. Use it to locate gaps, and let CI enforce a floor that only
ratchets up (the go-ci skill owns the gate). Without `-coverpkg`, a
package exercised only by another package's tests reports 0%.

## 7. Testing Concurrent Code

- Never synchronise a test with `time.Sleep` — it is slow when it works and
  flaky when it does not.
- Go 1.25+: run time-dependent code inside `synctest.Test`. The bubble's
  fake clock advances instantly once every goroutine is blocked, and
  `synctest.Wait()` replaces "sleep until the background goroutine gets
  there". On Go 1.24 the API is `synctest.Run` behind
  `GOEXPERIMENT=synctest`. Go 1.27 adds `synctest.Sleep(d)` (advance and
  settle in one call) and `httptest.NewTestServer(t, h)`, an in-memory
  server that works inside the bubble and closes itself.
- Any package that starts goroutines verifies none leak:
  `goleak.VerifyTestMain(m)` in `TestMain`, or `defer goleak.VerifyNone(t)`.
  `-race` finds data races; it does not find a goroutine that never exits.

## 8. Test Context, Benchmarks, and Artifacts

- `t.Context()` (Go 1.24+) instead of `context.Background()` — it is
  cancelled before `t.Cleanup` runs, so blocked work unwinds when the test
  ends.
- Benchmarks use `for b.Loop()` (Go 1.24+): setup runs once, results stay
  alive without a sink. Add `b.ReportAllocs()` and compare runs with
  `benchstat`.
- `t.Attr` (Go 1.25+) emits key/value pairs CI can parse;
  `t.ArtifactDir()` with `go test -artifacts` (Go 1.26+) keeps failing
  golden output or captured bodies outside the repository.

Examples for both sections: `references/concurrency-and-benchmarks.md`.

## 9. Assertion Library Traps

- **Build `assert.New(t)` / `require.New(t)` inside each `t.Run`.** An
  instance created from the parent `t` reports a subtest's failure against
  the parent, and the failing subtest itself prints `PASS`.
- **`require`, `t.Fatal` and `t.FailNow` only on the test goroutine.**
  From a spawned goroutine they cannot stop the test. Send results back on
  a channel and assert after receiving. `go vet`'s `testinggoroutine` and
  golangci-lint's `testifylint` catch most cases.
- **`require` for preconditions, `assert` for independent checks** — a
  failed precondition should stop the test before it nil-panics.
- **`assert.Eventually` hides timing bugs** in polling; prefer `synctest`.

## Anti-Patterns

- 🔴 Test with no assertions — always passes, proves nothing
- 🔴 `time.Sleep` for synchronization — use `testing/synctest`, channels, or polling
- 🔴 Test depends on execution order — each test must stand alone
- 🔴 Mocking everything — you end up testing your mocks, not your code
- 🟡 Test names like `Test1`, `TestSuccess` — name the scenario
- 🟡 Reaching into private fields — test through the public API
- 🟡 No edge cases: empty, nil, zero, max values, unicode
- 🟡 Giant shared setup — each test should set up only what it needs
- 🟡 `context.Background()` in a test — use `t.Context()`
- 🟡 `for range b.N` on Go 1.24+ — use `for b.Loop()`
- 🟢 Fuzz anything that takes untrusted input
- 🟢 Golden files for complex output comparisons

## Verification Checklist

1. Every test has meaningful assertions (no empty test bodies)
2. Test names describe the scenario, not the method
3. `t.Helper()` called in every test utility function
4. `t.Cleanup()` used for resource teardown
5. `t.Parallel()` used where safe, avoided where not
6. Integration tests guarded with `testing.Short()` or build tags
7. Mocks are minimal — only mock external dependencies
8. No `time.Sleep` for synchronization anywhere in the suite
9. Packages that start goroutines verify with `goleak`
10. Benchmarks use `for b.Loop()` and call `b.ReportAllocs()`
11. Edge cases covered: empty, nil, zero, boundary values
12. `go test -race ./...` passes
13. Coverage is used to find gaps; CI enforces a ratcheting floor, not a target
14. testify instances are built per subtest; no `require` off the test goroutine

© eduardo-sl · MIT · eduardo-sl/go-agent-skills

Similar skills

All skills for Testing and QA →

From the same set22

go-semantic-tools Finds function calls, interface implementations, and dependency graphs in Go via gopls and go list, not text search. Understands implicit interfaces, embedding, and cross-package dependencies that grep misses. CX Codex +1 ★ 83repo stars go-dependency-audit Audits Go module dependencies for vulnerabilities, stale versions, and go.mod issues. Use it before upgrades or a dependency review. CX Codex +1 ★ 83repo stars go-ci A skill that helps your AI agent write and set up GitHub Actions for Go projects. Use it when you need a pipeline with linting, tests, security checks, and caching. CX Codex +1 ★ 83repo stars git-commit A skill that makes your agent write commit messages in Conventional Commits style: typed, scoped, imperative. Useful when the repo history has turned into noise. CX Codex +1 ★ 83repo stars go-troubleshooting A skill that walks the agent through Go runtime diagnosis: panics, deadlocks, goroutine and memory leaks, race reports. Useful when a program crashes or hangs and the cause is unclear. CX Codex +1 ★ 83repo stars go-performance-review Find Go performance bottlenecks and check proposed changes with benchmarks and profiles. Use it to review hot paths or reduce allocations. CX Codex +1 ★ 83repo stars go-observability A skill that sets conventions for Go service telemetry: structured logs with log/slog, OpenTelemetry traces and metrics, SLO alerts. Useful once a service runs in production and you need to see what it does. CX Codex +1 ★ 83repo stars go-concurrency-review Review Go concurrency or build safer concurrent code with checks for goroutine lifecycles, channels, shared state, and context cancellation. CX Codex +1 ★ 83repo stars

Project sponsors

Companies that keep this analytics open
Gold sponsor

Impressions, clicks and code copies — open statistics. We keep the numbers public for the same reason NeBlask itself is public. Become a sponsor