Files
huang b68e7ec013
All checks were successful
构建并部署到测试环境(无 SSH) / build-and-deploy (push) Successful in 15s
优化测试数据库连接管理
- 创建全局单例连接池,性能提升 6-7 倍
- 实现 NewTestTransaction/GetTestRedis/CleanTestRedisKeys
- 移除旧的 SetupTestDB/TeardownTestDB API
- 迁移所有测试文件到新方案(47 个文件)
- 添加测试连接管理规范文档
- 更新 AGENTS.md 和 README.md

性能对比:
- 旧方案:~71 秒(204 测试)
- 新方案:~10.5 秒(首次初始化 + 后续复用)
- 内存占用降低约 80%
- 网络连接数从 204 降至 1
2026-01-22 14:38:43 +08:00

265 lines
7.9 KiB
Markdown

# testing-standards Specification
## Purpose
TBD - created by archiving change optimize-test-db-connection. Update Purpose after archive.
## Requirements
### Requirement: 全局单例数据库连接
测试套件 **SHALL** 使用全局单例模式管理数据库连接,避免重复创建连接。
**技术约束**:
- 使用 `sync.Once` 确保连接只初始化一次
- 整个测试套件(多个测试文件)共享同一个 `*gorm.DB` 实例
- `AutoMigrate` 只在首次连接时执行一次
- 连接失败应导致测试跳过,不应 panic
#### Scenario: 多个测试共享连接
- **GIVEN** 测试套件包含 100+ 个测试用例
- **WHEN** 执行 `go test ./...`
- **THEN** 只创建一次数据库连接
- **AND** 所有测试共享同一个连接池
- **AND** `AutoMigrate` 只执行一次
#### Scenario: 连接失败自动跳过
- **GIVEN** 测试数据库不可用
- **WHEN** 执行测试
- **THEN** 测试标记为 SKIP 而非 FAIL
- **AND** 显示跳过原因: "无法连接测试数据库"
---
### Requirement: 全局单例 Redis 连接
测试套件 **SHALL** 使用全局单例模式管理 Redis 连接,避免重复创建连接。
**技术约束**:
- 使用 `sync.Once` 确保连接只初始化一次
- 整个测试套件共享同一个 `*redis.Client` 实例
- 连接失败应导致测试跳过,不应 panic
#### Scenario: 多个测试共享 Redis 连接
- **GIVEN** 测试套件包含 50+ 个需要 Redis 的测试
- **WHEN** 执行 `go test ./...`
- **THEN** 只创建一次 Redis 连接
- **AND** 所有测试共享同一个 Redis 客户端
---
### Requirement: 事务隔离
每个测试 **SHALL** 在独立事务中运行,并在测试结束后自动回滚,确保测试间完全隔离。
**技术约束**:
- 使用 `db.Begin()` 开启事务
- 使用 `t.Cleanup(func() { tx.Rollback() })` 注册回滚函数
- 即使测试 panic 也能确保事务回滚(Go 的 defer/Cleanup 机制保证)
- 事务隔离级别使用数据库默认值(PostgreSQL: READ COMMITTED)
#### Scenario: 测试数据自动回滚
- **GIVEN** 测试 A 创建了用户 "test_user"
- **WHEN** 测试 A 完成
- **THEN** 事务自动回滚
- **AND** 数据库中不存在 "test_user"
- **AND** 测试 B 看不到测试 A 的数据
#### Scenario: 测试 panic 后自动清理
- **GIVEN** 测试 C 在执行中触发 panic
- **WHEN** panic 发生
- **THEN** `t.Cleanup` 仍然执行
- **AND** 事务被回滚
- **AND** 数据库状态恢复到测试前
---
### Requirement: Redis 键自动清理
每个测试 **SHALL** 使用测试名称作为 Redis 键前缀,并在测试结束后自动清理。
**技术约束**:
- 键前缀格式: `test:{TestName}:*`
- 使用 `t.Cleanup()` 注册清理函数
- 清理逻辑: `KEYS pattern` + `DEL keys...`
- 支持嵌套测试(子测试继承父测试的前缀)
#### Scenario: 测试前清理已有键
- **GIVEN** Redis 中存在键 `test:TestUserCreate:user:1` (上次运行残留)
- **WHEN** 测试 `TestUserCreate` 开始
- **THEN** 清理所有匹配 `test:TestUserCreate:*` 的键
- **AND** Redis 处于干净状态
#### Scenario: 测试后自动清理
- **GIVEN** 测试 `TestUserLogin` 创建了键 `test:TestUserLogin:session:abc`
- **WHEN** 测试完成
- **THEN** `t.Cleanup` 自动删除所有 `test:TestUserLogin:*`
- **AND** Redis 中不残留测试数据
---
### Requirement: 向后兼容性
新的连接管理方案 **SHALL** 与现有的 `SetupTestDB`/`TeardownTestDB` 方案共存,支持渐进迁移。
**技术约束**:
- 保留 `testutils/setup.go` 中的旧函数
- 在旧函数上添加 `// Deprecated` 注释
- 新旧方案可在同一测试套件中共存
- 迁移指引作为注释提供
#### Scenario: 旧测试正常运行
- **GIVEN** 测试文件使用 `SetupTestDB(t)`
- **WHEN** 执行测试
- **THEN** 测试正常通过
- **AND** 不影响其他使用新方案的测试
#### Scenario: 新旧方案混用
- **GIVEN** 测试套件包含 50% 旧方案测试,50% 新方案测试
- **WHEN** 执行 `go test ./...`
- **THEN** 所有测试正常运行
- **AND** 性能逐步提升(随迁移进度)
---
### Requirement: 简洁的测试代码
测试用例 **SHALL** 使用简洁的 API 创建事务和清理 Redis,减少样板代码。
**API 约束**:
- `NewTestTransaction(t)` 返回事务,自动注册回滚
- `CleanTestRedisKeys(t)` 清理 Redis,自动注册清理函数
- 无需显式 `defer` 或手动清理
- 函数名清晰表达意图
#### Scenario: 最小化样板代码
- **GIVEN** 开发者编写新测试
- **WHEN** 使用新 API
- **THEN** 只需 2 行代码完成设置:
```go
tx := testutils.NewTestTransaction(t)
testutils.CleanTestRedisKeys(t)
```
- **AND** 无需关心清理逻辑
#### Scenario: 对比旧方案
- **GIVEN** 旧方案需要 4 行代码:
```go
db, redisClient := testutils.SetupTestDB(t)
defer testutils.TeardownTestDB(t, db, redisClient)
```
- **WHEN** 使用新方案
- **THEN** 只需 2 行,且意图更清晰
---
### Requirement: 性能优化
测试套件运行速度 **SHALL** 显著提升,通过减少连接创建和表结构检查次数。
**性能目标**:
- 连接创建次数: 从 N(测试数量) 降低到 1
- AutoMigrate 次数: 从 N 降低到 1
- 测试套件总耗时提升: ≥ 5 倍
- 内存占用降低: ≥ 70%
#### Scenario: 大型测试套件性能提升
- **GIVEN** 测试套件包含 200 个测试
- **WHEN** 全部迁移到新方案
- **THEN** 总耗时从 ~70 秒降低到 ~10 秒
- **AND** 性能提升约 7 倍
#### Scenario: 连接复用
- **GIVEN** 测试套件运行期间
- **WHEN** 监控数据库连接数
- **THEN** 最多保持 1 个连接(来自连接池)
- **AND** 无重复连接创建
---
### Requirement: 子测试事务行为
使用 `t.Run` 创建子测试时,**SHALL** 明确子测试与父事务的关系。
**技术约束**:
- 父测试开启的事务,子测试默认共享
- 如需隔离,子测试必须开启独立事务
- 不支持在事务内使用 `t.Parallel()`(GORM 事务非线程安全)
#### Scenario: 子测试共享父事务
- **GIVEN** 父测试开启事务 `tx := NewTestTransaction(t)`
- **WHEN** 子测试使用 `t.Run` 运行
- **THEN** 子测试共享父事务
- **AND** 所有数据在父测试结束时统一回滚
#### Scenario: 子测试独立事务
- **GIVEN** 子测试需要数据隔离
- **WHEN** 子测试内调用 `tx := NewTestTransaction(t)`
- **THEN** 子测试拥有独立事务
- **AND** 子测试结束时独立回滚
---
### Requirement: Table-Driven Tests 支持
Table-Driven Tests **SHALL** 正确处理事务共享和回滚行为。
**技术约束**:
- 父测试开启事务,所有 cases 共享
- 所有 cases 的数据在测试结束时统一回滚
- 如需 case 间隔离,每个 case 开启独立事务
#### Scenario: Cases 共享父事务
- **GIVEN** Table-Driven Test 有 5 个 test cases
- **WHEN** 父测试开启事务
- **THEN** 所有 cases 在同一事务中运行
- **AND** Case 1 的数据对 Case 2 可见
- **AND** 所有数据在测试结束时统一回滚
#### Scenario: Cases 独立事务
- **GIVEN** 每个 case 需要独立数据环境
- **WHEN** 每个 case 内调用 `NewTestTransaction(t)`
- **THEN** Cases 间完全隔离
- **AND** Case 1 的数据对 Case 2 不可见
---
### Requirement: 规范文档化
测试连接管理规范 **SHALL** 以文档形式提供,并集成到项目开发规范中。
**文档要求**:
- 路径: `docs/testing/test-connection-guide.md`
- 包含: 原理说明、使用示例、最佳实践、常见陷阱
- 在 `AGENTS.md` 中引用,作为唯一标准
- 包含性能对比数据和迁移指南
#### Scenario: 开发者查找测试规范
- **GIVEN** 新加入的开发者需要编写测试
- **WHEN** 查阅 `AGENTS.md` 测试规范章节
- **THEN** 能找到 `test-connection-guide.md` 的引用
- **AND** 文档包含完整的 API 说明和示例
#### Scenario: 迁移指南
- **GIVEN** 现有测试使用旧的 `SetupTestDB`
- **WHEN** 查阅迁移指南
- **THEN** 提供逐步迁移步骤
- **AND** 包含前后代码对比示例