From 0724357ff41acecb0795e90cfcbf020603815a57 Mon Sep 17 00:00:00 2001 From: John Qiu Date: Mon, 16 Mar 2026 14:28:44 +1030 Subject: [PATCH] =?UTF-8?q?feat(dev-test):=20=E6=B7=BB=E5=8A=A0=E9=9B=86?= =?UTF-8?q?=E6=88=90=E6=B5=8B=E8=AF=95=E6=A8=A1=E6=9D=BF=20+=20TG2=20?= =?UTF-8?q?=E6=A3=80=E6=B5=8B=E8=A7=84=E5=88=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 新增 templates/go-integration-test.md 集成测试代码骨架模板 - SKILL.md 增加 TG2 集成测试检测:跨 handlers/middleware/routes 变更自动触发 Co-Authored-By: Claude Opus 4.6 --- .../dev-test-plugin/skills/dev-test/SKILL.md | 18 +++ .../dev-test/templates/go-integration-test.md | 127 ++++++++++++++++++ 2 files changed, 145 insertions(+) create mode 100644 skills-dev/dev-test-plugin/skills/dev-test/templates/go-integration-test.md diff --git a/skills-dev/dev-test-plugin/skills/dev-test/SKILL.md b/skills-dev/dev-test-plugin/skills/dev-test/SKILL.md index 3b055cf..d2cb350 100644 --- a/skills-dev/dev-test-plugin/skills/dev-test/SKILL.md +++ b/skills-dev/dev-test-plugin/skills/dev-test/SKILL.md @@ -14,6 +14,7 @@ description: 软件测试技能。用于单元测试、集成测试、E2E测试 | `ios-testing.md` | iOS 测试 (XCTest + Swift Concurrency) | | `android-testing.md` | Android 测试 (JUnit + Espresso + Compose) | | `e2e-testing.md` | E2E Playwright:API Mock 冒烟测试(无后端)+ 全链路集成测试 | +| `templates/go-integration-test.md` | Go 集成测试模板(多步骤 API 流程、中间件验证、租户隔离) | --- @@ -141,3 +142,20 @@ ai-proj task append-doc --id --content "# 测试报告 7. **Mock 仅限 Handler 层** - handler 层可以 mock biz 接口 + httptest 8. **E2E 冒烟测试必须用 API Mock** - E2E 门禁不能依赖后端,否则形同虚设。用 `page.route()` 拦截 API,见 `e2e-testing.md`。质量门禁流程(Gates 1-5)定义在 `req-test-gate` 技能中 9. **李宁测试用例** - Excel 导出见 `coolbuy-legacy` 技能的 `test-cases-excel.md` + +--- + +## TG2 集成测试检测 + +### 模板映射 + +| 变更范围 | 测试输出位置 | 模板 | +|----------|-------------|------| +| 单个 handler 或 service | `*_test.go` (同目录) | `go-testing.md` | +| handlers/ + middleware/ + routes/ (同一功能) | `tests/{feature}_integration_test.go` | `templates/go-integration-test.md` | + +### 检测规则 + +若 git diff 显示同一功能的 `handlers/`、`middleware/`、`routes/` 文件均有变更(通过命名模式识别,如 `impersonation_handler.go` + `impersonation_middleware.go` + `impersonation_routes.go`),则除单元测试外**额外生成** `backend/tests/` 下的集成测试。 + +识别方式:提取文件名中的功能前缀(如 `impersonation`),若在三个目录中均出现,则触发集成测试生成。 diff --git a/skills-dev/dev-test-plugin/skills/dev-test/templates/go-integration-test.md b/skills-dev/dev-test-plugin/skills/dev-test/templates/go-integration-test.md new file mode 100644 index 0000000..32a4849 --- /dev/null +++ b/skills-dev/dev-test-plugin/skills/dev-test/templates/go-integration-test.md @@ -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,避免测试间干扰 +- **清理顺序**:外键约束要求先删子表再删父表