Repository navigation
04 testing standards
omeyang edited this page Sep 16, 2026
·
1 revision
本文档定义 Go 项目的测试规范,包括 TDD 流程、覆盖率要求、Mock 策略和测试组织。
- 测试驱动开发(TDD) - 应该遵循"红-绿-重构"流程
- 高覆盖率 - 核心业务必须不低于 95%,整体必须不低于 90%
- 正确使用 Mock - 应该 Mock 外部依赖,禁止 Mock 核心业务逻辑
- 测试独立 - 测试必须互不依赖,必须可并行运行
- 测试清晰 - 测试应该作为文档,必须一看就懂
红(Red) -> 绿(Green) -> 重构(Refactor)
- 红(Red):先写测试,运行失败
- 绿(Green):写最简实现,让测试通过
- 重构(Refactor):优化代码,保持测试通过
| 误区 | 问题 | 改进方法 |
|---|---|---|
| 只写测试不重构 | 代码质量下降 | 必须重构实现代码 |
| 一次写完所有测试 | 偏离 TDD 流程 | 应该逐个测试编写 |
| 测试过于复杂 | 维护成本高 | 应该保持测试简单 |
| 为覆盖率而测试 | 测试无实际意义 | 必须测试行为而非代码行 |
| 代码类型 | 最低覆盖率 | 要求级别 |
|---|---|---|
| 核心业务逻辑 | 95% | MUST |
| API 层 | 90% | MUST |
| 工具函数 | 90% | SHOULD |
| 数据模型 | 80% | SHOULD |
| 配置管理 | 70% | SHOULD |
| 整体代码 | 90% | MUST |
-
main.go入口文件 - 自动生成的代码(protobuf、mock)
- 简单的 getter/setter
- 已 Mock 的外部依赖
go test ./... -cover -coverprofile=coverage.out
go tool cover -func=coverage.out
go tool cover -html=coverage.out -o coverage.html外部依赖(数据库、网络、消息队列、时间):
type MockRepository struct {
mock.Mock
}
func (m *MockRepository) FindByID(ctx context.Context, id string) (*Order, error) {
args := m.Called(ctx, id)
return args.Get(0).(*Order), args.Error(1)
}时间相关操作:
type TimeProvider interface {
Now() time.Time
}
type MockTimeProvider struct {
mock.Mock
}
func (m *MockTimeProvider) Now() time.Time {
return m.Called().Get(0).(time.Time)
}核心业务逻辑:
// 错误:Mock 核心业务逻辑
mockMatcher := new(MockMatcher)
mockMatcher.On("Match", ...).Return(expected, nil)
// 正确:测试真实业务逻辑
mockRepo := new(MockRepository) // 只 Mock 外部依赖
matcher := NewMatcher(mockRepo) // 使用真实业务逻辑
result, err := matcher.Match(ctx, criteria)简单数据结构和纯函数:
// 错误:Mock 简单结构体
type MockOrder struct { mock.Mock }
// 正确:直接使用真实结构体
order := &Order{ID: "001", Name: "test"}| 问题 | 影响 |
|---|---|
| 测试变成测 Mock | 未验证真实逻辑,测试失去意义 |
| 维护成本高 | 代码变更需同步修改大量 Mock |
| 假阳性 | 测试通过但实际代码有缺陷 |
internal/matcher/
+-- matcher.go # 业务代码
+-- matcher_test.go # 单元测试
+-- rule.go
+-- rule_test.go
+-- testdata/ # 测试数据
+-- orders.json
+-- rules.json
// 格式:Test<FunctionName>_<Scenario>
func TestFindByID_Success(t *testing.T) { ... }
func TestFindByID_NotFound(t *testing.T) { ... }
func TestFindByID_DatabaseError(t *testing.T) { ... }func TestMatcher_Match_Success(t *testing.T) {
// Given(准备测试数据和依赖)
mockRepo := new(MockRepository)
mockRepo.On("FindByID", mock.Anything, "001").
Return(&Order{ID: "001", GroupID: "g-01"}, nil)
matcher := NewMatcher(mockRepo)
// When(执行被测试的代码)
result, err := matcher.Match(context.Background(), &Criteria{ID: "001"})
// Then(验证结果)
assert.NoError(t, err)
assert.Equal(t, "g-01", result.GroupID)
mockRepo.AssertExpectations(t)
}func TestValidator_Validate(t *testing.T) {
tests := []struct {
name string
input *Order
wantErr bool
}{
{"valid order", &Order{ID: "001", Name: "test"}, false},
{"empty ID", &Order{Name: "test"}, true},
{"invalid name", &Order{ID: "001", Name: ""}, true},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
err := Validate(tt.input)
if tt.wantErr {
assert.Error(t, err)
} else {
assert.NoError(t, err)
}
})
}
}必须覆盖以下边界:
- 正常情况
- 空值/nil
- 零值
- 边界值(最大/最小)
- 错误情况
- 多个模块协作
- 涉及真实数据库操作
- 涉及消息队列
- 端到端业务流程
# 只运行单元测试
go test ./... -short
# 运行包括集成测试
go test ./... -tags=integration
# 运行特定集成测试
go test ./internal/matcher -tags=integration -run TestQueryFlow- 核心算法
- 频繁调用的函数
- 性能敏感的操作
func BenchmarkMatcher_Match(b *testing.B) {
mockRepo := new(MockRepository)
mockRepo.On("FindByID", mock.Anything, mock.Anything).
Return(&Order{ID: "001"}, nil)
matcher := NewMatcher(mockRepo)
criteria := &Criteria{ID: "001"}
b.ResetTimer()
for i := 0; i < b.N; i++ {
_, _ = matcher.Match(context.Background(), criteria)
}
}go test -bench=. ./...
go test -bench=. -benchmem ./...
go test -bench=. -cpuprofile=cpu.prof ./...
go tool pprof cpu.prof应该使用 testify/assert:
import (
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
// assert:失败后继续执行
assert.Equal(t, expected, actual)
// require:失败后立即终止
require.NoError(t, err)- 应该遵循 TDD 流程(红 -> 绿 -> 重构)
- 测试覆盖率达标(核心不低于 95%,整体不低于 90%)
- 应该使用 Given-When-Then 结构
- 测试命名清晰(Test<Function>_<Scenario>)
- 边界条件有覆盖
- 只 Mock 外部依赖
- 禁止 Mock 核心业务逻辑
- 禁止 Mock 简单结构体
- 应该验证 Mock 调用(AssertExpectations)
- 测试独立(可并行运行)
- 单元测试执行时间小于 1 秒
- 测试清晰(一看就懂)
- 无 flaky 测试
-
go test ./...全部通过 -
go test ./... -cover覆盖率达标 -
go test -bench=.性能无退化
- Go Testing 官方文档:https://pkg.go.dev/testing
- 表驱动测试:https://go.dev/wiki/TableDrivenTests
- testify:https://github.com/stretchr/testify
- Uber Go Style Guide(Testing):https://github.com/uber-go/guide
本页由 Maat 仓库的 scripts/sync-wiki.sh 自动生成,请勿直接编辑。