feat(dev-test): 添加集成测试模板 + TG2 检测规则
- 新增 templates/go-integration-test.md 集成测试代码骨架模板 - SKILL.md 增加 TG2 集成测试检测:跨 handlers/middleware/routes 变更自动触发 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,127 @@
|
||||
# Go 集成测试模板
|
||||
|
||||
## 适用场景
|
||||
|
||||
当 git diff 显示同一功能的 **handlers/ + middleware/ + routes/** 文件均有变更时,除单元测试外应额外生成集成测试。
|
||||
|
||||
适用于:
|
||||
- 多步骤 API 流程(登录→操作→验证→退出)
|
||||
- 中间件拦截验证(权限、限流、模拟状态限制)
|
||||
- 租户隔离 / 数据隔离验证
|
||||
- 跨模块交互(handler ↔ middleware ↔ repository)
|
||||
|
||||
## 检测规则
|
||||
|
||||
通过命名模式识别同一功能的跨文件变更:
|
||||
|
||||
```
|
||||
handlers/{feature}_handler.go
|
||||
middleware/{feature}_middleware.go
|
||||
routes/{feature}_routes.go
|
||||
```
|
||||
|
||||
例如 `impersonation_handler.go` + `impersonation_middleware.go` + `impersonation_routes.go` 同时变更 → 生成 `tests/impersonation_integration_test.go`。
|
||||
|
||||
## 生成规则
|
||||
|
||||
1. 测试文件放在 `backend/tests/` 目录(与 `test_helpers.go` 同包)
|
||||
2. 使用 `SetupTestApp` + `defer TeardownTestApp` 初始化真实路由和数据库
|
||||
3. 每个测试完全自包含:独立创建/清理数据
|
||||
4. 异步持久化操作(goroutine 写 DB)需 `time.Sleep(200-500ms)` 后再验证
|
||||
|
||||
## 代码骨架
|
||||
|
||||
```go
|
||||
package tests
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestFeature_Scenario(t *testing.T) {
|
||||
testApp := SetupTestApp(t)
|
||||
defer TeardownTestApp(t, testApp)
|
||||
|
||||
// === Setup: 创建测试数据 ===
|
||||
// 使用 test_helpers.go 中的 helper 函数
|
||||
sysAdmin := CreateTestSystemUser(t, testApp, "admin_scenario")
|
||||
// tenantID := CreateTestTenant(t, testApp, "scenario_tenant")
|
||||
// enterprise := CreateTestEnterpriseWithTenant(t, testApp, "Corp", "CODE", tenantID)
|
||||
// tenantAdmin := CreateTestTenantAdmin(t, testApp, "tadmin", enterprise.ID, tenantID)
|
||||
|
||||
// === Cleanup: 按外键约束顺序清理 ===
|
||||
defer CleanupImpersonationTestData(t, testApp,
|
||||
[]int{sysAdmin.ID},
|
||||
[]int{}, // enterpriseIDs
|
||||
[]int64{}, // tenantIDs
|
||||
)
|
||||
|
||||
// === 状态变量:跨步骤传递 ===
|
||||
var token string
|
||||
|
||||
t.Run("Step1_InitialAction", func(t *testing.T) {
|
||||
w := MakeAuthenticatedRequest(t, testApp, http.MethodPost,
|
||||
"/api/v1/...",
|
||||
map[string]string{"key": "value"},
|
||||
sysAdmin,
|
||||
)
|
||||
require.Equal(t, http.StatusOK, w.Code, "Step1 failed: %s", w.Body.String())
|
||||
|
||||
var resp map[string]interface{}
|
||||
require.NoError(t, json.Unmarshal(w.Body.Bytes(), &resp))
|
||||
// 提取后续步骤需要的数据
|
||||
token = resp["data"].(map[string]interface{})["token"].(string)
|
||||
})
|
||||
|
||||
t.Run("Step2_VerifyState", func(t *testing.T) {
|
||||
if token == "" {
|
||||
t.Skip("Step1 failed")
|
||||
}
|
||||
// 使用 Step1 产出的 token 继续验证
|
||||
})
|
||||
|
||||
t.Run("StepN_AsyncVerification", func(t *testing.T) {
|
||||
// 异步写入的数据需要等待
|
||||
time.Sleep(300 * time.Millisecond)
|
||||
// 然后验证 DB 数据
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
## 4 种必测场景
|
||||
|
||||
| 场景类型 | 说明 | 示例 |
|
||||
|----------|------|------|
|
||||
| **Happy Path** | 完整正常流程 | 开始模拟 → 访问 API → 查状态 → 退出 → 确认恢复 |
|
||||
| **权限拒绝** | 无权限用户尝试操作 | 普通用户尝试模拟 → 403 |
|
||||
| **隔离验证** | 跨租户/跨企业数据隔离 | tenant2 admin 访问 tenant1 企业 → 403 |
|
||||
| **边界条件** | 输入校验、状态冲突 | 原因太短 → 400;已在模拟中再次模拟 → 403 |
|
||||
|
||||
## Helper 函数参考
|
||||
|
||||
```go
|
||||
// 来自 test_helpers.go
|
||||
SetupTestApp(t) // 初始化完整应用(路由 + DB)
|
||||
CreateTestSystemUser(t, app, username) // 系统管理员 + JWT
|
||||
CreateTestTenant(t, app, name) // 创建租户,返回 int64 ID
|
||||
CreateTestEnterpriseWithTenant(t, app, name, code, tenantID) // 带租户的企业
|
||||
CreateTestTenantAdmin(t, app, username, enterpriseID, tenantID...) // 租户管理员
|
||||
CreateTestEnterpriseUser(t, app, username, enterpriseID) // 普通企业用户
|
||||
ExtractImpersonationToken(t, response) // 从模拟响应提取 token
|
||||
MakeAuthenticatedRequest(t, app, method, path, body, user) // 发送认证请求
|
||||
CleanupImpersonationTestData(t, app, userIDs, enterpriseIDs, tenantIDs) // 按FK顺序清理
|
||||
```
|
||||
|
||||
## 注意事项
|
||||
|
||||
- **Token 链式传递**:模拟 API 返回新 token,后续请求必须用新 token
|
||||
- **异步持久化**:handler 用 goroutine 写 session/audit,测试需 Sleep 后再查
|
||||
- **数据隔离**:每个测试用唯一的 username/code,避免测试间干扰
|
||||
- **清理顺序**:外键约束要求先删子表再删父表
|
||||
Reference in New Issue
Block a user