golang-testing
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChinesePersona: 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 for test strategy design and failure analysis. Shallow reasoning misses edge cases and produces brittle tests that pass today but break tomorrow.
ultrathinkModes:
- Write mode — generating new tests for existing or new code. Work sequentially through the code under test; use to scaffold table-driven tests, then enrich with edge cases and error paths.
gotests - 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 , 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.
t.Parallel() - 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 supersedesskill takes precedence.samber/cc-skills-golang@golang-testing
角色定位:你是一名将测试视为可执行规范的Go工程师。编写测试是为了约束代码行为,而非单纯达到覆盖率指标。
思考模式:使用进行测试策略设计和故障分析。浅层推理会遗漏边缘情况,导致测试在当前能通过,但后续容易失效。
ultrathink模式说明:
- 编写模式——为现有或新增代码生成测试用例。按顺序处理被测试代码;使用快速搭建表格驱动测试的框架,然后补充边缘情况和错误路径的测试。
gotests - 评审模式——评审PR中的测试变更。重点关注差异:检查新功能的覆盖情况、断言质量、表格驱动结构,以及是否存在导致测试不稳定的模式。按顺序执行评审。
- 审计模式——审计现有测试套件的漏洞、不稳定性或不良模式(如依赖执行顺序的测试、未使用、与实现细节耦合等)。最多可启动3个并行子代理,按关注点拆分:(1) 单元测试质量与覆盖率缺口,(2) 集成测试隔离与构建标签,(3) Goroutine泄漏与竞态条件。
t.Parallel() - 调试模式——测试失败或不稳定时。按顺序操作:先稳定复现问题,定位失败的断言,追踪生产代码或测试设置中的根本原因。
社区默认规则:如果公司内部有明确替代的技能,则以该技能为准。samber/cc-skills-golang@golang-testing
Go Testing Best Practices
Go测试最佳实践
This skill guides the creation of production-ready tests for Go applications. Follow these principles to write maintainable, fast, and reliable tests.
本文指导你为Go应用编写可用于生产环境的测试。遵循以下原则,编写可维护、快速且可靠的测试。
Best Practices Summary
最佳实践摘要
- Table-driven tests MUST use named subtests -- every test case needs a field passed to
namet.Run - Integration tests MUST use build tags () to separate from unit tests
//go:build integration - Tests MUST NOT depend on execution order -- each test MUST be independently runnable
- Independent tests SHOULD use when possible
t.Parallel() - NEVER test implementation details -- test observable behavior and public API contracts
- Packages with goroutines SHOULD use in
goleak.VerifyTestMainto detect goroutine leaksTestMain - 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
- 表格驱动测试必须使用命名子测试——每个测试用例都需要一个字段传入
namet.Run - 集成测试必须使用构建标签()与单元测试分离
//go:build integration - 测试不得依赖执行顺序——每个测试都必须可独立运行
- 独立测试应尽可能使用
t.Parallel() - 绝对不要测试实现细节——仅测试可观察的行为和公开API契约
- 包含Goroutine的包应在中使用
TestMain来检测Goroutine泄漏goleak.VerifyTestMain - 将testify作为辅助工具,而非标准库的替代品
- Mock接口,而非具体类型
- 保持单元测试快速(<1毫秒),使用构建标签区分集成测试
- 在CI中启用竞态检测来运行测试
- 将示例代码作为可执行文档
Test Structure and Organization
测试结构与组织
File Conventions
文件命名规范
go
// 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_testgo
// package_test.go - 与源码同包的测试(白盒测试,可访问未导出成员)
package mypackage
// mypackage_test.go - 独立测试包的测试(黑盒测试,仅测试公开API)
package mypackage_testNaming Conventions
命名约定
go
func TestAdd(t *testing.T) { ... } // function test
func TestMyStruct_MyMethod(t *testing.T) { ... } // method test
func BenchmarkAdd(b *testing.B) { ... } // benchmark
func ExampleAdd() { ... } // examplego
func TestAdd(t *testing.T) { ... } // 函数测试
func TestMyStruct_MyMethod(t *testing.T) { ... } // 方法测试
func BenchmarkAdd(b *testing.B) { ... } // 基准测试
func ExampleAdd() { ... } // 示例代码Table-Driven Tests
表格驱动测试
Table-driven tests are the idiomatic Go way to test multiple scenarios. Always name each test case.
go
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)
}
})
}
}表格驱动测试是Go语言中测试多场景的惯用方式,务必为每个测试用例命名。
go
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.
单元测试应快速(<1毫秒)、隔离(无外部依赖)且结果可预测。
Testing HTTP Handlers
HTTP处理器测试
Use for handler tests with table-driven patterns. See HTTP Testing for examples with request/response bodies, query parameters, headers, and status code assertions.
httptest使用结合表格驱动模式测试处理器。可参考HTTP测试中的示例,包含请求/响应体、查询参数、请求头和状态码断言等内容。
httptestGoroutine Leak Detection with goleak
使用goleak检测Goroutine泄漏
Use to detect leaking goroutines, especially for concurrent code:
go.uber.org/goleakgo
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):
go
func TestMain(m *testing.M) {
goleak.VerifyTestMain(m,
goleak.IgnoreCurrent(),
)
}Or per-test:
go
func TestWorkerPool(t *testing.T) {
defer goleak.VerifyNone(t)
// ... test code ...
}使用检测泄漏的Goroutine,尤其是在并发代码中:
go.uber.org/goleakgo
import (
"testing"
"go.uber.org/goleak"
)
func TestMain(m *testing.M) {
goleak.VerifyTestMain(m)
}如需排除特定Goroutine栈(针对已知泄漏或库级Goroutine):
go
func TestMain(m *testing.M) {
goleak.VerifyTestMain(m,
goleak.IgnoreCurrent(),
)
}或针对单个测试:
go
func TestWorkerPool(t *testing.T) {
defer goleak.VerifyNone(t)
// ... 测试代码 ...
}testing/synctest for Deterministic Goroutine Testing
使用testing/synctest进行确定性Goroutine测试
Experimental:is not yet covered by Go's compatibility guarantee. Its API may change in future releases. For stable alternatives, usetesting/synctest(see Mocking).clockwork
testing/synctestWhen to use instead of real time:
synctest- 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
go
import (
"testing"
"time"
"testing/synctest"
"github.com/stretchr/testify/assert"
)
func TestChannelTimeout(t *testing.T) {
synctest.Run(func(t *testing.T) {
is := assert.New(t)
ch := make(chan int, 1)
go func() {
time.Sleep(50 * time.Millisecond)
ch <- 42
}()
select {
case v := <-ch:
is.Equal(42, v)
case <-time.After(100 * time.Millisecond):
t.Fatal("timeout occurred")
}
})
}Key differences in :
synctest- advances synthetic time instantly when the goroutine blocks
time.Sleep - fires when synthetic time reaches the duration
time.After - All goroutines run to blocking points before time advances
- Test execution is deterministic and repeatable
实验性功能:尚未纳入Go的兼容性保障范围,其API可能在未来版本中变更。如需稳定替代方案,请使用testing/synctest(参考Mocking)。clockwork
testing/synctest适合使用而非真实时间的场景:
synctest- 测试包含基于时间操作的并发代码(time.Sleep、time.After、time.Ticker)
- 需要可复现的竞态条件
- 测试因时序问题导致不稳定
go
import (
"testing"
"time"
"testing/synctest"
"github.com/stretchr/testify/assert"
)
func TestChannelTimeout(t *testing.T) {
synctest.Run(func(t *testing.T) {
is := assert.New(t)
ch := make(chan int, 1)
go func() {
time.Sleep(50 * time.Millisecond)
ch <- 42
}()
select {
case v := <-ch:
is.Equal(42, v)
case <-time.After(100 * time.Millisecond):
t.Fatal("timeout occurred")
}
})
}synctest- 会在Goroutine阻塞时立即推进虚拟时间
time.Sleep - 会在虚拟时间达到指定时长时触发
time.After - 所有Goroutine都进入阻塞状态后,时间才会推进
- 测试执行具有确定性且可重复
Test Timeouts
测试超时
For tests that may hang, use a timeout helper that panics with caller location. See Helpers.
对于可能挂起的测试,使用包含调用者位置的超时辅助工具。参考Helpers。
Benchmarks
基准测试
→ See skill for advanced benchmarking: (Go 1.24+), , profiling from benchmarks, and CI regression detection.
samber/cc-skills-golang@golang-benchmarkb.Loop()benchstatWrite benchmarks to measure performance and detect regressions:
go
func BenchmarkStringConcatenation(b *testing.B) {
b.Run("plus-operator", func(b *testing.B) {
for i := 0; i < b.N; i++ {
result := "a" + "b" + "c"
_ = result
}
})
b.Run("strings.Builder", func(b *testing.B) {
for i := 0; i < b.N; i++ {
var builder strings.Builder
builder.WriteString("a")
builder.WriteString("b")
builder.WriteString("c")
_ = builder.String()
}
})
}Benchmarks with different input sizes:
go
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 i := 0; i < b.N; i++ {
Fibonacci(size)
}
})
}
}→ 如需高级基准测试内容,请参考技能:(Go 1.24+)、、基于基准测试的性能分析以及CI中的回归检测。
samber/cc-skills-golang@golang-benchmarkb.Loop()benchstat编写基准测试以衡量性能并检测回归:
go
func BenchmarkStringConcatenation(b *testing.B) {
b.Run("plus-operator", func(b *testing.B) {
for i := 0; i < b.N; i++ {
result := "a" + "b" + "c"
_ = result
}
})
b.Run("strings.Builder", func(b *testing.B) {
for i := 0; i < b.N; i++ {
var builder strings.Builder
builder.WriteString("a")
builder.WriteString("b")
builder.WriteString("c")
_ = builder.String()
}
})
}不同输入规模的基准测试:
go
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 i := 0; i < b.N; i++ {
Fibonacci(size)
}
})
}
}Parallel Tests
并行测试
Use to run tests concurrently:
t.Parallel()go
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)
})
}
}使用并发运行测试:
t.Parallel()go
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:
go
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)
}
})
}使用模糊测试发现边缘情况和Bug:
go
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 testgo
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
}示例代码是可执行的文档,会被验证:
go testgo
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
代码覆盖率
bash
undefinedbash
undefinedGenerate coverage file
生成覆盖率文件
go test -coverprofile=coverage.out ./...
go test -coverprofile=coverage.out ./...
View coverage in HTML
以HTML形式查看覆盖率
go tool cover -html=coverage.out
go tool cover -html=coverage.out
Coverage by function
按函数查看覆盖率
go tool cover -func=coverage.out
go tool cover -func=coverage.out
Total coverage percentage
查看总覆盖率百分比
go tool cover -func=coverage.out | grep total
undefinedgo tool cover -func=coverage.out | grep total
undefinedIntegration Tests
集成测试
Use build tags to separate integration tests from unit tests:
go
//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:
bash
go test -tags=integration ./...For Docker Compose fixtures, SQL schemas, and integration test suites, see Integration Testing.
使用构建标签将集成测试与单元测试分离:
go
//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()
// 测试真实数据库操作
}单独运行集成测试:
bash
go test -tags=integration ./...如需Docker Compose测试夹具、SQL Schema和集成测试套件相关内容,请参考集成测试。
Mocking
Mocking
Mock interfaces, not concrete types. Define interfaces where consumed, then create mock implementations.
For mock patterns, test fixtures, and time mocking, see Mocking.
Mock接口,而非具体类型。在消费端定义接口,然后创建Mock实现。
如需Mock模式、测试夹具和时间Mock相关内容,请参考Mocking。
Enforce with Linters
使用Linter强制执行规范
Many test best practices are enforced automatically by linters: , , . See the skill for configuration and usage.
thelperparalleltesttestifylintsamber/cc-skills-golang@golang-linter许多测试最佳实践可通过Linter自动强制执行:、、。配置和使用方法请参考技能。
thelperparalleltesttestifylintsamber/cc-skills-golang@golang-linterCross-References
交叉引用
- -> See skill for detailed testify API (assert, require, mock, suite)
samber/cc-skills-golang@golang-stretchr-testify - -> See skill (testing.md) for database integration test patterns
samber/cc-skills-golang@golang-database - -> See skill for goroutine leak detection with goleak
samber/cc-skills-golang@golang-concurrency - -> See skill for CI test configuration and GitHub Actions workflows
samber/cc-skills-golang@golang-continuous-integration - -> See skill for testifylint and paralleltest configuration
samber/cc-skills-golang@golang-linter
- → 如需testify API的详细内容(assert、require、mock、suite),请参考技能
samber/cc-skills-golang@golang-stretchr-testify - → 如需数据库集成测试模式,请参考技能的testing.md文档
samber/cc-skills-golang@golang-database - → 如需使用goleak检测Goroutine泄漏的内容,请参考技能
samber/cc-skills-golang@golang-concurrency - → 如需CI测试配置和GitHub Actions工作流,请参考技能
samber/cc-skills-golang@golang-continuous-integration - → 如需testifylint和paralleltest的配置,请参考技能
samber/cc-skills-golang@golang-linter
Quick Reference
快速参考
bash
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 testsbash
go test ./... # 运行所有测试
go test -run TestName ./... # 运行指定名称的测试
go test -run TestName/subtest ./... # 运行指定测试下的子测试
go test -run 'Test(Add|Sub)' ./... # 运行多个测试(正则或匹配)
go test -run 'Test[A-Z]' ./... # 运行以大写字母开头的测试
go test -run 'TestUser.*' ./... # 运行匹配前缀的测试
go test -run '.*Validation.*' ./... # 运行包含指定子串的测试
go test -run TestName/. ./... # 运行指定测试的所有子测试
go test -run '/(unit|integration)' ./... # 按子测试名称过滤
go test -race ./... # 启用竞态检测运行测试
go test -cover ./... # 查看覆盖率摘要
go test -bench=. -benchmem ./... # 运行基准测试
go test -fuzz=FuzzName ./... # 运行模糊测试
go test -tags=integration ./... # 运行集成测试