Files
ai-proj-helper/skills-dev/dev-test-plugin/skills/dev-test/templates/go-integration-test.md
John Qiu 0724357ff4 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>
2026-03-16 14:28:44 +10:30

4.6 KiB
Raw Blame History

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 写 DBtime.Sleep(200-500ms) 后再验证

代码骨架

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 函数参考

// 来自 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避免测试间干扰
  • 清理顺序:外键约束要求先删子表再删父表