
golang-testing
热门生产级 Go 测试——表格驱动测试、testify 套件与模拟、并行测试、模糊测试、测试夹具、基于 goleak 的协程泄漏检测、快照测试、代码覆盖率、集成测试、惯用测试命名。在编写或审查 Go 测试、选择测试方法、配置 Go 测试 CI、或调试不稳定/慢速测试时使用。有关 testify 特定 API,请参见 `samber/cc-skills-golang@golang-stretchr-testify`;有关测量方法,请参见 `samber/cc-skills-golang@golang-benchmark`。
生产级 Go 测试——表格驱动测试、testify 套件与模拟、并行测试、模糊测试、测试夹具、基于 goleak 的协程泄漏检测、快照测试、代码覆盖率、集成测试、惯用测试命名。在编写或审查 Go 测试、选择测试方法、配置 Go 测试 CI、或调试不稳定/慢速测试时使用。有关 testify 特定 API,请参见 `samber/cc-skills-golang@golang-stretchr-testify`;有关测量方法,请参见 `samber/cc-skills-golang@golang-benchmark`。
角色: 你是一位将测试视为可执行规范的 Go 工程师。你编写测试是为了约束行为,而不是为了达到覆盖率目标。
思考模式: 使用 ultrathink 进行测试策略设计和失败分析。浅层推理会遗漏边界情况,产生今天通过但明天就失效的脆弱测试。
模式:
- 编写模式 — 为现有或新代码生成新测试。按顺序处理被测代码;使用
gotests搭建表格驱动测试框架,然后补充边界情况和错误路径。 - 审查模式 — 审查 PR 中的测试变更。关注差异:检查新行为的覆盖率、断言质量、表格驱动结构以及是否缺乏不稳定模式。按顺序进行。
- 审计模式 — 审计现有测试套件中的缺口、不稳定或不良模式(顺序依赖测试、缺少
t.Parallel()、实现细节耦合)。按关注点启动最多 3 个并行子代理:(1) 单元测试质量和覆盖率缺口,(2) 集成测试隔离和构建标签,(3) 协程泄漏和竞态条件。 - 调试模式 — 测试失败或不稳定。按顺序工作:可靠地复现,隔离失败的断言,在生产代码或测试设置中追溯根本原因。
社区默认。 明确覆盖
samber/cc-skills-golang@golang-testing技能的公司技能优先。
依赖:
- gotests:
go install github.com/cweill/gotests/gotests@latest
Go 测试最佳实践
本技能指导为 Go 应用程序创建生产级测试。遵循以下原则编写可维护、快速且可靠的测试。
最佳实践总结
- 表格驱动测试必须使用命名子测试——每个测试用例需要一个传递给
t.Run的name字段 - 集成测试必须使用构建标签(
//go:build integration)与单元测试分离 - 测试不得依赖执行顺序——每个测试必须可独立运行
- 独立的测试应尽可能使用
t.Parallel() - 绝不测试实现细节——测试可观察行为和公共 API 契约
- 包含协程的包应在
TestMain中使用goleak.VerifyTestMain检测协程泄漏 - 将 testify 作为辅助工具,而非标准库的替代品
- 模拟接口,而非具体类型
- 保持单元测试快速(< 1ms),使用构建标签进行集成测试
- 在 CI 中启用竞态检测运行测试
- 包含示例作为可执行文档
测试结构与组织
文件约定
// package_test.go - 同一包内的测试(白盒,可访问未导出内容)
package mypackage
// mypackage_test.go - 测试包内的测试(黑盒,仅公共 API)
package mypackage_test
命名约定
func TestAdd(t *testing.T) { ... } // 函数测试
func TestMyStruct_MyMethod(t *testing.T) { ... } // 方法测试
func BenchmarkAdd(b *testing.B) { ... } // 基准测试
func ExampleAdd() { ... } // 示例
func FuzzAdd(f *testing.F) { ... } // 模糊测试
表格驱动测试
表格驱动测试是 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)
}
})
}
}
单元测试
单元测试应快速(< 1ms)、隔离(无外部依赖)且确定性。
测试 HTTP 处理器
使用 httptest 进行处理器测试,采用表格驱动模式。有关请求/响应体、查询参数、头部和状态码断言的示例,请参见 HTTP 测试。
使用 goleak 检测协程泄漏
使用 go.uber.org/goleak 检测泄漏的协程,尤其适用于并发代码:
import (
"testing"
"go.uber.org/goleak"
)
func TestMain(m *testing.M) {
goleak.VerifyTestMain(m)
}
排除特定协程栈(针对已知泄漏或库协程):
func TestMain(m *testing.M) {
goleak.VerifyTestMain(m,
goleak.IgnoreCurrent(),
)
}
或按测试排除:
func TestWorkerPool(t *testing.T) {
defer goleak.VerifyNone(t)
// ... 测试代码 ...
}
testing/synctest 用于确定性协程测试
testing/synctest(Go 1.25+)为协程、定时器、截止时间和上下文取消提供确定性测试。时间仅在所有协程阻塞时前进,使顺序可预测。
何时使用 synctest 而非真实时间:
- 测试包含基于时间操作(time.Sleep, time.After, time.Ticker)的并发代码
- 当竞态条件需要可复现时
- 当测试因时序问题而不稳定时
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)
}
})
}
在 Go 1.25+ 和 Go 1.26+ 中使用 synctest.Test。不要在 Go 1.25+ 或 Go 1.26+ 代码中使用旧的 Go 1.24 实验性 synctest.Run API。如果模块明确针对 Go 1.24 并启用了 GOEXPERIMENT=synctest,则仅作为兼容性回退使用旧 API。
synctest 的关键区别:
time.Sleep在协程阻塞时立即推进合成时间time.After在合成时间到达持续时间时触发- 所有协程运行到阻塞点后时间才推进
- 测试执行是确定性和可重复的
测试超时
对于可能挂起的测试,使用一个在调用者位置引发恐慌的超时辅助函数。请参见 辅助函数。
基准测试
→ 有关高级基准测试:b.Loop()(Go 1.24+)、benchstat、从基准测试中分析以及 CI 回归检测,请参见 samber/cc-skills-golang@golang-benchmark 技能。
编写基准测试以衡量性能并检测回归:
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()
}
})
}
不同输入大小的基准测试:
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)
}
})
}
}
对于 Go 1.24+,新基准测试应使用 b.Loop()。仅当模块目标为 Go <1.24 或有意保留旧基准测试代码时,才使用传统的 b.N 循环。
Go 1.26+:测试工件
当测试、基准测试或模糊测试目标需要持久化文件以供检查时,使用 ArtifactDir() 而非临时路径或仓库本地输出。
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)
}
在 Go 1.26+ 中可用于 *testing.T、*testing.B 和 *testing.F。
并行测试
使用 t.Parallel() 并发运行测试:
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)
})
}
}
模糊测试
使用模糊测试发现边界情况和错误:
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)
}
})
}
示例作为文档
示例是由 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
}
代码覆盖率
# 生成覆盖率文件
go test -coverprofile=coverage.out ./...
# 以 HTML 形式查看覆盖率
go tool cover -html=coverage.out
# 按函数查看覆盖率
go tool cover -func=coverage.out
# 总覆盖率百分比
go tool cover -func=coverage.out | grep total
集成测试
使用构建标签将集成测试与单元测试分离:
//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()
// 测试真实数据库操作
}
单独运行集成测试:
go test -tags=integration ./...
有关 Docker Compose 测试夹具、SQL 模式和集成测试套件,请参见 集成测试。
模拟
模拟接口,而非具体类型。在使用处定义接口,然后创建模拟实现。
有关模拟模式、测试夹具和时间模拟,请参见 模拟。
使用 Linter 强制执行
许多测试最佳实践由 linter 自动强制执行:thelper、paralleltest、testifylint。有关配置和使用,请参见 samber/cc-skills-golang@golang-lint 技能。
交叉引用
- -> 有关详细的 testify API(assert、require、mock、suite),请参见
samber/cc-skills-golang@golang-stretchr-testify技能 - -> 有关数据库集成测试模式,请参见
samber/cc-skills-golang@golang-database技能(testing.md) - -> 有关使用 goleak 检测协程泄漏,请参见
samber/cc-skills-golang@golang-concurrency技能 - -> 有关 CI 测试配置和 GitHub Actions 工作流,请参见
samber/cc-skills-golang@golang-continuous-integration技能 - -> 有关 testifylint 和 paralleltest 配置,请参见
samber/cc-skills-golang@golang-lint技能 - -> 有关使用这些指南在 CI 中进行自动化 AI 驱动代码审查,请参见
samber/cc-skills-golang@golang-continuous-integration技能
快速参考
go test ./... # 所有测试
go test -run TestName ./... # 按精确名称运行特定测试
go test -run TestName/subtest ./... # 测试内的子测试
go test -run 'Test(Add|Sub)' ./... # 多个测试(正则 OR)
go test -run 'Test[A-Z]' ./... # 以大写字母开头的测试
go test -run 'TestUser.*' ./... # 匹配前缀的测试
go test -run '.*Validation.*' ./... # 包含子字符串的测试
go test -run TestName/. ./... # TestName 的所有子测试
go test -run '/(unit|integration)' ./... # 按子测试名称过滤
go test -race ./... # 竞态检测
go test -cover ./... # 覆盖率摘要
go test -bench=. -benchmem ./... # 基准测试
go test -fuzz=FuzzName ./... # 模糊测试
go test -tags=integration ./... # 集成测试





