gotest

Specification-driven test suites for Go.

A go test code generator — you write structs with naming conventions, it handles the wiring.

go test under the hood No runtime deps No reflection Pure code generation
gotest gopher
go get -tool github.com/mvrahden/go-test/cmd/gotest@latest
· CI Go Report Card
gotest spec ./pkg/user
$ gotest spec ./pkg/user

UserService
  Create
    when email is valid
      ✓ creates the user (8ms)
      ✓ sends a welcome email (120ms)
    when email already exists
      ✓ returns ErrDuplicate (<1ms)
  Delete
    ✓ soft-deletes the user (5ms)
    ~ hard-deletes after 30 days — SKIPPED
1 suite, 5 behaviors: 4 passed, 0 failed, 1 skipped

Under the hood it's go test. On the surface, your suites read as specifications.

Tests that verify. Specifications that document.

Convention-driven suites that read as documentation. Structured output for humans and machines.

✓ Behavioral Specifications

When groups context, It specifies behavior. Your tests are the documentation — always in sync, never stale.

✓ Lifecycle Hooks

Setup and teardown at the suite or test level. Define it once, every test gets it. No more copy-pasted boilerplate.

✓ Type-Safe Assertions

Generic assertions that catch type mismatches at compile time. Equality failures include diffs. Failures trace back to the test call site automatically — no t.Helper() needed.

✓ Snapshot Testing

Capture expected output once, catch regressions automatically. Snapshots are created on first run and verified on every run after.

✓ Fixtures

Share expensive setup like databases or containers across test suites, even across packages. Fixtures nest and compose naturally.

✓ Watch Mode

Save a file, see results instantly. Only affected packages re-run. Focus on a single test for a tight feedback loop.

✓ Benchmark Gates

Benchmarks are suite methods too. Save a baseline, compare a branch against it, and fail the build when a number moves the wrong way.

✓ Fuzzing With Types

Fuzz a struct, not a byte slice. Seeds stay readable Go literals, and a crasher becomes a regression test in one command.

Write structs. Get tests.

The naming conventions are the API. Your test structure is the specification.

user_service_suite_test.go
type UserServiceTestSuite struct {
    svc *UserService
}

func (s *UserServiceTestSuite) BeforeEach(t *gotest.T) {
    s.svc = NewUserService()
}

func (s *UserServiceTestSuite) TestCreate(t *gotest.T) {
    t.When("email is valid", func(w *gotest.T) {
        w.It("creates the user", func(it *gotest.T) {
            err := s.svc.Create("alice@example.com")
            gotest.NoError(it, err)
        })
    })
}
You write Struct + methods with naming conventions
→
gotest generates The boilerplate you'd write by hand
→
go test runs Standard output, standard tooling. Your source tree stays clean.

Coming from testify/suite?

Same test organization, different mechanism.

testify/suitegotest
Runtime dependenciesYesNone
ReflectionYesNone
Registration boilerplatesuite.Run(t, new(...))None
Lock-inHighLow — standard Go structs
BDD vocabulary—When / It
Snapshot testing—Built-in
gotest migrate ./...

Automatic migration: renames hooks, rewrites assertions, removes testify imports.

Full ecosystem

CLI gotest ./... -v -race

Generate and test in one command

Linter gotest lint ./...

Catch common mistakes before they reach CI

VS Code mvrahden.gotest

Spec View, Test Explorer, coverage, debug

GitHub Action mvrahden/go-test@v1

PR annotations, step summaries, coverage reports

Agent skill gotest skill install

Teaches coding agents gotest's rules (v1.34+)