一键导入
golang-testing
Use this skill when writing or modifying Go tests, benchmarks, or test helpers. Covers test style, modern testing APIs, and common mistakes.
用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
菜单
Use this skill when writing or modifying Go tests, benchmarks, or test helpers. Covers test style, modern testing APIs, and common mistakes.
用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
基于 SOC 职业分类
Use when starting feature work that needs isolation from current workspace, before executing implementation plans, or when reviewing an existing branch (e.g. a PR) without switching away from the current branch. It creates isolated git worktrees under `.worktrees/`.
Use this skill when working on terminal UI rendering, interactive CLI prompts, ANSI color output, terminal screenshots, Bubble Tea/Lip Gloss/Huh/Glamour, curses-style apps, or CLI tests that depend on TTY behavior.
Use this skill when you're working with golang code.
Use whenever asked to create GitHub Pull Request.
Use when writing, rewriting, or updating a pull request description. Use this skill's explicit PR template unless the user provides a different template for the current task, keep the result concise and human-readable, explain why the change exists, and ask the user for motivation before updating the PR when the user has not explicitly provided it.
Use this skill whenever writing, editing, reviewing, or deciding whether to add code comments, docstrings, inline comments, or internal function documentation in any programming language. Use it even when the task only mentions "comments", "docs in code", "docstrings", "document this function", or "explain this helper".
| name | golang-testing |
| description | Use this skill when writing or modifying Go tests, benchmarks, or test helpers. Covers test style, modern testing APIs, and common mistakes. |
!grep -rh "^go " --include="go.mod" . 2>/dev/null | cut -d' ' -f2 | sort | uniq -c | sort -nr | head -1 | xargs | cut -d' ' -f2 | grep . || echo unknown
Use ONLY the version shown above to determine which testing APIs are available.
HARD RULE: Test functions MUST appear before any utility/helper functions.
Order test files top-to-bottom:
Test*, Benchmark*, Fuzz*, Example*)t.Helper())Never place utility or helper code above the tests that use it. The tests are what readers care about — helpers are implementation details.
TestXxx where Xxx does not start with a lowercase letter.
Underscores are allowed in test names (exception to general Go naming).
| Pattern | Use Case | Example |
|---|---|---|
TestFunction | Exported function | TestParse, TestMarshalJSON |
TestType_Method | Method on a type | TestReader_Read, TestConfig_Validate |
Test_unexported | Unexported function | Test_parseHeader, Test_resolve |
There is no enforced 1:1 mapping between test and production functions. Name tests for what they verify, not which function they call.
t.Run)Subtest names are slash-joined to the parent: TestParse/empty_input.
Spaces become underscores in output. Slashes create hierarchy levels.
// GOOD — describes the condition being tested:
t.Run("nil input", func(t *testing.T) { ... })
t.Run("duplicate keys", func(t *testing.T) { ... })
t.Run("trailing slash", func(t *testing.T) { ... })
// BAD:
t.Run("test 1", func(t *testing.T) { ... }) // meaningless number
t.Run("", func(t *testing.T) { ... }) // empty name
t.Run(fmt.Sprintf("%v", input), func(...) { ... }) // unreadable after escaping
The -run flag matches each /-separated level with an unanchored regex:
go test -run=TestParse/empty # parent + subtest
go test -run=TestParse/ # all subtests of TestParse
Always provide a descriptive name. Never use index-based identification. Prefer map-keyed tables when test order does not matter.
// GOOD — map key is the test name:
tests := map[string]struct {
in string
expected int
}{
"positive": {in: "42", expected: 42},
"zero": {in: "0", expected: 0},
"negative": {in: "-1", expected: -1},
}
// BAD — forces reader to count entries:
for i, tt := range tests {
t.Run(fmt.Sprintf("case_%d", i), func(t *testing.T) { ... })
}
Case name guidelines:
"nil map" not "returns error""empty slice", "UTF-8 multibyte"TestXxx already identifies it"case 1" forces counting to find failuresExample names are strictly enforced — they control where go doc renders them:
| Pattern | Documents |
|---|---|
Example() | Package |
ExampleFoo() | Function Foo |
ExampleBar_Baz() | Method Baz on type Bar |
ExampleFoo_suffix() | Additional example (suffix starts lowercase) |
| Anti-Pattern | Problem | Fix |
|---|---|---|
TestFoo1, TestFoo2 | Numbered — conveys nothing | TestFoo_NilInput, TestFoo_EmptySlice |
TestHappy | Too vague — happy path of what? | TestParse_ValidJSON |
TestParseParsesProperly | Repeats function name | TestParse or TestParse_ValidInput |
| Very long names | Unreadable in output | Shorten; use subtests for breakdown |
Testparse | Lowercase after Test | Won't be discovered by go test |
t.Run)t.Helper() in test helper functions
so failure lines point to the call site, not the helpert.Parallel() at the top of tests that are independenttestdata/ directory//go:build unit_test —
if present, include them in build and test commandsTable-driven tests are the default pattern for testing multiple inputs/outputs.
Every table test needs: a struct defining the test case,
a collection of named cases,
and a loop calling t.Run.
Prefer map-keyed tables when test execution order does not matter.
The map key is the test name — no name struct field needed.
Randomized iteration can expose order-dependent bugs.
func TestReverse(t *testing.T) {
tests := map[string]struct {
in string
expected string
}{
"empty": {in: "", expected: ""},
"single char": {in: "a", expected: "a"},
"palindrome": {in: "aba", expected: "aba"},
"ascii": {in: "hello", expected: "olleh"},
}
for name, tt := range tests {
t.Run(name, func(t *testing.T) {
got := Reverse(tt.in)
assert.Equal(t, tt.expected, got)
})
}
}
Use a slice when execution order matters (e.g. sequential dependencies).
Requires an explicit name field.
func TestMigrations(t *testing.T) {
tests := []struct {
name string
in string
expected string
}{
{name: "v1 to v2", in: "v1", expected: "v2"},
{name: "v2 to v3", in: "v2", expected: "v3"},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := Migrate(tt.in)
assert.Equal(t, tt.expected, got)
})
}
}
When testing both success and error paths, add an expectedErr field.
Use require for error checks before accessing the result.
func TestParsePort(t *testing.T) {
tests := map[string]struct {
in string
expected int
expectedErr string
}{
"valid": {in: "8080", expected: 8080},
"zero": {in: "0", expected: 0},
"negative": {in: "-1", expectedErr: "out of range"},
"not a number": {in: "abc", expectedErr: "invalid syntax"},
}
for name, tt := range tests {
t.Run(name, func(t *testing.T) {
got, err := ParsePort(tt.in)
if tt.expectedErr != "" {
require.ErrorContains(t, err, tt.expectedErr)
return
}
require.NoError(t, err)
assert.Equal(t, tt.expected, got)
})
}
}
When cases need individual setup, use a function field:
tests := []struct {
name string
setup func(t *testing.T) *Config
expected string
}{
{
name: "default config",
setup: func(t *testing.T) *Config {
return NewConfig()
},
expected: "localhost",
},
{
name: "custom host",
setup: func(t *testing.T) *Config {
c := NewConfig()
c.Host = "example.com"
return c
},
expected: "example.com",
},
}
Call t.Parallel() in both the parent and each subtest.
Capture the loop variable (required before Go 1.22):
func TestFetch(t *testing.T) {
t.Parallel()
tests := map[string]struct {
url string
expected int
}{
"ok": {url: "/ok", expected: 200},
"not found": {url: "/missing", expected: 404},
}
for name, tt := range tests {
t.Run(name, func(t *testing.T) {
t.Parallel()
got := fetch(tt.url)
assert.Equal(t, tt.expected, got)
})
}
}
Not everything belongs in a table. Avoid tables when:
nil per caseIn these situations, write separate TestXxx functions or
inline subtests instead of forcing a table structure.
assert vs requireUse github.com/stretchr/testify for assertions.
It provides two packages with identical APIs:
assert — logs the failure and continues the testrequire — logs the failure and stops the test immediately (t.FailNow())Rule: use require whenever subsequent code would panic or be meaningless
if the assertion fails.
Common cases where require is mandatory:
err == nil before using the resultfunc TestUserService(t *testing.T) {
user, err := service.CreateUser("alice")
require.NoError(t, err) // stop here if err != nil
require.NotNil(t, user) // stop here if user == nil
// safe to dereference now
assert.Equal(t, "alice", user.Name)
assert.NotEmpty(t, user.ID)
assert.True(t, user.CreatedAt.After(time.Time{}))
}
Contrast with using assert for everything — the test would panic on user.Name
if CreateUser returned nil, err:
// WRONG — panics if user is nil:
assert.NoError(t, err)
assert.Equal(t, "alice", user.Name) // nil dereference
Table-driven test with require:
func TestParse(t *testing.T) {
tests := map[string]struct {
input string
expected int
}{
"positive": {input: "42", expected: 42},
"zero": {input: "0", expected: 0},
}
for name, tt := range tests {
t.Run(name, func(t *testing.T) {
got, err := parse(tt.input)
require.NoError(t, err) // skip remaining assertions on failure
assert.Equal(t, tt.expected, got)
})
}
}
Common require/assert functions:
| Function | Use when |
|---|---|
require.NoError(t, err) | must succeed before using result |
require.NotNil(t, v) | must be non-nil before dereferencing |
require.Len(t, s, n) | must have length before indexing |
assert.Equal(t, expected, got) | value comparison |
assert.ErrorIs(t, err, target) | error chain check |
assert.Contains(t, s, sub) | substring / element presence |
assert.Eventually(t, cond, timeout, tick) | async condition |
t.Context() — test-scoped context:
func TestFoo(t *testing.T) {
ctx := t.Context() // canceled when test ends
result := doSomething(ctx)
}
ALWAYS use t.Context() instead of
context.WithCancel(context.Background()) in tests.
b.Loop() — benchmark main loop:
func BenchmarkFoo(b *testing.B) {
for b.Loop() {
doWork()
}
}
ALWAYS use b.Loop() instead of for i := 0; i < b.N; i++.
Prevents compiler over-optimization and runs setup/teardown only once.
t.Chdir(dir) — change working directory for test duration:
func TestConfig(t *testing.T) {
t.Chdir(t.TempDir())
// working directory is restored after test
}
testing/synctest — virtualized time for concurrent tests:
func TestTimeout(t *testing.T) {
synctest.Test(t, func(t *testing.T) {
ch := make(chan int)
go func() {
time.Sleep(5 * time.Second) // virtual, instant
ch <- 42
}()
synctest.Wait() // waits until all goroutines block
select {
case v := <-ch:
fmt.Println("got", v)
case <-time.After(10 * time.Second):
t.Fatal("timed out")
}
})
}
Key points:
synctest.Test(t, fn) runs fn in an isolated bubble with virtualized timesynctest.Wait() waits for all goroutines to reach a blocking statet.Attr() / t.Output() — structured test output:
func TestFeature(t *testing.T) {
t.Attr("issue", "PROJ-1234")
t.Attr("component", "auth")
log := slog.New(slog.NewTextHandler(t.Output(), nil))
log.Info("test log goes to test output, properly indented")
}
t.ArtifactDir() — store test output artifacts:
func TestGenerate(t *testing.T) {
dir := t.ArtifactDir()
os.WriteFile(filepath.Join(dir, "output.png"), data, 0644)
// run with: go test -v -artifacts -outputdir=/tmp/results
}
testing/cryptotest.SetGlobalRandom() — deterministic crypto in tests:
func TestCrypto(t *testing.T) {
cryptotest.SetGlobalRandom(t, 42) // seed for reproducibility
key, _ := ecdsa.GenerateKey(elliptic.P256(), nil)
}
context.Background() in tests (fixed in 1.24+):
// WRONG:
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
// RIGHT:
ctx := t.Context()
for i := 0; i < b.N; i++ in benchmarks (fixed in 1.24+):
// WRONG:
for i := 0; i < b.N; i++ { doWork() }
// RIGHT:
for b.Loop() { doWork() }
Vague or numbered test names:
// WRONG:
func TestParse1(t *testing.T) { ... }
func TestParse2(t *testing.T) { ... }
t.Run("test 1", func(t *testing.T) { ... })
// RIGHT:
func TestParse_ValidJSON(t *testing.T) { ... }
func TestParse_MalformedInput(t *testing.T) { ... }
t.Run("trailing comma", func(t *testing.T) { ... })
Index-based table test identification:
// WRONG:
for i, tt := range tests {
t.Run(fmt.Sprintf("case_%d", i), func(t *testing.T) { ... })
}
// RIGHT — always use a name field:
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) { ... })
}