井云服务中心后端测试规范
适用对象:井云服务中心后端服务(Go / Kratos / Ent / 微服务架构)
目标:建立统一、可维护、可自动化的测试规范,确保测试代码长期可读、可演进,并可稳定地由 AI 与人协同产出。
请严格遵守以下所有规范、限制和最佳实践,确保测试代码质量高、可维护且符合项目标准。
1. 测试体系理论基础
本章节介绍测试体系的核心概念、分层原理和工程化实践,为后续的具体技术规范提供理论基础。
1.1 测试分层模型
1.1.1 单元测试
核心关注点
- 验证单个函数/方法的正确性
- 关注输入输出逻辑是否符合预期
- 不依赖外部系统(数据库、网络、第三方服务)
- 执行速度快,反馈及时
测试方法
- 使用 Go 标准库
testing包编写测试用例 - 使用
testify/assert进行断言 - 使用
gomock或mockgen生成 mock 对象 - 遵循 Table-Driven Test 模式
输出价值
- 快速定位代码逻辑错误
- 作为代码重构的安全网
- 提供函数行为的文档说明
示例代码
func TestCalculatePoints(t *testing.T) {
tests := []struct {
name string
input float64
expected float64
}{
{"正常消费", 100.0, 10.0},
{"零消费", 0.0, 0.0},
{"负数消费", -50.0, 0.0},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
result := CalculatePoints(tt.input)
assert.Equal(t, tt.expected, result)
})
}
}
1.1.2 集成测试
核心关注点
- 验证多个模块/服务之间的协作是否正常
- 关注接口调用、数据流转、事务一致性
- 可能依赖外部系统(数据库、消息队列、缓存)
- 执行速度较慢,但覆盖面更广
测试方法
- 使用
sqlmock模拟数据库交互 - 使用
docker-compose启动依赖服务(PostgreSQL、Redis、RabbitMQ) - 使用
testcontainers-go进行容器化测试 - 验证 gRPC/HTTP 接口调用
输出价值
- 发现模块间集成问题
- 验证外部依赖配置正确性
- 确保数据一致性
示例代码
func TestUserService_CreateUser_Integration(t *testing.T) {
// 使用 sqlmock 模拟数据库
db, mock, err := sqlmock.New()
require.NoError(t, err)
defer db.Close()
// 设置 mock 期望
mock.ExpectBegin()
mock.ExpectQuery("INSERT INTO users").
WithArgs("test@example.com", "encrypted_password").
WillReturnRows(sqlmock.NewRows([]string{"id"}).AddRow(1))
mock.ExpectCommit()
// 创建服务实例
data := data.NewData(db, nil, nil)
usecase := user.NewUserUsecase(data)
// 执行测试
user, err := usecase.CreateUser(context.Background(), &v1.CreateUserReq{
Email: "test@example.com",
Password: "password123",
})
// 验证结果
require.NoError(t, err)
assert.Equal(t, int64(1), user.Id)
}
1.1.3 回归测试
核心关注点
- 验证新功能/修复不影响现有功能
- 关注历史 bug 是否复现
- 覆盖核心业务流程和关键路径
- 通常在 CI/CD 流水线中自动执行
测试方法
- 建立回归测试套件(包含所有单元测试和集成测试)
- 使用 Git 标签标记回归测试基线
- 定期执行全量回归测试(每日/每周)
- 监控测试通过率趋势
输出价值
- 确保代码变更不引入新问题
- 提供系统稳定性的信心
- 支持持续集成和持续交付
示例代码
// 回归测试套件示例
func TestRegressionSuite(t *testing.T) {
// 运行所有单元测试
t.Run("UnitTests", func(t *testing.T) {
t.Run("UserService", TestUserServiceSuite)
t.Run("PaymentService", TestPaymentServiceSuite)
t.Run("TenantService", TestTenantServiceSuite)
})
// 运行关键集成测试
t.Run("IntegrationTests", func(t *testing.T) {
t.Run("OrderFlow", TestOrderCreationFlow)
t.Run("PaymentFlow", TestPaymentCallbackFlow)
t.Run("DistributionFlow", TestDistributionCommissionFlow)
})
}
1.2 测试金字塔模型
1.2.1 三层结构
/\
/ \
/ E2E \ ← 端到端测试(少量)
/--------\
/ 集成测试 \ ← 集成测试(适量)
/------------\
/ 单元测试 \ ← 单元测试(大量)
/----------------\
1.2.2 各层比例建议
- 单元测试:70% - 快速、稳定、成本低
- 集成测试:20% - 验证模块协作
- 端到端测试:10% - 验证关键业务流程
1.2.3 井云项目实践
// 单元测试示例(70%)
func TestCalculateCommission(t *testing.T) {
// 纯逻辑测试,不依赖外部系统
}
// 集成测试示例(20%)
func TestDistributionService_Commission_Integration(t *testing.T) {
// 使用 sqlmock 模拟数据库
// 验证完整的佣金计算流程
}
// E2E 测试示例(10%)
func TestE2E_OrderToDistribution(t *testing.T) {
// 使用 testcontainers 启动完整环境
// 验证从下单到分佣的完整流程
}
1.3 工程化串联方式
1.3.1 CI/CD 自动化
GitHub Actions 配置示例
name: Test Pipeline
on:
push:
branches: [main, develop]
pull_request:
branches: [main, develop]
jobs:
unit-test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/setup-go@v4
with:
go-version: '1.25.4'
- name: Run unit tests
run: |
go test -v -race -coverprofile=coverage.out ./...
go tool cover -html=coverage.out -o coverage.html
- name: Upload coverage
uses: codecov/codecov-action@v3
with:
files: ./coverage.out
integration-test:
runs-on: ubuntu-latest
services:
postgres:
image: postgres:17.5
env:
POSTGRES_PASSWORD: postgres
options: >-
--health-cmd pg_isready
--health-interval 10s
--health-timeout 5s
--health-retries 5
steps:
- uses: actions/checkout@v3
- uses: actions/setup-go@v4
with:
go-version: '1.25.4'
- name: Run integration tests
run: |
go test -v -tags=integration ./...
1.3.2 测试数据管理
使用 Ent 的迁移和种子数据
// 测试数据工厂
package testutil
import "your-project/ent"
// CreateTestUser 创建测试用户
func CreateTestUser(ctx context.Context, client *ent.Client) *ent.User {
user, err := client.User.Create().
SetEmail("test@example.com").
SetPassword("encrypted_password").
SetStatus(v1.UserStatus_ACTIVE).
Save(ctx)
if err != nil {
panic(err)
}
return user
}
// CreateTestTenant 创建测试租户
func CreateTestTenant(ctx context.Context, client *ent.Client) *ent.Tenant {
tenant, err := client.Tenant.Create().
SetName("Test Tenant").
SetStatus(v1.TenantStatus_ACTIVE).
Save(ctx)
if err != nil {
panic(err)
}
return tenant
}
数据库清理策略
// 每个测试后清理
func TestUserService_Suite(t *testing.T) {
client := setupTestDB(t)
defer client.Close()
t.Run("CreateUser", func(t *testing.T) {
// 测试代码
})
t.Cleanup(func() {
// 清理测试数据
client.User.Delete().ExecX(context.Background())
})
}
1.3.3 覆盖率度量
Go 覆盖率工具
# 生成覆盖率 报告
go test -coverprofile=coverage.out ./...
# 查看覆盖率
go tool cover -func=coverage.out
# 生成 HTML 报告
go tool cover -html=coverage.out -o coverage.html
# 设置覆盖率阈值
go test -coverprofile=coverage.out -covermode=count ./...
覆盖率目标
- 整体覆盖率:≥ 90%
- 核心业务逻辑:≥ 95%
- 工具函数:≥ 80%
1.4 实践案例
1.4.1 井云分销系统测试实践
场景:用户下单后计算和发放分销佣金
单元测试层
// internal/biz/distribution/commission.go
func TestCalculateCommission(t *testing.T) {
tests := []struct {
name string
orderAmount float64
commissionRate float64
expected float64
}{
{"正常佣金", 100.0, 0.1, 10.0},
{"零佣金", 0.0, 0.1, 0.0},
{"高佣金", 1000.0, 0.2, 200.0},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
result := CalculateCommission(tt.orderAmount, tt.commissionRate)
assert.Equal(t, tt.expected, result)
})
}
}
集成测试层
// internal/service/distribution/distribution_test.go
func TestDistributionService_DistributeCommission_Integration(t *testing.T) {
// 使用 sqlmock 模拟数据库
db, mock, err := sqlmock.New()
require.NoError(t, err)
defer db.Close()
// Mock 数据库查询
mock.ExpectQuery("SELECT .* FROM users WHERE id = ?").
WithArgs(1).
WillReturnRows(sqlmock.NewRows([]string{"id", "commission_rate"}).
AddRow(1, 0.1))
mock.ExpectBegin()
mock.ExpectExec("INSERT INTO commissions").
WithArgs(1, 100.0, 10.0).
WillReturnResult(sqlmock.NewResult(1, 1))
mock.ExpectCommit()
// 创建服务实例
data := data.NewData(db, nil, nil)
usecase := distribution.NewDistributionUsecase(data)
// 执行测试
err = usecase.DistributeCommission(context.Background(), &v1.DistributeCommissionReq{
UserId: 1,
OrderAmount: 100.0,
})
// 验证结果
require.NoError(t, err)
}
回归测试层
// 回归测试套件
func TestRegression_DistributionSystem(t *testing.T) {
t.Run("UnitTests", func(t *testing.T) {
t.Run("CalculateCommission", TestCalculateCommission)
t.Run("ValidateCommissionRate", TestValidateCommissionRate)
})
t.Run("IntegrationTests", func(t *testing.T) {
t.Run("DistributeCommission", TestDistributionService_DistributeCommission_Integration)
t.Run("CommissionHistory", TestCommissionHistoryQuery)
})
}
1.4.2 测试执行流程
1.4.3 关键指标
- 单元测试通过率: 100%
- 集成测试通过率: 100%
- 代码覆盖率: ≥ 90%
- 测试执行时间: 单元测试 < 5 分钟,集成测试 < 15 分钟
1.5 最佳实践总结
- 遵循测试金字塔:70% 单元测试,20% 集成测试,10% E2E 测试
- 快速反馈:单元测试应在 5 分钟内完成
- 隔离性:每个测试应独立运行,不依赖其他测试
- 可读性:测试代码应清晰表达测试意图
- 维护性:定期更新测试用例,删除过时测试