☰
Terratest v2 迁移指南:从单模块到 16 个独立模块的完整升级路径
2026/9/27 21:15:32 网站建设 项目流程
  • 测试
  • 开发工具
  • DevOps
  • 质量保障

【免费下载链接】terratest

Terratest is a Go library that makes it easier to write automated tests for your infrastructure code.

项目地址:https://gitcode.com/gh_mirrors/te/terratest
点击查看免费下载

Terratest v2 将原本单一的github.com/gruntwork-io/terratest模块拆分为 16 个相互独立的 Go 模块,让测试代码只依赖真正用到的部分——导入terraform的测试不再被迫拉入 AWS SDK、client-go 以及其余所有 provider 的依赖树。本文基于 docs/_docs/04_migrating-to-v2/overview.md 梳理 v2 带来的五大变更、推荐的迁移顺序、已移除的包与行为变化,并结合当前仓库源码给出可验证的迁移步骤与验证命令。读完本文,你将能够把一套基于 v1 的 Terratest 测试代码系统化、可回滚地升级到 v2。

是否应该立即迁移

v2 目前处于 beta 阶段;v1 进入维护模式,在 v2.0.0 正式发布(general availability)后的 12 个月内仅接收安全修复。是否迁移取决于你的诉求:

  • 立即迁移:如果你想要更小的依赖图,或者正在从零搭建新的测试工程;
  • 等待 v2.0.0:如果你不愿跟随 beta 版本迭代。

迁移可以增量进行

v1 与 v2 的导入路径不同,因此两者可以在同一个 Go module 中共存,你可以逐个包地完成转换。这一变更只触及.go文件以及go.mod、go.sum,所以一次git checkout即可整体撤销,风险可控。

最低 Go 版本不变

v2 未提高最低 Go 版本要求。作为参考,当前仓库的 go.work 与各子模块(如 modules/terraform/go.mod、modules/core/go.mod)均声明go 1.26.0。

五大变更一览

v2 相对 v1 的破坏性变更全部可归为以下五类,其中前四类都能被编译器或静态检查工具发现,最后一类需要人工阅读代码。

1. v1 中已废弃的符号全部删除

v1 在引入替代实现的同时保留了废弃别名(deprecated aliases);v2 直接删除它们。这是整个迁移中改动量最大的一步,而且绝不只是Context系列变体:

  • 非Context包装函数被移除:如terraform.Apply现在只存在ApplyContext形态;
  • 首字母缩写规范重命名:如random.UniqueId变为UniqueID,aws.GetAccountIdE变为GetAccountIDContextE;
  • 整形后的辅助函数:如packer.BuildAmi变为BuildArtifactContextE。

最可靠的排查方式:先对你现有的 v1 代码运行 staticcheck,在处理导入路径之前清掉每一条 SA1019(使用了废弃符号)告警。当 v1 代码告警清零后,本文后续的迁移步骤才适用。

// v1 out := terraform.Apply(t, options) // v2 out := terraform.ApplyContext(t, t.Context(), options)

Context系列变体始终采用(t, ctx, ...原始参数)的形参顺序。t.Context()是最佳默认值(在 Go 1.24+ 的*testing.T上可用,它把 context 生命周期绑定到测试本身),context.Background()同样可用。这一步骤的完整背景可参考 v1 迁移指南:v1 中Foo/FooE/FooContext/FooContextE四种形态并存,非Context变体只是发出废弃警告,因此先在 v1 上完成 Context 化(两种形态都能编译)比与导入路径重写同时进行要容易得多。

2. 导入路径增加/v2后缀

/v2加在模块根之后,而不是路径末尾:

// v1 "github.com/gruntwork-io/terratest/modules/terraform" // v2 "github.com/gruntwork-io/terratest/modules/terraform/v2"

这与仓库的实际布局一致:modules/terraform/go.mod 第一行即module github.com/gruntwork-io/terratest/modules/terraform/v2。

3. 六个工具包并入core

random、files、logger、shell、retry、testing不再是独立模块,而是core模块的子包:

// v1 "github.com/gruntwork-io/terratest/modules/random" // v2 "github.com/gruntwork-io/terratest/modules/core/v2/random"

在仓库中,modules/core 目录下的random/、files/、logger/(含parser/子目录)、shell/、retry/、testing/即为这些子包;modules/core/formatting是 v2 新增、无 v1 对应物的子包(v1 中它是不可导入的internal/lib/formatting)。各子包的导入标识符(package identifier)在调用点保持不变。

4. 三个包去掉连字符改名

这不仅改变导入路径,还改变调用点的包标识符:

  • http-helper→httphelper
  • dns-helper→dnshelper
  • test-structure→teststructure

仓库目录同样与此对应:modules/httphelper、modules/dnshelper、modules/teststructure。完整对照表见 import map。

5. 每个模块都需要独立的require

v1 在go.mod中只需一行依赖,v2 需要为你导入的每个模块各加一行。建议用go get添加而不是手写:

go get github.com/gruntwork-io/terratest/modules/terraform/v2@v2.0.0-beta.2 go get github.com/gruntwork-io/terratest/modules/aws/v2@v2.0.0-beta.2

每个模块都按modules/<name>/vX.Y.Z打 tag,因此上面的命令对应 tag 为modules/terraform/v2.0.0-beta.2。16 个模块一同发布,且它们之间的交叉依赖(cross-module requires)被固定(pinned)到发布版本,所以请让所有模块保持在同一版本上。当前最新版本请查看官方 releases 页面。完整的路径映射见 import map。

推荐的迁移顺序

  1. 迁移到Context变体,理想情况下仍停留在 v1,这样两种形态都能编译;
  2. 重写导入路径与包标识符。这一步是机械性的,编译器会找出全部问题;
  3. 修复符号迁移(symbol relocations)。同样是编译错误,同样机械,明细见 rewriting imports;
  4. 为每个模块添加require,然后运行go mod tidy;
  5. 人工复核行为变更(behavior changes)——编译器不会替你发现这两处问题,且都在k8s模块中。

步骤 1 到 4 都是编译器可检测的,构建通过即代表完成;步骤 5 不是:代码无论改不改都能编译,因此在宣告迁移完成前必须通读行为变更一节。

已移除的包与二进制

以下六个包和两个二进制文件不会延续到 v2。其中三个有标准库替代方案:

v1替代方案
modules/collections标准库slices
modules/environment标准库os.Getenv
modules/git标准库os/exec
modules/slack无;如需则从 v1 自行 vendor
modules/version-checker无;用 shell 调用替代
modules/oci无;Oracle Cloud 不延续到 v2,请留在 v1

二进制方面,cmd/pick-instance-type与cmd/terratest_log_parser均被移除。其中日志解析器的库实现得以保留,位于modules/core/v2/logger/parser(仓库中对应 modules/core/logger/parser,包含parser.go、store.go、failed_test_marker.go等实现文件)。如果你依赖slack、version-checker或oci,v1 仍可用,留在 v1 即可。

没有改变的部分

Foo/FooE命名约定保持不变:FooContext失败时终止测试(t.Fatal),FooContextE返回error给调用方。除删除非Context包装函数和上述符号迁移外,函数参数与返回类型保持一致。v1 写入的测试数据文件(filenames 与 JSON 布局未变)在 v2 中可直接加载。

深入:批量重写导入路径

rewriting imports 给出了可直接执行的批量重写方案。以下命令按 BSDsed(macOS 自带)书写;在 Linux 上需去掉-i后的''。它们以#作为分隔符(避免与正则的|冲突),并避免使用 BSDsed不支持且会静默忽略的\b。

第 1 步:将工具包并入core

find . -name '*.go' -exec sed -i '' -E \ 's#gruntwork-io/terratest/modules/(random|files|logger|shell|retry|testing)#gruntwork-io/terratest/modules/core/v2/\1#g' {} +

第 2 步:重命名三个带连字符的包(路径、包标识符以及任何残留的别名)

find . -name '*.go' -exec sed -i '' -E \ -e 's#gruntwork-io/terratest/modules/http-helper#gruntwork-io/terratest/modules/httphelper/v2#g' \ -e 's#gruntwork-io/terratest/modules/dns-helper#gruntwork-io/terratest/modules/dnshelper/v2#g' \ -e 's#gruntwork-io/terratest/modules/test-structure#gruntwork-io/terratest/modules/teststructure/v2#g' \ -e 's#(^|[^A-Za-z0-9_])http_helper\.#\1httphelper.#g' \ -e 's#(^|[^A-Za-z0-9_])dns_helper\.#\1dnshelper.#g' \ -e 's#(^|[^A-Za-z0-9_])test_structure\.#\1teststructure.#g' \ -e 's#^([[:space:]]*)(http_helper|dns_helper|test_structure) "#\1"#' {} +

最后一个表达式至关重要:这些包常以显式别名导入,例如

test_structure "github.com/gruntwork-io/terratest/modules/test-structure"

只重写路径会让旧别名继续绑定新包,导致每个重写后的调用点报undefined: teststructure。该表达式会去掉别名,使包名直接生效。

第 3 步:给其余模块加/v2后缀

find . -name '*.go' -exec sed -i '' -E \ 's#gruntwork-io/terratest/modules/(aws|azure|gcp|k8s|helm|ssh|docker|packer|database|opa|terraform|terragrunt)([^/a-z]|$)#gruntwork-io/terratest/modules/\1/v2\2#g' {} +

第 4 步:重新格式化

重写后导入路径的字母序被打乱,导入块不再有序:

gofmt -w .

符号迁移(symbol relocations)

有八个函数迁移到了“拥有其操作类型”的模块,使teststructure不再依赖aws、k8s、packer和ssh。函数签名与磁盘文件名不变,因此这只是限定符(qualifier)的替换:

v1v2
test_structure.{Save,Load}Ec2KeyPairaws.{Save,Load}Ec2KeyPair
test_structure.{Save,Load}KubectlOptionsk8s.{Save,Load}KubectlOptions
test_structure.{Save,Load}PackerOptionspacker.{Save,Load}PackerOptions
test_structure.{Save,Load}SSHKeyPairssh.{Save,Load}SSHKeyPair

这一迁移在仓库源码中可逐一验证:modules/aws/save_test_data.go 定义了SaveEc2KeyPair/LoadEc2KeyPair,modules/k8s/save_test_data.go 定义了SaveKubectlOptions/LoadKubectlOptions,modules/packer/save_test_data.go 与 modules/ssh/save_test_data.go 同理。目标模块通常已经被导入(因为被保存的值正是来自该模块);若未导入则补上 import,之后重新运行go mod tidy——这一步可能拉入此前未 require 的模块。

teststructure中其余内容全部保留:modules/teststructure/teststructure.go 中的RunTestStage、CopyTerraformFolderToTemp、Terraform 选项辅助函数,以及 modules/teststructure/save_test_data.go 中的SaveString/LoadString、SaveInt/LoadInt、SaveArtifactID/LoadArtifactID和泛型SaveTestData/LoadTestData。

同时导入 Terratestaws与 AWS SDK 的文件

如果一个文件同时导入 AWS SDK 与 Terratest 的aws,通常会把裸aws绑定给 SDK:

import ( "github.com/aws/aws-sdk-go-v2/aws" terraAws "github.com/gruntwork-io/terratest/modules/aws/v2" )

此时上述符号迁移会解析到 SDK 并编译失败。解决办法是使用该文件中的别名:terraAws.LoadEc2KeyPair。这是脚本化重写唯一需要人工介入的地方,编译器会准确指出它。

验证迁移

确认没有遗留 v1 路径且一切仍可构建:

grep -rn 'gruntwork-io/terratest/modules/' --include='*.go' . | grep -v '/v2' go test -run '^$' ./...

grep应当无任何输出。使用go test -run '^$'而非go build:它只编译_test.go文件而不运行任何测试——对 Terratest 这类测试套件而言,你的全部代码恰好都在测试文件里。

深入:两处编译器发现不了的行为变更

这两处变更都在k8s模块中;不使用k8s可以跳过本节。细节见 behavior changes。

1. Node 地址优先返回ExternalIP

k8s.FindNodeHostnameContextE,以及针对 NodePort 服务的GetServiceEndpointContextE,在 Node 对象上存在ExternalIP时优先返回它;不存在时则与此前完全一致地回退到内部主机名(internal hostname)。

云控制器管理器(cloud controller manager)会把实例的公网 IP 记录为ExternalIP。Terratest 此前忽略该字段,在 AWS 上会调用ec2:DescribeInstances去查公网 IP。直接读取 Node 对象得到同样的结果却省去一次 API 调用,因此:

  • 此路径不再需要ec2:DescribeInstances;
  • k8s模块不再依赖aws模块,从其依赖图中移除了 22 个 AWS 服务客户端。

需要检查什么:如果你的集群对外通告了ExternalIP,而测试此前收到的是内部主机名,现在将收到外部地址。这是文档化的行为,也几乎肯定是你想要的,但它是一个不同的字符串。那些断言端点值、或依赖通过内部地址访问节点的测试,是需要重点检查的对象。

函数签名不变。ExternalIP优先规则对所有 provider 生效。对于没有通告ExternalIP的 AWS 节点,v2 新增了一对接收*KubectlOptions的函数,可注入你提供的查询函数:

options := k8s.NewKubectlOptions("", kubeconfig, "default") options.NodePublicIPLookup = aws.GetPublicIpsOfEc2InstancesContextE hostname, err := k8s.FindNodeHostnameWithOptionsContextE(t, ctx, options, node)

从源码看,NodePublicIPLookup定义在 modules/k8s/kubectl_options.go,类型为func(t testing.TestingT, ctx context.Context, instanceIDs []string, region string) (map[string]string, error),并标记了json:"-"。它只在 Node 自身的ExternalIP检查之后才被查询,因此大多数调用方可以保持为 nil。对应测试见 modules/k8s/node_hostname_test.go,覆盖了 AWS provider ID、GCE provider ID、lookup 返回空、lookup 报错等场景;modules/k8s/kubectl_options_test.go 还专门验证了该函数字段不会被序列化进 JSON。

2. 携带RestConfig的KubectlOptions无法保存

对NewKubectlOptionsWithRestConfig构建出的KubectlOptions执行json.Marshal,现在会返回一个包装了k8s.ErrRestConfigNotSerializable的错误。请用errors.Is而不是==匹配它:encoding/json返回的是*json.MarshalerError。这会影响k8s.SaveKubectlOptions、teststructure.SaveTestData,以及任何自行 marshal options 的代码。

这一行为在 v1 中同样会失败,但报错是不透明的json: unsupported type: transport.WrapperFunc——因为rest.Config含有encoding/json无法处理的函数类型字段。v2 的改进是错误信息明确说明了问题所在及应对方式。源码层面,modules/k8s/errors.go 定义了该哨兵错误,modules/k8s/kubectl_options.go 的MarshalJSON在 options 携带RestConfig时返回它。

之所以刻意设计为报错而非静默丢弃配置:一旦丢弃,重新加载的 options 将完全丢失集群身份,回退到环境中的 kubeconfig,把测试跑到另一个集群上去。

如何应对:对于分阶段(staged)测试,用 kubeconfig 路径或集群内认证构建 options,两者都能完整往返(round trip):

options := k8s.NewKubectlOptions(contextName, configPath, namespace) // or options := k8s.NewKubectlOptionsWithInClusterAuth()

如果运行时确实需要rest.Config,可以继续构建,但要在每个阶段重新构建而不是保存它。

完整对照表:import map

import map 给出了 v1 到 v2 的完整路径映射,批量应用方式见 rewriting imports。

并入core(调用点包标识符不变)

v1v2
modules/randommodules/core/v2/random
modules/filesmodules/core/v2/files
modules/loggermodules/core/v2/logger
modules/shellmodules/core/v2/shell
modules/retrymodules/core/v2/retry
modules/testingmodules/core/v2/testing

modules/logger/parser变为modules/core/v2/logger/parser。modules/core/v2/formatting是 v2 新增、无 v1 对应物(v1 中为不可导入的internal/lib/formatting)。

重命名(路径与包标识符都变)

v1v2
modules/http-helper,http_helper.Xmodules/httphelper/v2,httphelper.X
modules/dns-helper,dns_helper.Xmodules/dnshelper/v2,dnshelper.X
modules/test-structure,test_structure.Xmodules/teststructure/v2,teststructure.X

仅加后缀(包标识符不变)

v1v2
modules/awsmodules/aws/v2
modules/azuremodules/azure/v2
modules/gcpmodules/gcp/v2
modules/k8smodules/k8s/v2
modules/helmmodules/helm/v2
modules/sshmodules/ssh/v2
modules/dockermodules/docker/v2
modules/packermodules/packer/v2
modules/databasemodules/database/v2
modules/opamodules/opa/v2
modules/terraformmodules/terraform/v2
modules/terragruntmodules/terragrunt/v2

已移除

v1替代方案
modules/collections标准库slices
modules/environment标准库os.Getenv
modules/git标准库os/exec
modules/slack无;需要时从 v1 vendor
modules/version-checker无;用 shell 调用替代
modules/oci无;Oracle Cloud 不延续到 v2
cmd/pick-instance-type无
cmd/terratest_log_parser不再作为二进制发布;库实现保留在modules/core/v2/logger/parser

这些包先在 v1 中废弃,随后在 v2 切换时删除。如果你依赖slack、version-checker或oci,v1 仍可用,留在 v1 是合适的选择。

需要帮助

在 Terratest 仓库中提交 issue,说明你来自的版本(v1)以及遇到的错误信息。如果你发现本指南存在遗漏,可对docs/_docs/04_migrating-to-v2/提交 PR。

  • 测试
  • 开发工具
  • DevOps
  • 质量保障

【免费下载链接】terratest

Terratest is a Go library that makes it easier to write automated tests for your infrastructure code.

项目地址:https://gitcode.com/gh_mirrors/te/terratest
点击查看免费下载
上一篇:Kosmos-2实战指南:从安装到高级应用
下一篇:OpenPose模型训练全流程:从数据集准备到模型部署

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询