系统化定位 Go 程序根因,从复现到验证。
记忆
golang-testing
试用为 Go 代码产出可上生产的测试:表驱动用例、子测试命名、goleak 检漏、模糊测试与 CI 集成。
它能做什么
面向 Go 工程师的测试方法论,按 Write / Review / Audit / Debug 四种模式工作,把测试当作可执行规约来约束行为。覆盖表驱动子测试、用 build tag 隔离的集成测试、t.Parallel() 并发、goleak.VerifyTestMain 协程泄漏检测、Go 1.25+ 的 testing/synctest 确定性并发测试、模糊测试、Go 1.24+ 的 b.Loop() 基准测试,以及覆盖率采集流程。强调 mock 接口而非具体类型、单元测试控制在 1ms 内、不依赖用例执行顺序、不耦合实现细节等约束。
什么时候用它
- 为新增或存量 Go 代码补写测试
- 评审 PR 中的测试变更
- 审计现有测试套件的脆弱点与覆盖盲区
- 定位偶发或持续失败的 Go 测试
技能文档
Persona: You are a Go engineer who treats tests as executable specifications. You write tests to constrain behavior, not to hit coverage targets.
Thinking mode: Use ultrathink for test strategy design and failure analysis. Shallow reasoning misses edge cases and produces brittle tests that pass today but break tomorrow.
Modes:
- Write mode — generating new tests for existing or new code. Work sequentially through the code under test; use
goteststo scaffold table-driven tests, then enrich with edge cases and error paths. - Review mode — reviewing a PR's test changes. Focus on the diff: check coverage of new behaviour, assertion quality, table-driven structure, and absence of flakiness patterns. Sequential.
- Audit mode — auditing an existing test suite for gaps, flakiness, or bad patterns (order-dependent tests, missing
t.Parallel(), implementation-detail coupling). Launch up to 3 parallel sub-agents split by concern: (1) unit test quality and coverage gaps, (2) integration test isolation and build tags, (3) goroutine leaks and race conditions. - Debug mode — a test is failing or flaky. Work sequentially: reproduce reliably, isolate the failing assertion, trace the root cause in production code or test setup.
Community default. A company skill that explicitly supersedes
samber/cc-skills-golang@golang-testingskill takes precedence.
Dependencies:
- gotests:
go install github.com/cweill/gotests/gotests@latest
Go Testing Best Practices
This skill guides the creation of production-ready tests for Go applications. Follow these principles to write maintainable, fast, and reliable tests.
Best Practices Summary
- Table-driven tests MUST use named subtests -- every test case needs a
namefield passed tot.Run - Integration tests MUST use build tags (
//go:build integration) to separate from unit tests - Tests MUST NOT depend on execution order -- each test MUST be independently runnable
- Independent tests SHOULD use
t.Parallel()when possible - NEVER test implementation details -- test observable behavior and public API contracts
- Packages with goroutines SHOULD use
goleak.VerifyTestMaininTestMainto detect goroutine leaks - Use testify as helpers, not a replacement for standard library
- Mock interfaces, not concrete types
- Keep unit tests fast (< 1ms), use build tags for integration tests
- Run tests with race detection in CI
- Include examples as executable documentation
Test Structure and Organization
File Conventions
// package_test.go - tests in same package (white-box, access unexported)
package mypackage
// mypackage_test.go - tests in test package (black-box, public API only)
package mypackage_test
Naming Conventions
func TestAdd(t *testing.T) { ... } // function test
func TestMyStruct_MyMethod(t *testing.T) { ... } // method test
func BenchmarkAdd(b *testing.B) { ... } // benchmark
func ExampleAdd() { ... } // example
func FuzzAdd(f *testing.F) { ... } // fuzz test
Table-Driven Tests
Table-driven tests are the idiomatic Go way to test multiple scenarios. Always name each test case.
func TestCalculatePrice(t *testing.T) {
tests := []struct {
name string
quantity int
unitPrice float64
expected float64
}{
{
name: "single item",
quantity: 1,
unitPrice: 10.0,
expected: 10.0,
},
{
name: "bulk discount - 100 items",
quantity: 100,
unitPrice: 10.0,
expected: 900.0, // 10% discount
},
{
name: "zero quantity",
quantity: 0,
unitPrice: 10.0,
expected: 0.0,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := CalculatePrice(tt.quantity, tt.unitPrice)
if got != tt.expected {
t.Errorf("CalculatePrice(%d, %.2f) = %.2f, want %.2f",
tt.quantity, tt.unitPrice, got, tt.expected)
}
})
}
}
Unit Tests
Unit tests should be fast (< 1ms), isolated (no external dependencies), and deterministic.
Testing HTTP Handlers
Use httptest for handler tests with table-driven patterns. See HTTP Testing for examples with request/response bodies, query parameters, headers, and status code assertions.
Goroutine Leak Detection with goleak
Use go.uber.org/goleak to detect leaking goroutines, especially for concurrent code:
import (
"testing"
"go.uber.org/goleak"
)
func TestMain(m *testing.M) {
goleak.VerifyTestMain(m)
}
To exclude specific goroutine stacks (for known leaks or library goroutines):
func TestMain(m *testing.M) {
goleak.VerifyTestMain(m,
goleak.IgnoreCurrent(),
)
}
Or per-test:
func TestWorkerPool(t *testing.T) {
defer goleak.VerifyNone(t)
// ... test code ...
}
testing/synctest for Deterministic Goroutine Testing
testing/synctest (Go 1.25+) provides deterministic tests for goroutines, timers, deadlines, and context cancellation. Time advances only when all goroutines are blocked, making ordering predictable.
When to use synctest instead of real time:
- Testing concurrent code with time-based operations (time.Sleep, time.After, time.Ticker)
- When race conditions need to be reproducible
- When tests are flaky due to timing issues
import (
"context"
"testing"
"testing/synctest"
"time"
)
func TestContextTimeout(t *testing.T) {
synctest.Test(t, func(t *testing.T) {
const timeout = 5 * time.Second
ctx, cancel := context.WithTimeout(t.Context(), timeout)
defer cancel()
time.Sleep(timeout - time.Nanosecond)
synctest.Wait()
if err := ctx.Err(); err != nil {
t.Fatalf("before timeout: %v", err)
}
time.Sleep(time.Nanosecond)
synctest.Wait()
if err := ctx.Err(); err != context.DeadlineExceeded {
t.Fatalf("after timeout: got %v, want DeadlineExceeded", err)
}
})
}
Use synctest.Test in Go 1.25+ and Go 1.26+. Do not use the old Go 1.24 experimental synctest.Run API in Go 1.25+ or Go 1.26+ code. If a module explicitly targets Go 1.24 and opts into GOEXPERIMENT=synctest, use the old API only as a compatibility fallback.
Key differences in synctest:
time.Sleepadvances synthetic time instantly when the goroutine blockstime.Afterfires when synthetic time reaches the duration- All goroutines run to blocking points before time advances
- Test execution is deterministic and repeatable
Test Timeouts
For tests that may hang, use a timeout helper that panics with caller location. See Helpers.
Benchmarks
→ See samber/cc-skills-golang@golang-benchmark skill for advanced benchmarking: b.Loop() (Go 1.24+), benchstat, profiling from benchmarks, and CI regression detection.
Write benchmarks to measure performance and detect regressions:
func BenchmarkStringConcatenation(b *testing.B) {
b.Run("plus-operator", func(b *testing.B) {
for b.Loop() {
result := "a" + "b" + "c"
_ = result
}
})
b.Run("strings.Builder", func(b *testing.B) {
for b.Loop() {
var builder strings.Builder
builder.WriteString("a")
builder.WriteString("b")
builder.WriteString("c")
_ = builder.String()
}
})
}
Benchmarks with different input sizes:
func BenchmarkFibonacci(b *testing.B) {
sizes := []int{10, 20, 30}
for _, size := range sizes {
b.Run(fmt.Sprintf("n=%d", size), func(b *testing.B) {
b.ReportAllocs()
for b.Loop() {
Fibonacci(size)
}
})
}
}
For Go 1.24+, new benchmarks should use b.Loop(). Use legacy b.N loops only when the module targets Go <1.24 or when preserving old benchmark code intentionally.
Go 1.26+: test artifacts
When a test, benchmark, or fuzz target needs to persist files for inspection, use ArtifactDir() instead of ad-hoc paths or repo-local output.
func TestRenderGoldenArtifact(t *testing.T) {
dir := t.ArtifactDir()
out := filepath.Join(dir, "rendered.json")
if err := os.WriteFile(out, renderedBytes, 0o644); err != nil {
t.Fatal(err)
}
t.Logf("artifact written: %s", out)
}
Available on *testing.T, *testing.B, and *testing.F in Go 1.26+.
Parallel Tests
Use t.Parallel() to run tests concurrently:
func TestParallelOperations(t *testing.T) {
tests := []struct {
name string
data []byte
}{
{"small data", make([]byte, 1024)},
{"medium data", make([]byte, 1024*1024)},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
is := assert.New(t)
result := Process(tt.data)
is.NotNil(result)
})
}
}
Fuzzing
Use fuzzing to find edge cases and bugs:
func FuzzReverse(f *testing.F) {
f.Add("hello")
f.Add("")
f.Add("a")
f.Fuzz(func(t *testing.T, input string) {
reversed := Reverse(input)
doubleReversed := Reverse(reversed)
if input != doubleReversed {
t.Errorf("Reverse(Reverse(%q)) = %q, want %q", input, doubleReversed, input)
}
})
}
Examples as Documentation
Examples are executable documentation verified by go test:
func ExampleCalculatePrice() {
price := CalculatePrice(100, 10.0)
fmt.Printf("Price: %.2f\n", price)
// Output: Price: 900.00
}
func ExampleCalculatePrice_singleItem() {
price := CalculatePrice(1, 25.50)
fmt.Printf("Price: %.2f\n", price)
// Output: Price: 25.50
}
Code Coverage
# Generate coverage file
go test -coverprofile=coverage.out ./...
# View coverage in HTML
go tool cover -html=coverage.out
# Coverage by function
go tool cover -func=coverage.out
# Total coverage percentage
go tool cover -func=coverage.out | grep total
Integration Tests
Use build tags to separate integration tests from unit tests:
//go:build integration
package mypackage
func TestDatabaseIntegration(t *testing.T) {
db, err := sql.Open("postgres", os.Getenv("DATABASE_URL"))
if err != nil {
t.Fatal(err)
}
defer db.Close()
// Test real database operations
}
Run integration tests separately:
go test -tags=integration ./...
For Docker Compose fixtures, SQL schemas, and integration test suites, see Integration Testing.
Mocking
Mock interfaces, not concrete types. Define interfaces where consumed, then create mock implementations.
For mock patterns, test fixtures, and time mocking, see Mocking.
Enforce with Linters
Many test best practices are enforced automatically by linters: thelper, paralleltest, testifylint. See the samber/cc-skills-golang@golang-lint skill for configuration and usage.
Cross-References
- -> See
samber/cc-skills-golang@golang-stretchr-testifyskill for detailed testify API (assert, require, mock, suite) - -> See
samber/cc-skills-golang@golang-databaseskill (testing.md) for database integration test patterns - -> See
samber/cc-skills-golang@golang-concurrencyskill for goroutine leak detection with goleak - -> See
samber/cc-skills-golang@golang-continuous-integrationskill for CI test configuration and GitHub Actions workflows - -> See
samber/cc-skills-golang@golang-lintskill for testifylint and paralleltest configuration - -> See
samber/cc-skills-golang@golang-continuous-integrationskill for automated AI-driven code review in CI using these guidelines
Quick Reference
go test ./... # all tests
go test -run TestName ./... # specific test by exact name
go test -run TestName/subtest ./... # subtests within a test
go test -run 'Test(Add|Sub)' ./... # multiple tests (regexp OR)
go test -run 'Test[A-Z]' ./... # tests starting with capital letter
go test -run 'TestUser.*' ./... # tests matching prefix
go test -run '.*Validation.*' ./... # tests containing substring
go test -run TestName/. ./... # all subtests of TestName
go test -run '/(unit|integration)' ./... # filter by subtest name
go test -race ./... # race detection
go test -cover ./... # coverage summary
go test -bench=. -benchmem ./... # benchmarks
go test -fuzz=FuzzName ./... # fuzzing
go test -tags=integration ./... # integration tests
常见问题
- 这个技能会深入讲 testify 吗?
- 把它定位为标准库的辅助工具,assert、require、mock、suite 等 API 细节请参考专门的 testify 技能。
- 集成测试怎么组织?
- 通过 //go:build integration build tag 与单元测试分离,使用 `go test -tags=integration ./...` 单独运行,避免拖慢日常单测。
- 能检测协程泄漏吗?
- 推荐在 TestMain 中接入 go.uber.org/goleak.VerifyTestMain 做包级兜底,并在涉及并发的子测试中用 VerifyNone 逐项检查。
- 对付偶发或慢测试有什么建议?
- Debug 模式先稳定复现、再定位失败断言;依赖时间或并发的脆弱测试可借助 Go 1.25+ 的 testing/synctest 让执行顺序可重现。
相关技能
用统计严谨的方式测量 Go 性能 —— 写基准、剖析热点、在合并前捕获回归。
Golang skills orchestrator — always active on any Golang coding, review, debug, or setup task. Reads the task context and loads the most relevant skills from samber/cc-skills-golang, often multiple at once: writing a gRPC service loads golang-grpc + golang-testing + golang-error-handling; debugging a panic loads golang-troubleshooting + golang-safety; auditing security loads golang-security + golang-lint + golang-safety. Also: disambiguates competing clusters when two skills seem to overlap (performance vs benchmark vs troubleshooting, samber/lo vs mo vs ro, DI cluster, safety vs security), and configures the project's agent-config file (CLAUDE.md, AGENTS.md, GEMINI.md, Cursor rules, or Copilot instructions) to force-trigger skills in a project (/golang-how-to configure).
编写并审查使用 testify 的 Go 测试,合理使用断言、Mock 和测试套件。
把已通过 profiling 定位的 Go 瓶颈映射到对应优化模式,采用先测量后改动的迭代方法。
为需要人工判断的 Go 风格问题提供规则:换行、变量声明、控制流与可读性,补齐 lint 工具覆盖不到的判断。