mcpField
G
go-testing

go-testing

github.com/o-marsters-1997Testing & quality062 installsUpdated 3d ago
Repository

About go-testing

A skill for writing Go tests consistently across repositories. It gives coding agents concrete conventions for test structure, doubles, databases, concurrency, and validation.

Key features

  1. 01Black-box tests through exported APIs
  2. 02Stateful fakes over interaction mocks
  3. 03Real Postgres integration tests
  4. 04Guidance for async and fuzz tests
  5. 05Test and lint commands

Use cases

  1. 01Adding tests to a Go feature
  2. 02Testing SQL-backed behavior
  3. 03Checking concurrent Go code
  4. 04Reviewing a Go test suite

Install

Copy the command and run it in a terminal.

$npx skills add https://github.com/o-marsters-1997/my-claude-code --skill go-testing

After installing, type/go-testingin Claude Code to confirm it loaded.

Bundled files

References and scripts installed along with SKILL.md.

assets/
golangci-depguard.yml
assets/pgtest/
pgtest_test.go
pgtest.go
settings.go
sweep_test.go
evals/
evals.json
references/
examples.md
integration-db.md

Details

From the project README

Go Testing

How a Go test is written, in every repo. What to test is the repo's testing-policy when it ships one: the kinds of test it uses, what each is worth, which a feature needs, and the reusable patterns to copy as the codebase grows. It never restates how a test is written, so the two do not overlap. For the red-green-refactor loop, use tdd.

Your defaults that are wrong here

These are the habits that show up unprompted. Check your test against this table before writing it, and again before finishing, because they creep back in late in a long task.

You reach forDo this instead
testify assert/require, any assertion libraryPlain if plus t.Errorf; cmp.Diff for anything not a scalar
reflect.DeepEqualcmp.Diff(want, got), reported as (-want +got)
A table with wantErr bool and an if tt.wantErrTwo tables under one test: success, and errors checked with errors.Is
gomock, mockery, EXPECT().Times(1)A stateful in-memory fake; assert on the state it ends in
sqlmock or a mocked database/sql driverA real Postgres from internal/pgtest
TestMain starting a container per packagepgtest.New(t): one container per repo, a database per test
TRUNCATE between tests, shared rowsA fresh database per test, so t.Parallel() is safe
time.Sleep to wait for a goroutineA channel the code signals, a deadline poll, or testing/synctest
strings.Contains(err.Error(), "...")errors.Is / errors.As against an exported sentinel or type
A new test dependencyOnly github.com/google/go-cmp; nothing else without asking

Rules

  1. Test behaviour through the exported API, in the black-box package foo_test. Use package foo only when the unit is unexported by design, and export_test.go as a last resort.
  2. Assert on outputs and state, not on which calls were made. A test that breaks on a refactor that changes no behaviour is a change-detector: delete it.
  3. Pick the most faithful double: real, then fake, then stub, then mock. Mock only a state-changing call across a system boundary (an email sent, a card charged), and then a hand-written recorder, not a mocking library.
  4. A fake is stateful and honest: the same sentinels, the same not-found behaviour. One contract suite runs against the fake and the real implementation. Force an error path with a one-method stub that embeds the interface, never with failure knobs on the fake.
  5. Test SQL against a real Postgres through internal/pgtest. If the repo has no internal/pgtest, install it from this skill's assets: load references/integration-db.md.
  6. Compare with cmp.Diff(want, got) and report (-want +got). If go.mod lacks github.com/google/go-cmp, add it with go get on the first test you touch, and fix any lint rule or doc in the repo that contradicts this in the same change. When the user or the repo has explicitly ruled out new dependencies ("stdlib only"), compare scalars with ==, slices and maps with slices.Equal/maps.Equal, structs field by field. Never reflect.DeepEqual either way.
  7. Failure messages read Func(in) = got, want want. Match errors with errors.Is/errors.As; a caller that needs to tell errors apart gets an exported sentinel or type.
  8. One outer test per function or method under test (TestFunc, TestType_Method); every case runs in t.Run. Where cases are checked the same way, use a table: a slice of structs with a name field run with t.Run(tt.name, ...). Where they aren't, write named subtests inside the same outer test. A branch on a case field means named subtests, not one table. Never a second top-level Test named after a scenario.
  9. t.Error to keep going, t.Fatal when the rest can't run. Never t.Fatal from a goroutine the test didn't start on; send the error back on a channel.
  10. Helpers take t, call t.Helper(), fail on setup errors and register t.Cleanup. They set up; they don't assert. No generic assertEqual.
  11. No time.Sleep to wait. Wait on a channel, poll a condition with a deadline, or run the test in synctest.Test (Go 1.25+) when it involves timers.
  12. t.Parallel() only for tests that share no state: no globals, t.Setenv, t.Chdir or shared rows. A pgtest database is never shared, so DB tests can be parallel.
  13. Compare JSON by decoding it, never as bytes. Normalise golden output before comparing; a human reviews every -update diff.
  14. Fuzz parsers of untrusted input with a property (no panic, round trip); commit testdata/fuzz/.
  15. Test HTTP handlers through the real router and middleware, built the way main builds them.

Running

Plain go test ./... runs everything, database tests included: no build tag, no -short skip, no make target. Build tags are only for end-to-end suites. Without Docker and without TEST_DATABASE_URL, pgtest fails with a message naming both; don't paper over it with t.Skip. Run go test -race ./... and golangci-lint run before finishing.

Where to look

If you're writing…Load
A test that touches Postgres, or a repo with no internal/pgtest yetreferences/integration-db.md
CI for database tests (TEST_DATABASE_URL, service containers)references/integration-db.md
A unit, table, fake plus contract suite, HTTP, async, golden, fuzz or benchmark testreferences/examples.md

assets/pgtest/ holds the helper and its own tests; assets/golangci-depguard.yml holds the lint rule that keeps testcontainers imports inside internal/pgtest.

See also

SkillWhen
go-idiomaticEverything about the code under test
tddWriting the test first, one behaviour at a time
testing-policyWhat to test: test kinds, their value, reusable patterns (per repo)

Comments

Markdown supported