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