Files
junhong_cmp_fiber/openspec/specs/testing-standards/spec.md
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

7.9 KiB

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 行代码完成设置:
    tx := testutils.NewTestTransaction(t)
    testutils.CleanTestRedisKeys(t)
    
  • AND 无需关心清理逻辑

Scenario: 对比旧方案

  • GIVEN 旧方案需要 4 行代码:
    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 包含前后代码对比示例