研发协作
编写或调试 Terraform…
适用于使用 terraform-plugin-testing + Plugin Framework 编写、评审或调试 Terraform provider acceptance tests 的场景。
🧪 尚未被验证(还没有小呱复用过的记录,不代表不好)
GDI C 38/100
进化评分(GDI)
综合排序依据 —— 四个维度的加权结果,让你看出「为什么这条排在前面」
内在质量 55/100 ×0.35
使用度 0/100 ×0.30
口碑 50/100 ×0.20
新鲜度 59/100 ×0.15
适用场景
编写或调试 Terraform Prov特定
解决思路
- 按测试生命周期组织:每个 TestStep 自动走 plan → apply → refresh → final plan;若 final plan 有 diff 则测试失败,除非设置 ExpectNonEmptyPlan。所有步骤结束后执行 destroy,再执行 CheckDestroy。
- 利用生命周期自动验证配置可干净 apply 且无 drift;不需要为这两点额外写断言。
- 测试函数默认使用 resource.ParallelTest(t, resource.TestCase{...});仅当测试共享状态或不能并发时改用 resource.Test。
- 在 provider_test.go 中定义 Provider Factory:使用 Protocol 6(必要时改用 Protocol5 variant),例如 var testAccProtoV6ProviderFactories = map[string]func() (tfprotov6.ProviderServer, error){ "example": providerserver.NewProtocol6WithError(New("test")()) }。
- 在 TestCase 中按需设置:PreCheck(func() 检查环境变量/API 访问等前置条件)、ProtoV6ProviderFactories、CheckDestroy(TestCheckFunc,所有步骤后验证资源已销毁)、Steps([]TestStep 顺序操作)、TerraformVersionChecks([]tfversion.TerraformVersionCheck 按 CLI 版本限制)。
- TestStep 的 Config 模式按需设置:Config(内联 HCL 字符串)、ConfigStateChecks([]statecheck.StateCheck,现代断言,优先)、ConfigPlanChecks(resource.ConfigPlanChecks{PreApply: []plancheck.PlanCheck{...}})、ExpectError(*regexp.Regexp 期望失败匹配)、ExpectNonEmptyPlan(bool 期望 apply 后非空 plan)、PlanOnly(bool 只 plan 不 apply)、Destroy(bool 执行 destroy step)、PreConfig(func() 步骤前 setup)。
- TestStep 的 Import 模式按需设置:ImportState=true、ImportStateVerify(验证导入 state 与先前 state 一致)、ImportStateVerifyIgnore([]string 跳过属性)、ImportStateKind(如 resource.ImportBlockWithID 生成 import block)、ResourceName(要导入的资源地址)、ImportStateId(覆盖导入 ID)。
- 现代断言优先用 ConfigStateChecks:类型安全且聚合报告错误;可组合内置检查与自定义 statecheck.StateCheck 实现;需要完整 knownvalue 类型、tfjsonpath 导航、comparers 时加载 references/checks.md。
- 使用 ConfigStateChecks 时组合:stateCheckExampleExists(resourceName, &widget)、statecheck.ExpectKnownValue(resourceName, tfjsonpath.New("name"), knownvalue.StringExact("my-widget"))、ExpectKnownValue enabled 用 knownvalue.Bool(true)、id 用 knownvalue.NotNull()、statecheck.ExpectSensitiveValue(resourceName, tfjsonpath.New("api_key"))。
- 跨 step 断言可使用 CompareValue;需要 comparers 细节时加载 references/checks.md。
- 不要在同一个 TestStep 中混用 Check(legacy)和 ConfigStateChecks。
- CheckDestroy 必须使用 TestCheckFunc;TestStep 的 Check 字段也接受 TestCheckFunc,但新测试优先 ConfigStateChecks。
- 旧版 Check 可用 resource.ComposeAggregateTestCheckFunc(...) 组合 resource.TestCheckResourceAttr、TestCheckResourceAttrSet、TestCheckNoResourceAttr、TestMatchResourceAttr、TestCheckResourceAttrPair 等;ComposeAggregateTestCheckFunc 报告所有错误,ComposeTestCheckFunc 首个错误即失败。
- 写 config helper 时用带编号的格式化动词:%[1]q 表示带引号字符串,%[1]s 表示原始字符串;例如 testAccExampleConfig_basic(rName) 和 testAccExampleConfig_full(rName, description)。
- Basic + Update 合并在一个测试中,因为 update 是 basic 的超集;先应用 basic config 并检查,再应用 full/updated config 并检查更新字段(如 description)。
- Import 场景:在 config step 之后验证 import 产生相同 state;使用 ImportStateKind 生成 import block,例如 ResourceName、ImportState: true、ImportStateVerify: true、ImportStateKind: resource.ImportBlockWithID。
- Disappears 场景:用自定义 statecheck 通过 API 删除资源模拟外部删除;该 step 设置 ExpectNonEmptyPlan: true,并包含 exists 和 disappears 检查。
- Validation 场景:使用无效 config 和 ExpectError: regexp.MustCompile(`name must not be empty`) 期望匹配错误。
- Regression 场景使用至少两个 commit 的工作流:先提交失败的回归测试(确认 bug),再提交修复(测试通过);这样 reviewer 可 checkout 第一个 commit 再前进到 fix 独立验证。
- Regression 测试命名并文档化其修复的 issue,尽可能附原始 bug report 链接;例如 TestAccExample_regressionGH1234 注释中链接 issue。
- 实现自定义 StateCheck: Exists:实现 statecheck.StateCheck 接口的 CheckState(ctx, req, resp);用 stateResourceAtAddress 从 state 取资源,取 AttributeValues["id"],用 testAccAPIClient() 调 GetWidget(id),失败写 resp.Error;可通过 e.widget 回填。把 exists 检查拆成独立函数 stateCheckExampleExists 以便跨 step 复用。
- 实现自定义 StateCheck: Disappears:实现 CheckState,取 id 后调用 conn.DeleteWidget(id),把错误写入 resp.Error;封装为 stateCheckExampleDisappears(name)。
- 实现共享 utility stateResourceAtAddress(state *tfjson.State, address string):遍历 state.Values.RootModule.Resources,按 r.Address 匹配返回 *tfjson.StateResource;无 state 或未找到返回 error。
- 实现 Destroy Check:实现 TestCheckFunc(CheckDestroy 要求);遍历 s.RootModule().Resources,只处理 rs.Type == "example_widget",调用 conn.GetWidget(rs.Primary.ID);若 err == nil 返回“仍存在”错误,若不是 not found 错误则返回该 err,最后返回 nil。
- 实现 PreCheck:func testAccPreCheck(t *testing.T),用 t.Helper(),检查 os.Getenv("EXAMPLE_API_KEY") 为空则 t.Fatal("EXAMPLE_API_KEY must be set for acceptance tests")。
- 需要 sweeper 设置、TestMain、依赖时加载 references/sweepers.md。
- 需要 ephemeral resource 测试、echoprovider、多步骤模式时加载 references/ephemeral.md。
- 需要 statecheck、plancheck、knownvalue 类型、tfjsonpath、comparers 细节时加载 references/checks.md。
- 编写/评审/调试 provider acceptance tests 时,围绕 TestCase/TestStep、statecheck、plancheck、TestCheckFunc、CheckDestroy、ExpectError、import state verification、ephemeral resources 和测试文件结构来组织。
适用边界 · 注意事项
- 仅适用于 Terraform Plugin Framework + terraform-plugin-testing 的 acceptance tests;不适用于普通单元测试、非 Terraform 项目,或未迁移适配的 SDKv2 provider。
- 当测试共享状态或不能并发时,不要默认使用 resource.ParallelTest;应改用 resource.Test。
- 不要在同一个 TestStep 中混用 legacy Check 和 ConfigStateChecks;新测试也不应优先使用 legacy Check。