跳到主要内容

井云服务中心后端测试规范

适用对象:井云服务中心后端服务(Go / Kratos / Ent / 微服务架构)

目标:建立统一、可维护、可自动化的测试规范,确保测试代码长期可读、可演进,并可稳定地由 AI 与人协同产出。


请严格遵守以下所有规范、限制和最佳实践,确保测试代码质量高、可维护且符合项目标准。


1. 测试体系理论基础

本章节介绍测试体系的核心概念、分层原理和工程化实践,为后续的具体技术规范提供理论基础。

1.1 测试分层模型

1.1.1 单元测试

核心关注点

  • 验证单个函数/方法的正确性
  • 关注输入输出逻辑是否符合预期
  • 不依赖外部系统(数据库、网络、第三方服务)
  • 执行速度快,反馈及时

测试方法

  • 使用 Go 标准库 testing 包编写测试用例
  • 使用 testify/assert 进行断言
  • 使用 gomockmockgen 生成 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 最佳实践总结

  1. 遵循测试金字塔:70% 单元测试,20% 集成测试,10% E2E 测试
  2. 快速反馈:单元测试应在 5 分钟内完成
  3. 隔离性:每个测试应独立运行,不依赖其他测试
  4. 可读性:测试代码应清晰表达测试意图
  5. 维护性:定期更新测试用例,删除过时测试

2. 测试目标与原则

2.1 测试目标

  • 验证业务行为是否符合预期,而非实现细节
  • 为重构提供安全网
  • 作为业务规则的可执行文档
  • 确保微服务间的接口契约稳定性

2.2 核心原则

  • 测试边界清晰:一层只测一层,微服务间测试隔离
  • 测试应稳定、可重复:不依赖外部环境(数据库、Redis、RabbitMQ、Consul)
  • 失败应可诊断:错误断言明确,包含详细的上下文信息
  • 测试即代码资产:与业务代码同等维护标准
  • 覆盖率驱动:核心业务逻辑必须达到高覆盖率

3. 测试分层与边界定义

3.1 微服务架构分层说明

服务层级职责测试类型依赖处理
GatewayserviceHTTP/gRPC 协议适配、路由单元测试(重点)手动 mock gRPC 客户端
其他服务servicetransport / protocol 适配可选mock biz
所有服务biz业务规则、流程单元测试(重点)sqlmock + Ent ORM
所有服务data数据访问、第三方集成单元 / 轻量集成sqlmock

3.2 明确不测内容

  • 不在单元测试中访问真实数据库、Redis、RabbitMQ
  • 不调用真实第三方 API(微信、阿里云、短信服务等)
  • 不测试 Kratos transport 层的框架行为
  • Gateway 服务不使用 gomock,必须使用函数字段手动 mock
  • 不测试服务间网络通信,只测试业务逻辑

4. 技术栈与工具约定

4.1 基础技术栈

  • 项目:井云服务中心后端 (Jingyun Center Backend)
  • Go 版本:Go 1.25.4
  • 服务框架:Kratos v2
  • ORM:Ent
  • 数据库:PostgreSQL 17.5
  • 缓存:Redis
  • 消息队列:RabbitMQ
  • 服务注册:Consul
  • 依赖注入:Wire

4.2 测试工具

  • 官方 testing 包
  • testify(推荐 require
  • sqlmock(数据库层测试,推荐
  • gomock(仅用于传统接口 mock,Gateway 服务禁止使用

说明:

  • Gateway 服务必须使用函数字段手动 mock gRPC 客户端
  • 其他服务使用 sqlmock + Ent ORM 进行数据库测试
  • 避免在同一项目中混用多种 mock 框架

5. 单元测试编写规范

5.1 测试文件与命名

  • 文件名:xxx_test.go
  • 测试函数:Test<ServiceName>_<MethodName>_<Scenario>

命名规范:

  • 成功场景:Success
  • 错误场景:具体错误原因(如 NotFoundNoTenantIDDatabaseError
  • 边界场景:具体边界条件(如 EmptyListWithOptionalParam

示例:

// Gateway 服务示例
func TestAgentCategoryService_CreateAgentCategory_Success(t *testing.T) {}
func TestAgentCategoryService_CreateAgentCategory_NoTenantID(t *testing.T) {}
func TestAgentCategoryService_ListAgentCategories_WithIsActiveTrue(t *testing.T) {}

// 其他服务示例
func TestUserUsecase_CreateUser_Success(t *testing.T) {}
func TestUserUsecase_CreateUser_EmailAlreadyExists(t *testing.T) {}

5.2 Table-driven Tests(强制)

所有业务测试必须使用 table-driven 形式:

tests := []struct {
name string
input *CreateUserRequest
setupMock func(mock sqlmock.Sqlmock)
expectedResp *CreateUserResponse
expectedError error
}{
{
name: "Success",
input: &CreateUserRequest{...},
setupMock: func(mock sqlmock.Sqlmock) {
// Mock SQL 操作
rows := sqlmock.NewRows([]string{"id"}).AddRow(int64(101))
mock.ExpectQuery(`INSERT INTO "users"`).WillReturnRows(rows)
},
expectedResp: &CreateUserResponse{Id: 101},
expectedError: nil,
},
{
name: "EmailAlreadyExists",
input: &CreateUserRequest{...},
setupMock: func(mock sqlmock.Sqlmock) {
mock.ExpectQuery(`SELECT .* FROM "users"`).WillReturnError(sql.ErrNoRows)
},
expectedResp: nil,
expectedError: biz.ErrUserExists,
},
}

好处:

  • 用例结构统一
  • 便于扩展失败场景
  • 易于 AI 自动生成
  • 明确的输入输出定义

6. Mock 使用规范

6.1 Mock 边界

Gateway 服务

  • service 层:使用函数字段手动 mock gRPC 客户端
  • 禁止使用 gomock

其他服务(agent/auth/payment/tenant/user):

  • biz 层:只使用 sqlmock mock 数据库操作
  • service 层:只 mock biz 层接口
  • 禁止直接 mock ent.Client

禁止行为:

  • 在任何测试中绕过接口直接访问实现
  • Gateway 服务使用 gomock
  • 访问真实数据库、Redis、RabbitMQ

6.2 Mock 行为声明

Gateway 服务手动 mock

// 定义 mock 客户端
type MockAgentCategoryClient struct {
CreateAgentCategoryFunc func(ctx context.Context, in *agentv1.CreateAgentCategoryRequest, opts ...grpc.CallOption) (*agentv1.CreateAgentCategoryReply, error)
ListAgentCategoriesFunc func(ctx context.Context, in *agentv1.ListAgentCategoriesRequest, opts ...grpc.CallOption) (*agentv1.ListAgentCategoriesReply, error)
}

// 在测试中设置 mock 行为
mockClient.CreateAgentCategoryFunc = func(ctx context.Context, in *agentv1.CreateAgentCategoryRequest, opts ...grpc.CallOption) (*agentv1.CreateAgentCategoryReply, error) {
// 验证传入参数
assert.Equal(t, int64(1), in.TenantId)
assert.Equal(t, "测试分类", in.Name)

return &agentv1.CreateAgentCategoryReply{
Category: &agentv1.AgentCategory{
Id: 1,
TenantId: 1,
Name: "测试分类",
// ... 其他字段必须完整
},
}, nil
}

其他服务 sqlmock

// 标准 setup 函数
func setupServiceTest(t *testing.T) (*sql.DB, sqlmock.Sqlmock, *YourService) {
mockDB, mock, err := sqlmock.New()
require.NoError(t, err)

drv := entsql.OpenDB("postgres", mockDB)
entClient := entclient.NewClient(entclient.Driver(drv))

// 初始化服务
logger := kmlog.NewStdLogger(io.Discard)
testData := data.NewTestData(mockDB, entClient, logger)
repo := data.NewYourRepo(testData)
uc := biz.NewYourUsecase(repo, logger)
svc := NewYourService(uc, logger)

return mockDB, mock, svc
}

// Mock SQL 操作
func TestCreateUser(t *testing.T) {
mockDB, mock, svc := setupServiceTest(t)
defer mockDB.Close()

// Mock INSERT 返回 ID
rows := sqlmock.NewRows([]string{"id"}).AddRow(int64(101))
mock.ExpectQuery(`INSERT INTO "users"`).WillReturnRows(rows)

// 执行测试
resp, err := svc.CreateUser(ctx, req)
require.NoError(t, err)
require.Equal(t, int64(101), resp.Id)

// 验证所有 SQL 期望都被调用
require.NoError(t, mock.ExpectationsWereMet())
}

6.3 通用规范

  • mock 行为必须在每个 test case 中显式声明
  • 禁止在 init() 或全局变量中设置 mock 行为
  • sqlmock 必须在测试结束前验证 ExpectationsWereMet()

7. 断言(Assertion)规范

7.1 基本规则

  • 优先使用 require,而非 assert
  • 每个测试用例至少有一个明确断言

7.2 错误断言

禁止:

require.EqualError(t, err, "xxx")

推荐:

require.Error(t, err)
require.True(t, errors.Is(err, biz.ErrUserExists))

Kratos 错误:

require.True(t, errors.Is(err, errors.BadRequest("USER_EXISTS", "")))

8. 覆盖要求(最低标准)

8.1 测试覆盖率要求

总覆盖率要求

  • 项目整体测试覆盖率应保持在 60%~70%
  • 总覆盖率仅作为健康度参考,不作为合并阻断条件
  • 严禁为了提升总覆盖率而编写无业务价值的测试
  • 若 biz 覆盖率提升而总覆盖率未同步提升,视为正常且正向结果

分层覆盖率规范

  • service 层

    • 不设强制覆盖率要求
    • 仅在存在自定义逻辑时编写测试
    • 覆盖率不纳入 CI 考核
  • biz 层

    • 最低覆盖率:80%
    • 推荐目标:85%–90%
    • CI 强制校验,未达标禁止合并
  • data 层

    • 推荐覆盖率:50%–70%
    • 重点覆盖复杂查询、事务与映射逻辑
    • 不要求覆盖 Ent 自动生成代码

8.2 用例覆盖要求

对于每一个 public 方法

  • 至少 1 个成功用例(happy path)
  • 至少 2 个失败用例

业务失败场景包括但不限于:

通用失败场景

  • 参数非法(空值、格式错误)
  • 下游依赖返回错误(数据库错误、网络错误)
  • 业务约束不满足(资源已存在、权限不足)

Gateway 服务特定场景

  • 缺少必需参数(如 TenantID)
  • gRPC 客户端获取失败
  • gRPC 调用失败
  • 资源不存在(404/NotFound)

其他服务特定场景

  • 数据库连接失败
  • SQL 查询失败
  • 数据不存在

边界场景

  • 空列表返回
  • 分页边界测试
  • 可选参数的分支覆盖(nil 和非 nil)

8.3 完整字段验证(强制)

返回的结构体所有字段都必须验证,避免字段遗漏:

// ❌ 错误示例 - 字段验证不完整
assert.Equal(t, int64(1), resp.Category.Id)
assert.Equal(t, "测试分类", resp.Category.Name)

// ✅ 正确示例 - 验证所有字段
assert.Equal(t, int64(1), resp.Category.Id)
assert.Equal(t, int64(1), resp.Category.TenantId)
assert.Equal(t, "测试分类", resp.Category.Name)
assert.Equal(t, "测试描述", resp.Category.Description)
assert.Equal(t, int32(1), resp.Category.SortOrder)
assert.False(t, resp.Category.IsFeatured) // 不能遗漏任何字段
assert.True(t, resp.Category.IsActive) // 不能遗漏任何字段
assert.Equal(t, int64(1672531200), resp.Category.CreatedAt)
assert.Equal(t, int64(1672531200), resp.Category.UpdatedAt)

为什么必须验证所有字段

  1. 防止字段映射遗漏(生产 bug)
  2. 确保数据完整性和一致性
  3. 测试即文档,明确所有字段含义
  4. 防止重构时意外删除字段

9. Ent 相关测试规范

9.1 基本原则

  • biz 层不得直接依赖 ent.Client,必须通过 data 层接口
  • ent schema 不直接编写测试(schema 是配置,不是逻辑)
  • data 层必须使用 sqlmock 进行数据库测试

9.2 sqlmock 使用规范

// 标准 setup 函数
func setupServiceTest(t *testing.T) (*sql.DB, sqlmock.Sqlmock, *YourService) {
// 1. 创建 sqlmock
mockDB, mock, err := sqlmock.New()
require.NoError(t, err)

// 2. 使用 Ent driver 包装 mock DB
drv := entsql.OpenDB("postgres", mockDB)
entClient := entclient.NewClient(entclient.Driver(drv))

// 3. 初始化 data/biz/service 层
logger := kmlog.NewStdLogger(io.Discard)
testData := data.NewTestData(mockDB, entClient, logger)
repo := data.NewYourRepo(testData)
uc := biz.NewYourUsecase(repo, logger)
svc := NewYourService(uc, logger)

return mockDB, mock, svc
}

9.3 SQL 模式匹配

  • 使用正则表达式匹配 SQL:mock.ExpectQuery(\SELECT .* FROM "table_name"`)`
  • INSERT 返回 ID:WillReturnRows(sqlmock.NewRows([]string{"id"}).AddRow(int64(101)))
  • SELECT 返回多行:链式调用 .AddRow() 添加多条数据
  • WHERE 条件匹配:SQL 模式会自动忽略参数值,只需匹配结构

9.4 必须验证期望

每个使用 sqlmock 的测试必须验证:

// 在测试结束前
require.NoError(t, mock.ExpectationsWereMet())

10. 反模式(必须避免)

  • 在测试中复制业务实现逻辑
  • 使用 sleep 等待异步结果
  • 依赖测试执行顺序
  • 测试中 hardcode 私有常量

11. AI 协作测试指令模板(推荐)

注意:此处为参考模板。请始终以 ai_testing_prompt.md 为准,它包含 AI 最新的、最严格的输入约束和指令。

11.1 Gateway 服务测试模板

请为以下 Gateway 服务方法编写单元测试,遵循井云服务中心后端测试规范:

【服务类型】
- Gateway service 层

【测试要求】
- Go 1.25.4
- Kratos v2
- 使用函数字段手动 mock gRPC 客户端(禁止使用 gomock)
- table-driven tests
- 覆盖成功 + 至少 2 个失败场景
- 验证所有返回字段
- 使用 require 断言
- 输出完整可编译的 *_test.go 文件

【代码如下】
<在此粘贴被测试代码>

11.2 其他服务 Biz 层测试模板

请为以下方法编写 biz 层单元测试,遵循井云服务中心后端测试规范:

【服务类型】
- biz 层(agent/auth/payment/tenant/user 服务)

【测试要求】
- Go 1.25.4
- Kratos v2
- 使用 sqlmock + Ent ORM mock 数据库操作
- table-driven tests
- 覆盖成功 + 至少 2 个失败场景
- 验证所有返回字段
- 使用 require 断言
- 必须验证 mock.ExpectationsWereMet()
- 输出完整可编译的 *_test.go 文件

【代码如下】
<在此粘贴被测试代码>

11.3 Data 层测试模板

请为以下 data 层方法编写单元测试,遵循井云服务中心后端测试规范:

【服务类型】
- data 层

【测试要求】
- Go 1.25.4
- Kratos v2
- 使用 sqlmock + Ent ORM
- table-driven tests
- 覆盖成功 + 至少 1 个失败场景
- Mock SQL 查询和返回结果
- 验证所有返回字段
- 使用 require 断言
- 必须验证 mock.ExpectationsWereMet()
- 输出完整可编译的 *_test.go 文件

【代码如下】
<在此粘贴被测试代码>

11.4 通用要求

所有测试必须包含:

  • 完整的 import 声明
  • 测试辅助函数(如 setupServiceTest)
  • 详细的测试用例命名
  • 完整的字段验证
  • 错误处理验证

12. 维护与演进

12.1 日常维护要求

  • 新增业务规则 → 必须新增测试用例,确保覆盖率不下降
  • 修复 bug → 必须先补测试,再修代码(TDD 原则)
  • 重构代码 → 运行完整测试套件,确保所有测试通过
  • 测试规范变更 → 更新本文件并同步团队

12.2 井云项目特定要求

  • 微服务接口变更 → 更新相关 Gateway 服务的 mock 测试
  • 数据库 Schema 变更 → 更新所有相关的 sqlmock 测试
  • 业务逻辑复杂化 → 增加边界测试和异常场景测试
  • 性能优化 → 保持测试覆盖率,不因优化而降低测试质量

12.3 质量检查点

  • 代码评审 → 检查测试覆盖率、测试质量、字段完整性
  • CI/CD 流水线 → 自动运行测试,biz 层覆盖率低于 80% 构建失败
  • 发布前检查 → 确保所有核心功能测试通过

本规范为井云服务中心后端项目的强约束规范,任何偏离需在代码评审中明确说明原因。