This skill should be used when writing, reviewing, or debugging Go tests. It covers table-driven tests, test organization, assertions, mocking patterns, coverage analysis, benchmarking, integration tests, race detection, and test quality standards.
From mnpx claudepluginhub molcajeteai/plugin --plugin mThis skill uses the workspace's default tool permissions.
references/benchmarking.mdreferences/coverage.mdreferences/mocking.mdreferences/table-driven-tests.mdSearches, retrieves, and installs Agent Skills from prompts.chat registry using MCP tools like search_skills and get_skill. Activates for finding skills, browsing catalogs, or extending Claude.
Searches prompts.chat for AI prompt templates by keyword or category, retrieves by ID with variable handling, and improves prompts via AI. Use for discovering or enhancing prompts.
Provides ClickHouse patterns for MergeTree schemas, query optimization, aggregations, window functions, joins, and data ingestion for high-performance analytics.
Quick reference for writing effective Go tests. Each section summarizes the key rules — reference files provide full examples and edge cases.
The standard Go testing pattern. Use it for any function with multiple input/output combinations.
func TestParseAge(t *testing.T) {
tests := []struct {
name string
input string
want int
wantErr bool
}{
{name: "valid age", input: "25", want: 25},
{name: "zero", input: "0", want: 0},
{name: "negative", input: "-1", wantErr: true},
{name: "not a number", input: "abc", wantErr: true},
{name: "empty string", input: "", wantErr: true},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := ParseAge(tt.input)
if tt.wantErr {
if err == nil {
t.Fatal("expected error, got nil")
}
return
}
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if got != tt.want {
t.Errorf("ParseAge(%q) = %d, want %d", tt.input, got, tt.want)
}
})
}
}
name field and t.Run. Makes failures easy to identify.t.Parallel() to subtests when they don't share mutable state.See references/table-driven-tests.md for parallel subtests, cleanup, golden files, and advanced patterns.
Place tests in a _test.go file in the same package:
internal/service/
├── user.go
├── user_test.go # Same package (white-box)
└── user_export_test.go # _test package (black-box, optional)
<source>_test.go in the same directory.Test<FunctionName> or Test<Type>_<Method>."valid email", "empty input", "duplicate key".test or put in a testutil package. Call t.Helper() in every test helper.func newTestUser(t *testing.T, name string) *User {
t.Helper()
u, err := NewUser(name, "test@example.com")
if err != nil {
t.Fatalf("creating test user: %v", err)
}
return u
}
Use TestMain for package-level setup/teardown (database connections, test servers):
func TestMain(m *testing.M) {
// Setup
db := setupTestDB()
defer db.Close()
// Run tests
os.Exit(m.Run())
}
Go's testing package uses explicit comparisons. This keeps tests readable and avoids assertion library dependencies.
if got != want {
t.Errorf("Add(%d, %d) = %d, want %d", a, b, got, want)
}
Use testify when it significantly improves readability, especially for:
assert.Equal(t, want, got)assert.ElementsMatch(t, want, got)require.NoError(t, err) (stops test on failure)require — Stops the test immediately on failure. Use for preconditions and setup steps.assert — Records failure but continues. Use for the actual assertions when you want to see all failures.func TestCreateUser(t *testing.T) {
// Preconditions — stop if these fail
db, err := setupTestDB(t)
require.NoError(t, err)
user, err := CreateUser(db, "alice")
require.NoError(t, err)
// Assertions — check all properties
assert.Equal(t, "alice", user.Name)
assert.NotEmpty(t, user.ID)
assert.WithinDuration(t, time.Now(), user.CreatedAt, time.Second)
}
Define small interfaces at the consumer site, then create test implementations:
// In production code
type UserStore interface {
FindByID(ctx context.Context, id string) (*User, error)
}
// In test code
type mockUserStore struct {
findByIDFn func(ctx context.Context, id string) (*User, error)
}
func (m *mockUserStore) FindByID(ctx context.Context, id string) (*User, error) {
return m.findByIDFn(ctx, id)
}
Use httptest.NewServer for HTTP client testing and httptest.NewRecorder for handler testing:
func TestGetUser(t *testing.T) {
rec := httptest.NewRecorder()
req := httptest.NewRequest("GET", "/users/123", nil)
handler := NewHandler(mockStore)
handler.GetUser(rec, req)
assert.Equal(t, http.StatusOK, rec.Code)
}
testcontainers-go for disposable Postgres instances in CI.See references/mocking.md for mock generation, test doubles taxonomy, and database testing patterns.
# Basic coverage
go test -cover ./...
# Generate HTML report
go test -coverprofile=coverage.out ./...
go tool cover -html=coverage.out -o coverage.html
# Coverage for specific package
go test -cover ./internal/service/...
# Show uncovered lines
go tool cover -func=coverage.out
| Level | Range | Meaning |
|---|---|---|
| Good | 70–80% | Solid coverage for most projects |
| Excellent | 80–90% | Strong confidence in code correctness |
| Diminishing returns | 90%+ | Only pursue for critical paths |
main.go wiring codeSee references/coverage.md for CI integration, coverage gates, and per-package analysis.
func BenchmarkParseAge(b *testing.B) {
for b.Loop() {
ParseAge("25")
}
}
# Run all benchmarks
go test -bench=. ./...
# With memory allocation stats
go test -bench=. -benchmem ./...
# Specific benchmark
go test -bench=BenchmarkParseAge -benchmem ./internal/parser/
# Compare results with benchstat
go test -bench=. -count=10 ./... > old.txt
# ... make changes ...
go test -bench=. -count=10 ./... > new.txt
benchstat old.txt new.txt
-benchmem — Allocation counts matter as much as speed.-count=10 for reliable results. Single runs are noisy.benchstat — Compare before/after with statistical confidence.b.ResetTimer() after expensive setup that shouldn't be measured.See references/benchmarking.md for memory benchmarks, sub-benchmarks, and benchstat workflow.
Use build tags or _integration_test.go suffix to separate from unit tests:
//go:build integration
package service_test
func TestUserService_Integration(t *testing.T) {
if testing.Short() {
t.Skip("skipping integration test")
}
// ...
}
func TestUserRepository_Create(t *testing.T) {
if testing.Short() {
t.Skip("skipping integration test")
}
db := testutil.NewTestDB(t) // starts container, runs migrations
repo := NewUserRepository(db)
user, err := repo.Create(ctx, "alice", "alice@example.com")
require.NoError(t, err)
assert.NotEmpty(t, user.ID)
// Verify persisted
found, err := repo.FindByID(ctx, user.ID)
require.NoError(t, err)
assert.Equal(t, "alice", found.Name)
}
Test the full HTTP stack with httptest.NewServer:
func TestAPI_CreateUser(t *testing.T) {
srv := httptest.NewServer(setupRouter())
defer srv.Close()
resp, err := http.Post(srv.URL+"/users", "application/json",
strings.NewReader(`{"name":"alice"}`))
require.NoError(t, err)
defer resp.Body.Close()
assert.Equal(t, http.StatusCreated, resp.StatusCode)
}
# Always run tests with race detector
go test -race ./...
# Build with race detector for manual testing
go build -race ./cmd/server
sync.RWMutex or sync.Map.-race in CI — Always. It's a hard requirement, not optional.testing.Short().After writing or modifying tests, always run the full verification protocol from the go-writing-code skill:
make fmt && make lint && make vet && make build && make test
All 5 steps must pass. See go-writing-code skill for details.
| File | Description |
|---|---|
| references/table-driven-tests.md | Parallel subtests, cleanup, golden files, advanced patterns |
| references/mocking.md | Interface mocks, httptest, test doubles, database testing |
| references/coverage.md | Coverage commands, targets, CI integration, per-package analysis |
| references/benchmarking.md | Benchmark patterns, benchstat, memory benchmarks, sub-benchmarks |