docs: enhance testing strategy to v1.2 with industry best practices
Based on expert review, key improvements: 1. Standardize testing pyramid to 3 layers (Unit/Integration/E2E) - Remove non-standard "Component" layer - Add target percentages per industry standards 2. Add test utilities infrastructure - testutil/factory/ - Test data factories - testutil/mock/ - Unified mock library - testutil/assert/ - Custom assertions 3. Add missing build tags - //go:build slow for performance tests - //go:build e2e for E2E tests 4. Add performance regression testing guidelines 5. Fix flaky test handling - Proper use of context timeout - Skip flaky tests in local dev, run in CI 6. Update references to Google Testing Blog and Atlassian Testing Guide Coverage targets remain aligned with industry: - Unit: 70-80% - Integration: 15-20% - E2E: 5-10%
This commit is contained in:
@@ -1,45 +1,50 @@
|
||||
# Supply API 测试方案 v1.1
|
||||
# Supply API 测试方案 v1.2
|
||||
|
||||
## 1. 概述
|
||||
|
||||
本文档定义了 Supply API 项目的系统性测试策略,确保代码质量、可维护性和快速迭代能力。
|
||||
|
||||
### 1.1 测试金字塔
|
||||
|
||||
```
|
||||
┌─────────────┐
|
||||
│ E2E │ ← 关键业务流程验证
|
||||
┌─────────────┐
|
||||
│ Integration│ ← API、DB、消息队列
|
||||
┌───────────────┐
|
||||
│ Unit │ ← 业务逻辑、领域模型
|
||||
┌─────────────────┐
|
||||
│ Component │ ← 单组件内部逻辑
|
||||
└──────────────────┘
|
||||
```
|
||||
|
||||
| 层级 | 目标 | 工具 | 速度 | Build Tag |
|
||||
|------|------|------|------|-----------|
|
||||
| E2E | 关键业务流程 | Playwright | < 1s | `//go:build e2e` |
|
||||
| Integration | Store、Repository、DB | go:build integration | < 100ms | `//go:build integration` |
|
||||
| Unit | 业务逻辑、领域模型 | Go testing + testify | < 1ms | (默认) |
|
||||
| Component | 单组件内部逻辑 | Go testing | < 1ms | (默认) |
|
||||
|
||||
**重要**: Middleware 模块当前覆盖率 52.7%,**未达标**(目标 80%),需优先改进。
|
||||
遵循 Google Testing Blog 和 Atlassian Testing Guide 行业最佳实践。
|
||||
|
||||
---
|
||||
|
||||
## 2. 测试组织结构
|
||||
## 2. 测试金字塔(标准三层)
|
||||
|
||||
### 2.1 文件命名规范
|
||||
### 2.1 金字塔结构
|
||||
|
||||
```
|
||||
{package}_test.go # 单元测试(默认,无 build tag)
|
||||
┌─────────────┐
|
||||
│ E2E │ ← 5-10% (Playwright)
|
||||
┌─────────────┐
|
||||
│ Integration │ ← 15-20% (Store, DB, 真实依赖)
|
||||
┌───────────────┐
|
||||
│ Unit │ ← 70-80% (业务逻辑、领域模型)
|
||||
└───────────────┘
|
||||
```
|
||||
|
||||
### 2.2 各层定义
|
||||
|
||||
| 层级 | 目标占比 | 定义 | Build Tag | 速度目标 |
|
||||
|------|----------|------|-----------|----------|
|
||||
| E2E | 5-10% | 关键业务流程端到端验证 | `//go:build e2e` | < 1s |
|
||||
| Integration | 15-20% | Store、Repository、DB 集成 | `//go:build integration` | < 100ms |
|
||||
| Unit | 70-80% | 业务逻辑、领域模型、组件 | (默认) | < 10ms |
|
||||
|
||||
**注意**: 移除非标准的 "Component" 层,其包含在 Unit 层中。
|
||||
|
||||
---
|
||||
|
||||
## 3. 测试组织结构
|
||||
|
||||
### 3.1 文件命名规范
|
||||
|
||||
```
|
||||
{package}_test.go # 单元测试(默认)
|
||||
{package}_integration_test.go # 集成测试(需数据库)
|
||||
{package}_e2e_test.go # E2E 测试(需完整环境)
|
||||
{package}_slow_test.go # 慢速测试(默认跳过)
|
||||
```
|
||||
|
||||
### 2.2 Build Tag 使用
|
||||
### 3.2 Build Tag 使用
|
||||
|
||||
```go
|
||||
//go:build unit
|
||||
@@ -56,143 +61,97 @@ package repository_test // 集成测试
|
||||
// +build e2e
|
||||
|
||||
package e2e_test // E2E 测试
|
||||
|
||||
//go:build slow
|
||||
// +build slow
|
||||
|
||||
package slow_test // 慢速测试(CI中默认跳过)
|
||||
```
|
||||
|
||||
### 2.3 测试包结构
|
||||
### 3.3 测试包结构
|
||||
|
||||
```
|
||||
internal/
|
||||
├── domain/ # 领域模型
|
||||
│ ├── account.go # 账号领域逻辑
|
||||
│ ├── account.go
|
||||
│ ├── account_test.go # 账号单元测试
|
||||
│ ├── package.go # 套餐领域逻辑
|
||||
│ ├── package.go
|
||||
│ ├── package_test.go # 套餐单元测试
|
||||
│ └── invariants_test.go # 不变量测试
|
||||
│
|
||||
├── audit/ # 审计模块
|
||||
│ ├── service/
|
||||
│ │ ├── audit_service.go
|
||||
│ │ └── audit_service_test.go
|
||||
│ └── handler/
|
||||
│ ├── audit_handler.go
|
||||
│ └── audit_handler_test.go
|
||||
├── testutil/ # 测试工具包(新增)
|
||||
│ ├── factory/ # 测试数据工厂
|
||||
│ │ ├── account.go
|
||||
│ │ ├── package.go
|
||||
│ │ └── settlement.go
|
||||
│ ├── mock/ # 统一Mock
|
||||
│ │ └── mocks.go
|
||||
│ └── assert/ # 自定义断言
|
||||
│ └── assertions.go
|
||||
│
|
||||
├── middleware/ # HTTP中间件
|
||||
│ ├── auth.go
|
||||
│ ├── auth_test.go # 认证测试
|
||||
│ ├── ratelimit.go
|
||||
│ └── ratelimit_test.go # 限流测试
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 3. 单元测试规范
|
||||
|
||||
### 3.1 测试结构 (AAA模式)
|
||||
|
||||
```go
|
||||
func TestXXX_Scenario(t *testing.T) {
|
||||
// Arrange - 准备测试数据
|
||||
store := newMockStore()
|
||||
svc := NewService(store)
|
||||
|
||||
// Act - 执行被测操作
|
||||
result, err := svc.DoSomething(ctx, req)
|
||||
|
||||
// Assert - 验证结果
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, expected, result)
|
||||
}
|
||||
```
|
||||
|
||||
### 3.2 Mock 接口而非具体实现
|
||||
|
||||
```go
|
||||
// ✅ 正确 - Mock 接口
|
||||
type mockSettlementStore struct {
|
||||
settlements map[int64]*Settlement
|
||||
}
|
||||
|
||||
func (m *mockSettlementStore) GetByID(ctx context.Context, supplierID, id int64) (*Settlement, error) {
|
||||
if s, ok := m.settlements[id]; ok && s.SupplierID == supplierID {
|
||||
return s, nil
|
||||
}
|
||||
return nil, errors.New("not found")
|
||||
}
|
||||
|
||||
// ❌ 错误 - Mock 具体类型
|
||||
type mockRepo struct {
|
||||
repo *repository.SettlementRepository
|
||||
}
|
||||
```
|
||||
|
||||
### 3.3 Mock 审计存储正确姿势
|
||||
|
||||
审计存储使用 `audit.AuditStore` 接口:
|
||||
|
||||
```go
|
||||
type AuditStore interface {
|
||||
Emit(ctx context.Context, event audit.Event) error
|
||||
Query(ctx context.Context, filter audit.EventFilter) ([]audit.Event, error)
|
||||
QueryWithTotal(ctx context.Context, filter audit.EventFilter) ([]audit.Event, int64, error)
|
||||
GetByID(ctx context.Context, eventID string) (audit.Event, error)
|
||||
}
|
||||
```
|
||||
|
||||
**成功场景:**
|
||||
```go
|
||||
func (m *mockAuditStore) Emit(ctx context.Context, event audit.Event) error {
|
||||
return nil // 成功时不记录
|
||||
}
|
||||
```
|
||||
|
||||
**错误场景(关键):**
|
||||
```go
|
||||
func (m *mockFailingAuditStore) Emit(ctx context.Context, event audit.Event) error {
|
||||
return errors.New("audit emit failed")
|
||||
}
|
||||
```
|
||||
|
||||
### 3.4 表驱动测试
|
||||
|
||||
适用于多场景测试:
|
||||
|
||||
```go
|
||||
func TestSettlementStatus_Transitions(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
from SettlementStatus
|
||||
to SettlementStatus
|
||||
expected bool
|
||||
}{
|
||||
{"pending to processing", SettlementStatusPending, SettlementStatusProcessing, true},
|
||||
{"pending to completed", SettlementStatusPending, SettlementStatusCompleted, false},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
result := ValidateStateTransition(tt.from, tt.to)
|
||||
assert.Equal(t, tt.expected, result)
|
||||
})
|
||||
}
|
||||
}
|
||||
│ └── auth_test.go # 认证测试
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 4. 测试数据管理
|
||||
|
||||
### 4.1 测试 Setup/Teardown
|
||||
### 4.1 测试数据工厂(新增)
|
||||
|
||||
```go
|
||||
func TestAccountService(t *testing.T) {
|
||||
store := newMockAccountStore()
|
||||
// internal/testutil/factory/account.go
|
||||
|
||||
t.Cleanup(func() {
|
||||
// 清理测试数据(如果需要)
|
||||
})
|
||||
type AccountFactory struct {
|
||||
supplierID int64
|
||||
provider Provider
|
||||
accountType AccountType
|
||||
credential string
|
||||
riskAck bool
|
||||
}
|
||||
|
||||
// 测试逻辑...
|
||||
func NewAccountFactory() *AccountFactory {
|
||||
return &AccountFactory{
|
||||
supplierID: 1001,
|
||||
provider: ProviderOpenAI,
|
||||
accountType: AccountTypeAPIKey,
|
||||
credential: "sk-test-key",
|
||||
riskAck: true,
|
||||
}
|
||||
}
|
||||
|
||||
func (f *AccountFactory) WithSupplierID(id int64) *AccountFactory {
|
||||
f.supplierID = id
|
||||
return f
|
||||
}
|
||||
|
||||
func (f *AccountFactory) WithProvider(p Provider) *AccountFactory {
|
||||
f.provider = p
|
||||
return f
|
||||
}
|
||||
|
||||
func (f *AccountFactory) Build() *CreateAccountRequest {
|
||||
return &CreateAccountRequest{
|
||||
SupplierID: f.supplierID,
|
||||
Provider: f.provider,
|
||||
AccountType: f.accountType,
|
||||
Credential: f.credential,
|
||||
RiskAck: f.riskAck,
|
||||
}
|
||||
}
|
||||
|
||||
// 使用示例
|
||||
func TestAccountService_Create(t *testing.T) {
|
||||
factory := NewAccountFactory()
|
||||
|
||||
// 正常场景
|
||||
req := factory.Build()
|
||||
|
||||
// 边界场景
|
||||
invalidReq := factory.
|
||||
WithCredential("").
|
||||
Build()
|
||||
}
|
||||
```
|
||||
|
||||
@@ -232,9 +191,95 @@ tests := []struct {
|
||||
|
||||
---
|
||||
|
||||
## 5. 集成测试规范
|
||||
## 5. 单元测试规范
|
||||
|
||||
### 5.1 Build Tag 隔离
|
||||
### 5.1 测试结构 (AAA模式)
|
||||
|
||||
```go
|
||||
func TestXXX_Scenario(t *testing.T) {
|
||||
// Arrange - 准备测试数据
|
||||
store := newMockStore()
|
||||
svc := NewService(store)
|
||||
|
||||
// Act - 执行被测操作
|
||||
result, err := svc.DoSomething(ctx, req)
|
||||
|
||||
// Assert - 验证结果
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, expected, result)
|
||||
}
|
||||
```
|
||||
|
||||
### 5.2 Mock 接口而非具体实现
|
||||
|
||||
```go
|
||||
// ✅ 正确 - Mock 接口
|
||||
type mockSettlementStore struct {
|
||||
settlements map[int64]*Settlement
|
||||
}
|
||||
|
||||
func (m *mockSettlementStore) GetByID(ctx context.Context, supplierID, id int64) (*Settlement, error) {
|
||||
if s, ok := m.settlements[id]; ok && s.SupplierID == supplierID {
|
||||
return s, nil
|
||||
}
|
||||
return nil, errors.New("not found")
|
||||
}
|
||||
|
||||
// ❌ 错误 - Mock 具体类型
|
||||
type mockRepo struct {
|
||||
repo *repository.SettlementRepository
|
||||
}
|
||||
```
|
||||
|
||||
### 5.3 Mock 审计存储正确姿势
|
||||
|
||||
```go
|
||||
type AuditStore interface {
|
||||
Emit(ctx context.Context, event audit.Event) error
|
||||
Query(ctx context.Context, filter audit.EventFilter) ([]audit.Event, error)
|
||||
QueryWithTotal(ctx context.Context, filter audit.EventFilter) ([]audit.Event, int64, error)
|
||||
GetByID(ctx context.Context, eventID string) (audit.Event, error)
|
||||
}
|
||||
|
||||
// ✅ 正确 - 使用具体类型
|
||||
func (m *mockAuditStore) Emit(ctx context.Context, event audit.Event) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// ✅ 错误模拟 - 返回错误
|
||||
func (m *mockFailingAuditStore) Emit(ctx context.Context, event audit.Event) error {
|
||||
return errors.New("audit emit failed")
|
||||
}
|
||||
```
|
||||
|
||||
### 5.4 表驱动测试
|
||||
|
||||
```go
|
||||
func TestSettlementStatus_Transitions(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
from SettlementStatus
|
||||
to SettlementStatus
|
||||
expected bool
|
||||
}{
|
||||
{"pending to processing", SettlementStatusPending, SettlementStatusProcessing, true},
|
||||
{"pending to completed", SettlementStatusPending, SettlementStatusCompleted, false},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
result := ValidateStateTransition(tt.from, tt.to)
|
||||
assert.Equal(t, tt.expected, result)
|
||||
})
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 6. 集成测试规范
|
||||
|
||||
### 6.1 Build Tag 隔离
|
||||
|
||||
```go
|
||||
//go:build integration
|
||||
@@ -251,11 +296,27 @@ func TestIntegrationSettlementRepository(t *testing.T) {
|
||||
if testing.Short() {
|
||||
t.Skip("Skipping integration test in short mode")
|
||||
}
|
||||
// 需要真实的 PostgreSQL
|
||||
// 需要真实的 PostgreSQL 或使用 sqlmock
|
||||
}
|
||||
```
|
||||
|
||||
### 5.2 运行命令
|
||||
### 6.2 使用 Test Database
|
||||
|
||||
```go
|
||||
//go:build integration
|
||||
// +build integration
|
||||
|
||||
func TestIntegrationSettlementRepository(t *testing.T) {
|
||||
// 选项1: 使用 sqlmock
|
||||
db, mock, _ := sqlmock.New()
|
||||
defer db.Close()
|
||||
|
||||
// 选项2: 使用轻量级测试数据库
|
||||
// 推荐: github.com/testcontainers/testcontainers-go
|
||||
}
|
||||
```
|
||||
|
||||
### 6.3 运行命令
|
||||
|
||||
```bash
|
||||
# 只运行单元测试(默认)
|
||||
@@ -264,52 +325,43 @@ go test ./...
|
||||
# 包含集成测试
|
||||
go test -tags=integration ./...
|
||||
|
||||
# 排除集成测试
|
||||
# 排除集成测试(快速模式)
|
||||
go test -short ./...
|
||||
|
||||
# 运行特定 tag
|
||||
go test -tags=unit ./internal/domain/...
|
||||
# 运行慢速测试
|
||||
go test -tags=slow ./...
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 6. 覆盖率要求
|
||||
## 7. 覆盖率要求
|
||||
|
||||
### 6.1 模块覆盖率目标
|
||||
### 7.1 模块覆盖率目标
|
||||
|
||||
| 模块 | 最低覆盖率 | 当前覆盖率 | 状态 | 优先级 |
|
||||
|------|-----------|-----------|------|--------|
|
||||
| domain | 70% | 71.2% | ✅ | - |
|
||||
| **middleware** | **80%** | **52.7%** | 🔴 | **P0** |
|
||||
| audit/handler | 75% | 79.6% | ✅ | - |
|
||||
| audit/service | 80% | 83.0% | ✅ | - |
|
||||
| audit/model | 80% | 93.8% | ✅ | - |
|
||||
| audit/sanitizer | 80% | 84.3% | ✅ | - |
|
||||
| security | 80% | 88.8% | ✅ | - |
|
||||
| iam | 70% | 93.2% | ✅ | - |
|
||||
| 模块 | 最低覆盖率 | 当前 | 状态 |
|
||||
|------|-----------|------|------|
|
||||
| domain | 70% | 71.2% | ✅ |
|
||||
| middleware | 80% | 80.4% | ✅ |
|
||||
| audit/handler | 75% | 79.6% | ✅ |
|
||||
| audit/service | 80% | 83.0% | ✅ |
|
||||
| audit/model | 80% | 93.8% | ✅ |
|
||||
| audit/sanitizer | 80% | 84.3% | ✅ |
|
||||
| security | 80% | 88.8% | ✅ |
|
||||
| iam | 70% | 93.2% | ✅ |
|
||||
|
||||
**⚠️ 关键问题**: Middleware 模块覆盖率 52.7%,与目标差距 -27.3%,需优先改进。
|
||||
|
||||
### 6.2 覆盖率检查命令
|
||||
|
||||
**重要**: Go test 在运行 `go test ./...` 时会进行覆盖率聚合,可能导致某些模块显示的覆盖率低于单独运行时的值。
|
||||
### 7.2 覆盖率检查命令
|
||||
|
||||
```bash
|
||||
# ✅ 推荐:单独验证关键模块(显示真实覆盖率)
|
||||
go test -cover ./internal/domain/... # → 71.2%
|
||||
go test -cover ./internal/middleware/... # → 80.4%
|
||||
go test -cover ./internal/audit/handler/...
|
||||
go test -cover ./internal/audit/service/...
|
||||
go test -cover ./internal/middleware/... # → 80.4%
|
||||
|
||||
# ⚠️ 联合运行(覆盖率数值会被稀释,不反映真实情况)
|
||||
# ⚠️ 联合运行(覆盖率数值会被稀释)
|
||||
go test -coverprofile=coverage.out ./...
|
||||
go tool cover -html=coverage.out -o coverage.html
|
||||
|
||||
# 检查覆盖率达标情况(使用单独运行)
|
||||
go test -cover ./internal/domain/... 2>&1 | grep "coverage"
|
||||
```
|
||||
|
||||
### 6.3 覆盖率未达标处理
|
||||
### 7.3 覆盖率未达标处理
|
||||
|
||||
1. 分析未覆盖代码路径
|
||||
2. 添加针对性测试用例
|
||||
@@ -318,9 +370,9 @@ go test -cover ./internal/domain/... 2>&1 | grep "coverage"
|
||||
|
||||
---
|
||||
|
||||
## 7. 测试命名规范
|
||||
## 8. 测试命名规范
|
||||
|
||||
### 7.1 函数命名
|
||||
### 8.1 函数命名
|
||||
|
||||
```
|
||||
Test{Service}_{Method}_{Scenario}
|
||||
@@ -332,7 +384,7 @@ Test{Service}_{Method}_{Scenario}
|
||||
- TestSettlementService_Withdraw_ExceedsBalance
|
||||
```
|
||||
|
||||
### 7.2 子测试命名
|
||||
### 8.2 子测试命名
|
||||
|
||||
```go
|
||||
func TestAccountService_Activate(t *testing.T) {
|
||||
@@ -348,12 +400,6 @@ func TestAccountService_Activate(t *testing.T) {
|
||||
supplierID: 1001,
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "activate non-existent fails",
|
||||
setup: func() *Account { return nil },
|
||||
supplierID: 9999,
|
||||
wantErr: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
@@ -366,9 +412,9 @@ func TestAccountService_Activate(t *testing.T) {
|
||||
|
||||
---
|
||||
|
||||
## 8. 并发与竞态测试
|
||||
## 9. 并发与竞态测试
|
||||
|
||||
### 8.1 启用 Race 检测
|
||||
### 9.1 启用 Race 检测
|
||||
|
||||
```bash
|
||||
# 运行所有测试并检测竞态条件
|
||||
@@ -378,7 +424,7 @@ go test -race ./...
|
||||
go test -race -v ./internal/domain/...
|
||||
```
|
||||
|
||||
### 8.2 并发安全测试示例
|
||||
### 9.2 并发安全测试示例
|
||||
|
||||
```go
|
||||
func TestConcurrentAccountAccess(t *testing.T) {
|
||||
@@ -400,16 +446,45 @@ func TestConcurrentAccountAccess(t *testing.T) {
|
||||
|
||||
---
|
||||
|
||||
## 9. 测试运行策略
|
||||
## 10. 性能回归测试
|
||||
|
||||
### 9.1 本地开发
|
||||
### 10.1 执行时间监控
|
||||
|
||||
```go
|
||||
//go:build slow
|
||||
// +build slow
|
||||
|
||||
func TestPerformance_SettlementQuery(t *testing.T) {
|
||||
if testing.Short() {
|
||||
t.Skip("Skipping performance test")
|
||||
}
|
||||
|
||||
start := time.Now()
|
||||
|
||||
// 执行查询
|
||||
result, err := svc.Query(ctx, req)
|
||||
|
||||
elapsed := time.Since(start)
|
||||
|
||||
// 断言在可接受范围内
|
||||
assert.NoError(t, err)
|
||||
assert.True(t, elapsed < 100*time.Millisecond,
|
||||
"Query took %v, expected < 100ms", elapsed)
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 11. 测试运行策略
|
||||
|
||||
### 11.1 本地开发
|
||||
|
||||
```bash
|
||||
# 快速测试(跳过慢速测试)
|
||||
# 快速测试(跳过慢速和集成测试)
|
||||
go test -short ./...
|
||||
|
||||
# 完整测试(含集成测试)
|
||||
go test -tags=integration ./...
|
||||
go test -tags=integration, slow ./...
|
||||
|
||||
# 竞态检测
|
||||
go test -race ./...
|
||||
@@ -421,7 +496,7 @@ go test ./internal/domain/...
|
||||
go test -v -cover ./internal/domain/...
|
||||
```
|
||||
|
||||
### 9.2 CI/CD
|
||||
### 11.2 CI/CD
|
||||
|
||||
```yaml
|
||||
# .github/workflows/test.yml
|
||||
@@ -445,12 +520,8 @@ jobs:
|
||||
- name: Run integration tests
|
||||
run: go test -tags=integration -race -coverprofile=coverage.out ./...
|
||||
|
||||
- name: Check Coverage
|
||||
run: |
|
||||
go test -cover ./... > coverage.txt
|
||||
cat coverage.txt
|
||||
# 检查关键模块覆盖率
|
||||
grep "middleware" coverage.txt
|
||||
- name: Run slow tests
|
||||
run: go test -tags=slow ./...
|
||||
|
||||
- name: Upload coverage
|
||||
uses: codecov/codecov-action@v4
|
||||
@@ -460,16 +531,11 @@ jobs:
|
||||
|
||||
---
|
||||
|
||||
## 10. 常见问题处理
|
||||
## 12. 常见问题处理
|
||||
|
||||
### 10.1 测试依赖外部服务
|
||||
### 12.1 测试依赖外部服务
|
||||
|
||||
```go
|
||||
// ❌ 依赖真实存储
|
||||
func TestSettlementService(t *testing.T) {
|
||||
repo, _ := NewPostgresRepository(db)
|
||||
}
|
||||
|
||||
// ✅ 使用 Mock
|
||||
func TestSettlementService(t *testing.T) {
|
||||
store := newMockSettlementStore()
|
||||
@@ -477,40 +543,45 @@ func TestSettlementService(t *testing.T) {
|
||||
}
|
||||
```
|
||||
|
||||
### 10.2 时间相关测试
|
||||
### 12.2 时间相关测试
|
||||
|
||||
```go
|
||||
// 使用依赖注入
|
||||
type SettlementService struct {
|
||||
store SettlementStore
|
||||
clock Clock // 注入时间依赖
|
||||
}
|
||||
|
||||
func (s *SettlementService) Withdraw(ctx context.Context, supplierID int64, req *WithdrawRequest) (*Settlement, error) {
|
||||
now := s.clock.Now() // 使用注入的时间
|
||||
store SettlementStore
|
||||
clock Clock // 注入时间依赖
|
||||
}
|
||||
```
|
||||
|
||||
### 10.3 Flaky 测试处理
|
||||
### 12.3 Flaky 测试处理
|
||||
|
||||
```go
|
||||
// ❌ 错误 - 在测试中重试
|
||||
func TestNetworkCall(t *testing.T) {
|
||||
// 重试机制
|
||||
var lastErr error
|
||||
for i := 0; i < 3; i++ {
|
||||
if err := attempt(); err == nil {
|
||||
return
|
||||
}
|
||||
lastErr = err
|
||||
time.Sleep(10 * time.Millisecond)
|
||||
}
|
||||
t.Fatalf("failed after retries: %v", lastErr)
|
||||
}
|
||||
|
||||
// ✅ 正确 - 标记为已知问题并使用超时
|
||||
func TestNetworkCall(t *testing.T) {
|
||||
if os.Getenv("CI") == "" {
|
||||
t.Skip("Skipping flaky test outside CI")
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
||||
defer cancel()
|
||||
|
||||
err := callWithRetry(ctx, endpoint)
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 11. 测试检查清单
|
||||
## 13. 测试检查清单
|
||||
|
||||
新代码合并前:
|
||||
|
||||
@@ -522,32 +593,42 @@ func TestNetworkCall(t *testing.T) {
|
||||
- [ ] 测试名称符合规范
|
||||
- [ ] 表驱动测试覆盖边界情况
|
||||
- [ ] 集成测试在 CI 中正常运行
|
||||
- [ ] **Middleware 模块覆盖率优先改进**(当前 52.7% → 目标 80%)
|
||||
- [ ] 性能测试在慢速测试套件中
|
||||
|
||||
---
|
||||
|
||||
## 12. 下一步行动计划
|
||||
## 14. 下一步行动计划
|
||||
|
||||
### ✅ 已完成
|
||||
|
||||
1. **Domain 模块覆盖率提升** (40.7% → 71.2%) ✅
|
||||
2. **Middleware 模块覆盖率提升** (52.7% → 80.4%) ✅
|
||||
3. **Audit handler 模块覆盖率提升** (75% → 79.6%) ✅
|
||||
1. Domain 模块覆盖率提升 (40.7% → 71.2%)
|
||||
2. Middleware 模块覆盖率提升 (52.7% → 80.4%)
|
||||
3. Audit handler 模块覆盖率提升 (75% → 79.6%)
|
||||
|
||||
### P1 - 高优先级
|
||||
1. Repository 模块覆盖率提升(1.3% → 30%)
|
||||
2. settlement.go 方法覆盖(部分方法 0%)
|
||||
### P1 - 创建测试工具包
|
||||
|
||||
### P2 - 中优先级
|
||||
3. IAM handler/service 测试补充
|
||||
4. HTTP API handler 测试补充
|
||||
5. E2E 测试骨架
|
||||
1. **testutil/factory** - 测试数据工厂
|
||||
2. **testutil/mock** - 统一Mock库
|
||||
3. **testutil/assert** - 自定义断言
|
||||
|
||||
### P2 - 完善集成测试
|
||||
|
||||
1. Repository 模块集成测试骨架
|
||||
2. Settlement Store 集成测试
|
||||
|
||||
### P3 - 补充测试类型
|
||||
|
||||
1. E2E 测试骨架
|
||||
2. 性能回归测试
|
||||
|
||||
---
|
||||
|
||||
## 13. 参考资料
|
||||
## 15. 参考资料
|
||||
|
||||
- [Google Testing Blog](https://testing.googleblog.com/)
|
||||
- [Atlassian Testing Guide](https://www.atlassian.com/continuous-delivery/software-testing)
|
||||
- [Go Testing](https://pkg.go.dev/testing)
|
||||
- [testify](https://github.com/stretchr/testify)
|
||||
- [testcontainers-go](https://github.com/testcontainers/testcontainers-go)
|
||||
- [Go Race Detector](https://go.dev/blog/race-detector)
|
||||
- [Advanced Testing in Go](https://google.github.io/aip/214)
|
||||
|
||||
Reference in New Issue
Block a user