Agent skill

Writing gotest Tests

by mvrahden in mvrahden/go-test

Guides writing, fixing and migrating tests in Go repositories that use the gotest suite framework, including version differences and CI setup.

MITAuto-check passedTesting & QA

Install Writing gotest Tests

skills CLI
$ npx skills add mvrahden/go-test --skill writing-gotest-tests -a claude-code

Project install by default; add -g for ~/.claude/skills/.

GitHub CLI
$ gh skill install mvrahden/go-test writing-gotest-tests --agent claude-code

Project scope by default; add --scope user for a personal install. Needs GitHub CLI 2.90.0 or later (public preview).

Manual copy
$ git clone --depth 1 https://github.com/mvrahden/go-test.git skills-src && mkdir -p .claude/skills && cp -r skills-src/skills/writing-gotest-tests .claude/skills/writing-gotest-tests && rm -rf skills-src

Use ~/.claude/skills/ instead of .claude/skills for a personal install. The folder must contain SKILL.md.

Claude Code skills documentation · loads skills from .claude/skills/

Facts

Skill name
writing-gotest-tests
GitHub stars
128
Token cost
~3.1k tokens
SKILL.md length
1,500 words
Files
19
Skills in repo
1
Repo updated
First seen
Licence
MIT

At a glance

Guides writing, fixing and migrating tests in Go repositories that use the gotest suite framework, including version differences and CI setup.

  • Works in 10 steps: Suites are structs, naming is the API.… → Lifecycle hooks own resources. Setup in… → Do not imitate existing tests blindly.… → …
  • Writing or fixing tests in a Go repo that uses the gotest framework
  • SKILL.md covers Bootstrap, The Two Runners — a complete…, The write loop and Core rules, plus 3 more sections
  • Runs Go scripts from its folder; calls go

What it does

This skill covers writing, fixing, reviewing and restructuring tests in Go repositories that use the gotest framework (github.com/mvrahden/go-test), which inverts habits learned from the standard library and testify. It centers on TestSuite structs, gotest.T, BeforeEach and AfterAll hooks, Fixture types and the gotest CLI, and also covers migrating testify or standard-library tests to gotest and setting up or repairing CI. A reference folder covers assertions, CI, the CLI, config, fixtures and fuzzing.

Version handling comes first: the agent runs go tool gotest version, or reads go.mod before bootstrap, remembering that a replace line overrides the require version. The skill targets v1.26.0 and later and lists what differs on v1.25.x, such as the parallel suite config recipe, config values where zero keeps the default, Test*Async methods that are recognized but never rendered, and filtering individual Each rows with -run, which can deadlock the test binary. Features such as SuiteConfig.Exclusive and gotest spec --static need v1.27 or newer.

When your agent uses it

  • Writing or fixing tests in a Go repo that uses the gotest framework
  • Migrating testify or standard-library tests to gotest
  • Setting up or repairing CI for a gotest repository
  • Checking which gotest features work on an older version

Example prompts

  • “Add a gotest suite for the pricing package with BeforeEach setup and a shared fixture.”
  • “Migrate the testify tests in store/ to gotest.”
  • “Fix the CI pipeline for this gotest repo, which fails on the gotest step.”

Requirements

  • A Go repository using the gotest framework

Workflow steps

10 steps, taken from the first numbered list in SKILL.md.

  1. Suites are structs, naming is the API. type XxxTestSuite struct{},
  2. Lifecycle hooks own resources. Setup in BeforeEach (or BeforeAll
  3. Do not imitate existing tests blindly. Observed failure: agents copy
  4. Ask "why is this suite NOT parallel?" Observed failure: agents never
  5. Poll, never sleep. `gotest.Eventually(t, waitFor, tick, func(poll
  6. Never call t.T().Helper() — call sites resolve automatically; the
  7. **Ask "why is this wall-clock-asserting suite NOT Exclusive?"
  8. Write the condition, not the connective (v1.29+). `t.When("email
  9. **Fuzz targets are suite methods, and they assert a property
  10. Gate on the environment with SuiteGuard, never a skip. A suite

What it can do on your machine

Read from SKILL.md and the folder at commit 577a818. It shows what the files ask for, not the result of running them.

  • Tool permissions

    Pre-approves nothing: there is no allowed-tools line, so your agent's usual permission prompts apply.

    From allowed-tools in the SKILL.md frontmatter.

  • Runs code

    Ships script files (Go, from the files we listed), which the agent can run.

    Shell commands in SKILL.md call:

    • go

    From the folder's file list and the shell code blocks in SKILL.md.

  • Network

    No URLs in SKILL.md.

    From URLs in SKILL.md, links to its own repository left out.

  • Credentials

    Names no API keys, tokens, secrets or passwords.

    From names ending in _API_KEY, _TOKEN, _SECRET, _KEY or _PASSWORD in SKILL.md.

Context cost

Writing gotest Tests loads about 3.1k tokens when it runs. Until then it costs about 94 tokens; SKILL.md has 1,500 words of instructions outside code blocks.

Always · name and description, kept in context so the agent knows when to use it
~94
When it runs · the whole SKILL.md, loaded when a task matches
~3.1k

Estimates: characters ÷ 4, the usual rule of thumb; real counts depend on the model's tokenizer. Scripts and assets cost tokens only if the agent reads them.

Safety

Auto-check passed

The automated check found no risky patterns in SKILL.md.

Automated static check — not a guarantee. Review scripts before installing. It scans the text of SKILL.md for risky patterns (piping downloads into a shell, reading credential files, hidden Unicode, destructive commands); files beside SKILL.md are not scanned.

SKILL.md

The full file from mvrahden/go-test at commit 577a818, republished under its MIT licence (© mvrahden). 1,500 words, ~3,075 tokens.

Download SKILL.mdSave it as .claude/skills/writing-gotest-tests/SKILL.md (or your agent's skills folder). This skill also uses 18 other files; get the full folder from GitHub.
name
writing-gotest-tests
description
Use when writing, fixing, reviewing, or restructuring tests in a Go repository that uses the gotest framework (github.com/mvrahden/go-test) — `*TestSuite` structs, `gotest.T`, `BeforeEach`/`AfterAll` hooks, `*Fixture` types, or the `gotest` CLI — when migrating testify or stdlib tests to gotest, and when setting up or fixing CI for a gotest repository.

Writing gotest tests

gotest inverts habits learned from stdlib/testify. Follow this file's rules; consult reference/ only when the task touches that area.

Version check (do this FIRST): run go tool gotest version (after Bootstrap below); dev (replace directive) / dev (source checkout) mean a source build — assume current behavior. Before Bootstrap, read go.mod instead, remembering that a replace line overrides the require version.

This skill describes v1.32. On an older release, read reference/versions.md before writing anything: it lists what each older release lacks or does differently, including rules below that fail there (on v1.25.x, rules 1, 3 and 4). A rule tagged v1.27+ (or later) needs that release; skip it on older ones.

Bootstrap

The repo has the library; the CLI runs via Go's tool directive (requires Go ≥ 1.25; v1.30+ requires Go ≥ 1.26). One-time:

sh
go get -tool github.com/mvrahden/go-test/cmd/gotest@$(go list -m -f '{{.Version}}' github.com/mvrahden/go-test)
go tool gotest version

Keep the @version: without it go get -tool upgrades the library pin, which setup must never do (@latest only in a project with no pin yet). In a vendored module (vendor/modules.txt) the go.mod edit breaks every build until go mod vendor reruns, and gotest cannot run vendored at all (the overlay's pkg/gotestruntime import is never vendored) — report, do not work around.

Then every command is go tool gotest <args>; if Go reports the short name ambiguous, use go tool github.com/mvrahden/go-test/cmd/gotest. Never a global go install binary: it drifts from the pin and from the module's Go, and releases after v1.28.1 refuse to run on either drift (FAIL: naming the version or the Go it was built with, exit 2). That refusal is never a fault in the tests; switch to go tool gotest instead of bumping the pin. (Bare go run github.com/mvrahden/go-test/cmd/gotest fails on fresh consumers: module pruning leaves the CLI's deps out of go.sum.)

In a go.work workspace the tool declared in any use module works from the root and inside each module, at the highest pin. ./... is rejected at the root, so name each module (go tool gotest ./svc/... ./lib/...) or run inside one. The go.work go line must be at least the modules' go lines.

The Two Runners — a complete run is BOTH commands

go test ignores gotest suites (signature incompatibility, by design). gotest runs suites and never runs stdlib tests — it prints [no suites] plus a stderr note when it skips them. Running only one command silently misses tests. Always finish with:

sh
go tool gotest ./...
go test ./...

Add -race to both before calling anything done.

The write loop

Write → lint → both runners → fix:

sh
go tool gotest lint -fix ./...
go get -tool golang.org/x/tools/cmd/goimports
go tool goimports -l .

lint -fix applies suggested fixes as TEXTUAL edits — no formatting pass runs. A fix that strands or misses an import leaves the file uncompilable: install goimports once via the tool directive (as above — plain go run of it fails on missing go.sum entries), then always run go tool goimports -w . after -fix and re-run the loop.

The linter catches direct misuse (t.T() escapes even inside closures, outer-t in poll callbacks, testify idioms, focus leftovers, and fail-guard: any if cond { gotest.Fail(t, …) } or if err != nil { t.Fatal(err) } guard — assertions halt on failure, so state them directly: gotest.NoError(t, err), never a guarded fail). Fixes can compose — a rewritten guard may itself be simplifiable — so re-run lint -fix until it reports nothing. When two findings share a construct the linter reports only the stronger one (integrity over style); fix what it says before expecting style suggestions there.

Suppression follows rule tiers: integrity rules (poll-scope, suite-lifecycle, focus, …) accept only per-line //nolint:<rule>; style and migration rules can also be skipped project-wide via .gotest.yml lint.skip or -skip-<rule> flags. The stdlib-test rule flags every stdlib TestXxx(*testing.T) — when a stdlib test is intentional (assertion-layer tests, benchmarks-adjacent code), mark its package clause with //nolint:stdlib-test, or the lint run fails. The linter does NOT catch: plain defer cleanup, shared mutable state in parallel suites, or structural problems — those are your job, below.

Core rules

  1. Suites are structs, naming is the API. type XxxTestSuite struct{}, exported, methods func (s *X) TestBehavior(t *gotest.T). No TestMain, no registration — the CLI generates the harness invisibly (never commit the gotest_psuite_test.go/gotest_pxsuite_test.go files gotest generate writes; gotest clean removes them). F_/X_ prefixes focus/exclude; Test*Async(t, done) declares async tests.

  2. Lifecycle hooks own resources. Setup in BeforeEach (or BeforeAll for expensive read-only state), teardown in AfterEach/AfterAll as suite fields — NEVER defer or t.T().Cleanup in a test method. A plain defer lints clean and is still wrong: it skips teardown verification and blocks parallelization.

  3. Do not imitate existing tests blindly. Observed failure: agents copy a repo's existing SuiteConfig() verbatim, propagating anti-patterns. A SuiteConfig() marker states intent: omit it entirely for defaults. A duration the marker leaves at zero gets the default, as if the marker were absent; gotest.NoDeadline disables one. On v1.26–v1.29 a zero/omitted duration meant NO deadline instead — compose onto a preset there.

  4. Ask "why is this suite NOT parallel?" Observed failure: agents never parallelize unprompted, even when asked to improve tests. The recipe:

    go
    func (s *ShopTestSuite) SuiteConfig() gotest.SuiteConfig {
        cfg := gotest.DefaultSuiteConfig()
        cfg.Parallel = true
        return cfg
    }
    
    type shopCtx struct{ inv *Inventory }
    
    func (s *ShopTestSuite) BeforeEach(t *gotest.T) *shopCtx {
        return &shopCtx{inv: NewInventory()}
    }

    Every test method then takes (t *gotest.T, ctx *shopCtx) — the generator enforces this. Legitimate reasons to stay sequential: Setenv (panics in parallel tests), shared live resources without per-test keys/schemas (-race is process-local and cannot see datastore contention — it is necessary, not sufficient).

  5. Poll, never sleep. gotest.Eventually(t, waitFor, tick, func(poll *gotest.R) { ... }) — assert a stable fixed point, not a transient state. The callback's poll handle has only Errorf/FailNow/ Failed/Message; pass poll (not t) to assertions inside it.

  6. Never call t.T().Helper() — call sites resolve automatically; the linter flags it. Reach for t.T() only when nothing on gotest.T (It, When, Context, TempDir, Setenv, Skipf, Errorf, FailNow) covers the need.

  7. Ask "why is this wall-clock-asserting suite NOT Exclusive?" (v1.27+) — the counterpart to rule 4. A suite whose assertions or timeout budgets measure elapsed time (latency bounds, timing budgets, contended ports/containers) cannot share a saturated machine: mark it SuiteConfig{Exclusive: true} (statically parsed like Parallel — boolean literal only; see reference/config.md) and it dispatches strictly alone after all other suites finish. A budget verdict taken under load is not a verdict you can act on.

  8. Write the condition, not the connective (v1.29+). t.When("email is valid"), never t.When("when email is valid"); t.It("creates the user"), never t.It("it creates the user"). The spec renders every When label as when <condition> on every surface (terminal, JSON, discovery, the editor's tree and Spec View) and the ✓/✗ glyph plays the role of "it", so the word in the source is said twice. A When that opens with its own connective (with …, given …, after …, if …, …) is rendered as written. The behavior-wording rule flags the redundant word and lint -fix drops it. Subtest names never carry the connective — -run filters and snapshot keys are unaffected.

  9. Fuzz targets are suite methods, and they assert a property (v1.29+). func (s *XTestSuite) FuzzParse(f *gotest.F): f.Add typed seeds first, then f.Fuzz(func(t *gotest.T, in …) { … }) whose body asserts something the input must satisfy (round-trip, idempotence, no panic is not enough — fuzz-no-oracle). Never a top-level func FuzzX(*testing.F): gotest ignores it on every version. Struct and named-type arguments are fine and fan out per field; the refused shapes and the crasher loop are in reference/fuzzing.md. Search with go tool gotest fuzz --for=30s ./...; a crasher becomes a seed through gotest fuzz promote, never a hand-committed corpus file for a struct target.

  10. Gate on the environment with SuiteGuard, never a skip. A suite that needs what a machine may lack (a DATABASE_URL, Docker, a credential) declares func (s *X) SuiteGuard() string: empty runs the suite, anything else skips it with that reason. It runs before the suite's fixtures, config and BeforeAll, so the suite's own setup never starts; t.T().Skip() in BeforeAll runs after setup has begun. Read the environment only: fixture fields are still nil in the guard.

Show full SKILL.md (253 more words)Show less

Restructuring existing suites (the blue phase)

Enter only at a green pause point when: setup is duplicated across tests, a third similar test is being added, a touched suite already smells, or you were asked to clean up. Follow reference/refactoring.md for the ladder and smells list. Non-negotiable safety invariants (tests protect nothing — observed failure: agents restructure with no case accounting):

  1. Capture the executed case LIST before and after, into separate files, and diff them:

    sh
    go tool gotest -json ./... | grep -o '"Package":"[^"]*","Test":"[^"]*"' | sort -u > cases-before.txt
    test -s cases-before.txt

    Capture the Package+Test PAIR — bare Test names collapse identically named suites across packages, hiding whole-package deletions.

    After the refactor, capture cases-after.txt the same way and run diff cases-before.txt cases-after.txt. Enumerate every rename/merge BEFORE editing; every diff line must map to that list. Coverage may only grow.

  2. Both runners + -race green before AND after.

  3. Never delete or weaken an assertion without saying so in your report.

  4. Never touch production code during a test refactor — a test that resists restructuring is a design finding to report.

  5. Consider rule 4 (parallelization) part of every improvement pass.

Minimal complete suite

go
package shop_test

import (
	"example.com/shop"
	"github.com/mvrahden/go-test/pkg/gotest"
)

type CartTestSuite struct {
	cart *shop.Cart
}

func (s *CartTestSuite) BeforeEach(t *gotest.T) {
	s.cart = shop.NewCart()
}

func (s *CartTestSuite) TestTotalsItems(t *gotest.T) {
	s.cart.Add("apple", 2)
	gotest.Equal(t, 2, s.cart.Count("apple"))
}

References

  • reference/assertions.md — full assertion surface, Nil/NotNil type guards, snapshot testing
  • reference/config.md — literal config semantics, presets, compose form
  • reference/fixtures.md — fixture DAG, shared fixtures, hooks
  • reference/cli.md — the full CLI surface and flags
  • reference/refactoring.md — restructuring ladder, smells → moves
  • reference/ci.md — CI workflow shape, the gotest action, linter coexistence
  • reference/migration.md — testify/stdlib → gotest
  • reference/fuzzing.md — fuzz targets, seeds, struct arguments, the crasher loop
  • reference/versions.md — what older releases lack or do differently

© mvrahden, MIT. Rendered from Markdown: HTML in the file is shown as text, images as links, and headings moved down two levels. Raw file

Files

SKILL.md and 18 other files in skills/writing-gotest-tests of mvrahden/go-test.

  • SKILL.md
  • evals/RUBRIC.md
  • evals/consumer-fixture/go.mod
  • evals/consumer-fixture/go.sum
  • evals/consumer-fixture/go.work
  • evals/consumer-fixture/pricing/pricing.go
  • evals/consumer-fixture/pricing/pricing_test.go
  • evals/consumer-fixture/store/snapshot.go
  • evals/consumer-fixture/store/store.go
  • evals/consumer-fixture/store/store_test.go
  • reference/assertions.md
  • reference/ci.md
  • reference/cli.md
  • reference/config.md
  • reference/fixtures.md
  • reference/fuzzing.md
  • … and 3 more

Open the folder on GitHubat commit 577a818

Compare with similar skills

Writing gotest Tests next to the 5 skills that share the most tags, products or categories with it. Stars are the repository's; “used in” counts other GitHub owners with a copy.

Writing gotest Tests compared with similar skills
SkillStarsUsed inTokensAuto-checkLicenceRepo updated
Writing gotest Tests this skillmvrahden/go-test128—~3.1kAutomated safety check: PassMIT
Migrate Vstest To Mtprunceel/ReactiveProperty944—~4.3kAutomated safety check: PassMIT
Debug Playwright Prowquay/quay2.8k—~2.2kAutomated safety check: PassApache-2.0
Bats Shell Testing Patternswshobson/agents40k12 repos~1.3kAutomated safety check: PassMIT
Simple Modern Uvjlevy/simple-modern-uv301—~1.9kAutomated safety check: PassMIT
NIC Testing Patternsnginx/kubernetes-ingress5.1k—~2.8kAutomated safety check: PassApache-2.0

Similar skills

  • Migrate Vstest To Mtp

    runceel/ReactiveProperty

    Migrates .NET test projects from VSTest to Microsoft.Testing.Platform (MTP).

    944 GitHub stars~4.3k tokensUpdated 1 mo ago
    DevOps & CloudAuto-check passed
  • Deep-dive diagnosis of a Playwright test failure already isolated to one Quay Prow/OpenShift CI run: downloads its GCS artifacts (results.json, JUnit, build/pod logs, Jaeger traces), classifies real…

    2.8k GitHub stars~2.2k tokensUpdated yesterday
    Testing & QAAuto-check passed
  • Writes unit tests for shell scripts with Bats: error-condition tests, fixtures and mocks, cross-shell checks, parallel runs, helper files and CI integration.

    40k GitHub starsUsed in 12 repos~1.3k tokens
    Testing & QAAuto-check passed
  • Simple Modern Uv

    jlevy/simple-modern-uv

    Start, selectively modernize, fully migrate, or update Python projects using simple-modern-uv practices: uv, ruff, BasedPyright, pytest, GitHub Actions CI, and tag-driven PyPI publishing.

    301 GitHub stars~1.9k tokensUpdated 1 mo ago
    Testing & QAAuto-check passed
  • NIC Testing Patterns

    nginx/kubernetes-ingress

    Testing conventions for the NGINX Ingress Controller repo: Go table-driven tests, mandatory snapshot regeneration, Helm tests and Python pytest integration tests.

    5.1k GitHub stars~2.8k tokensUpdated yesterday
    Testing & QAAuto-check passed
  • Robotics Testing

    arpitg1304/robotics-agent-skills

    Testing strategies, patterns, and tools for robotics software.

    369 GitHub stars~4.7k tokensUpdated 2 mo ago
    Testing & QAAuto-check passed

Works with

Categories

Questions about Writing gotest Tests

What does Writing gotest Tests do?

Guides writing, fixing and migrating tests in Go repositories that use the gotest suite framework, including version differences and CI setup. com/mvrahden/go-test), which inverts habits learned from the standard library and testify.T, BeforeEach and AfterAll hooks, Fixture types and the gotest CLI, and also covers migrating testify or standard-library tests to gotest and setting up or repairing CI.

When should I use Writing gotest Tests?

Writing gotest Tests fits situations like: writing or fixing tests in a Go repo that uses the gotest framework; migrating testify or standard-library tests to gotest; setting up or repairing CI for a gotest repository; checking which gotest features work on an older version.

How do I install Writing gotest Tests in Claude Code?

Run `npx skills add mvrahden/go-test --skill writing-gotest-tests -a claude-code`. Or copy the skill folder (skills/writing-gotest-tests in mvrahden/go-test) into .claude/skills/writing-gotest-tests in your project. Claude Code loads it when a task matches its description.

How do I install Writing gotest Tests in Codex?

Run `npx skills add mvrahden/go-test --skill writing-gotest-tests -a codex`. Or copy the skill folder (skills/writing-gotest-tests in mvrahden/go-test) into .agents/skills/writing-gotest-tests in your project. Codex loads it when a task matches its description.

Can I use Writing gotest Tests in Cursor, Gemini CLI or GitHub Copilot?

Cursor, Gemini CLI, GitHub Copilot and OpenCode also load SKILL.md folders. With the skills CLI, run `npx skills add mvrahden/go-test --skill writing-gotest-tests -a cursor` (or -a gemini-cli, github-copilot or opencode for the others). To copy it by hand, put the folder in .cursor/skills/writing-gotest-tests, .gemini/skills/writing-gotest-tests, .github/skills/writing-gotest-tests and .opencode/skills/writing-gotest-tests in your project.

What does Writing gotest Tests need to run?

Going by SKILL.md and its folder, Writing gotest Tests needs Go for the scripts in its folder and the command-line tools its instructions call (go). Our summary lists: A Go repository using the gotest framework.

Does Writing gotest Tests access the network?

SKILL.md contains no URLs. Any network use would come from the scripts or tools the agent runs. This is read from the text; nothing was executed.

Is Writing gotest Tests safe to install?

Our automated static check of SKILL.md found no risky patterns, such as piping downloads into a shell, reading credential files or hidden Unicode. It is not a guarantee. Review the folder before installing.

What licence does Writing gotest Tests use?

Writing gotest Tests is published under the MIT licence (the repository's licence). It allows redistribution, so the full SKILL.md is shown on this page.

How many tokens does Writing gotest Tests use?

About 3.1k tokens (SKILL.md is roughly 12k characters). Agents keep only the skill's name and description in context until a task matches; then they load SKILL.md in full.

What are the alternatives to Writing gotest Tests?

Skills that share tags, products or a category with Writing gotest Tests: Migrate Vstest To Mtp (runceel/ReactiveProperty, 944 stars), Debug Playwright Prow (quay/quay, 2.8k stars), Bats Shell Testing Patterns (wshobson/agents, 40k stars) and Simple Modern Uv (jlevy/simple-modern-uv, 301 stars). The comparison table on this page puts their stars, adoption, token cost, safety result and licence side by side.

Who maintains Writing gotest Tests?

mvrahden (a GitHub user) maintains it in mvrahden/go-test, which has 128 GitHub stars. The repository was last updated on October 10, 2026.

Source: mvrahden/go-test on GitHub. Facts on this page come from the repository at the commit we read; the author's words are quoted as theirs.