跳至主要内容

命令:test

tofu test 命令允许您通过创建真实的 基础设施并检查是否满足所需的条件(断言)来测试您的 OpenTofu 配置。测试完成后,OpenTofu 会销毁它创建的资源。

用法

用法:tofu test [选项]

此命令将执行当前目录或名为 tests 的目录中的所有 *.tftest.hcl*.tftest.json*.tofutest.hcl*.tofutest.json 文件。您可以使用下面的 选项 自定义此行为。

扩展优先级

当同一目录中存在具有相同基本名称的 .tftest.hcl.tofutest.hcl 文件时,OpenTofu 将优先考虑 .tofutest.hcl 文件并忽略 .tftest.hcl 文件。例如

  • 如果 main.tftest.hclmain.tofutest.hcl 都存在于同一目录中,则 OpenTofu 将仅加载 main.tofutest.hcl 并忽略 main.tftest.hcl

这确保了当两种文件都可用时,.tofu 文件始终优先于 .tf 文件。对于希望其模块同时支持 OpenTofu 和 Terraform 并希望为每个模块创建不同测试的模块作者来说,这种情况非常有用。

相同的规则适用于基于 JSON 的测试文件

  • 如果 main.tftest.jsonmain.tofutest.json 都存在于同一目录中,则 OpenTofu 将仅加载 main.tofutest.json 并忽略 main.tftest.json

选项

  • -test-directory=path 设置测试目录(默认值:“tests”)。当您运行 tofu test 时,OpenTofu 将在指定的目录以及当前目录中搜索测试文件。路径应相对于当前工作目录。
  • -filter=testfile 指定要运行的单个测试文件。多次使用此选项以指定多个文件。路径应相对于当前工作目录。
  • -var 'foo=bar' 设置根模块的输入变量。多次指定此选项以添加多个变量。
  • -var-file=filename 从指定的文件设置多个变量。除了此文件之外,OpenTofu 还自动加载 terraform.tfvars*.auto.tfvars。多次使用此选项以指定多个文件。
  • -json 将输出格式更改为 JSON。
  • -no-color 在命令输出中禁用彩色输出。
  • -verbose 打印每个测试运行块在执行时的计划或状态。

目录结构

tofu test 命令支持两种目录布局:扁平化或嵌套。

这种布局将 *.tftest.hcl 测试文件直接放在与其测试的 *.tf 文件旁边。没有规则要求每个 *.tf 文件都必须有自己的测试文件,但遵循此做法是一个好习惯。

代码块
.
├── main.tf
├── main.tftest.hcl
├── foo.tf
├── foo.tftest.hcl
├── bar.tf
└── bar.tftest.hcl

测试模块

测试模块时,您可以为每个模块使用上述目录结构之一。

使用此布局,运行 tofu test -test-directory=./path/to/module 来测试目标模块。

代码块
.
├── module1
│ ├── main.tf
│ ├── main.tftest.hcl
│ ├── foo.tf
│ ├── foo.tftest.hcl
│ ├── bar.tf
│ └── bar.tftest.hcl
└── module2
└── ...

*.tftest.hcl / *.tofutest.hcl 文件结构

OpenTofu 的测试语言类似于 OpenTofu 的主要语言,并使用相同的块结构。

一个测试文件由以下部分组成:

run

一个 run 块包含一个单个测试用例,该用例运行 tofu applytofu plan,然后评估所有 assert 块。测试完成后,它使用 tofu destroy 删除临时创建的资源。

一个 run 块由以下元素组成:

名称类型描述
assert定义断言,以检查您的代码(例如 main.tf)是否正确创建了基础设施。如果您未指定任何 assert 块,则 OpenTofu 将简单地应用配置,而无需任何断言。
module覆盖正在测试的模块。您可以使用它来加载辅助模块以进行更详细的测试。
expect_failures列表在当前运行中应无法预置的资源列表。
variables定义当前测试用例的变量。请参阅变量部分
commandplanapply定义 OpenTofu 将执行的命令,planapply。默认为 apply
plan_optionsplanapply 操作的选项。
providers对象提供程序的别名。
override_resource定义要为运行覆盖的资源。
override_data定义要为运行覆盖的数据源。
override_module定义要为运行覆盖的模块调用。

run.assert

您可以在 run 块内指定 assert 块来测试 applyplan 操作完成后基础设施的状态。您可以定义的块数量没有理论上的限制。

每个块都需要以下两个属性:

  1. condition 是一个OpenTofu 条件,它应该返回 true 以使测试通过,返回 false 以使测试失败。该条件**必须**引用主代码中的资源、数据源、变量、输出或模块,否则 OpenTofu 将拒绝运行测试。
  2. error_message 是一个字符串,解释测试失败时发生的情况。

请注意,条件仅允许您对当前 OpenTofu 状态执行基本检查并使用 OpenTofu 函数。**您不能在测试代码中直接定义其他数据源。**要解决此限制,您可以使用module来加载辅助模块。

run.module

在某些情况下,您可能会发现条件表达式中提供的工具不足以测试您的代码是否正确创建了基础设施。

您可以使用 module 块来覆盖 tofu test 加载的主模块。这使您有机会创建其他资源或数据源,您可以在 assert 条件中使用这些资源或数据源。

其语法类似于在普通 OpenTofu 代码中加载模块:

代码块
run "test" {
module {
source = "./some-module"
}
}

module 块具有以下两个属性:

  • source 属性指向要加载的模块的目录或任何其他模块源
  • version 指定您要使用的模块的版本。

variablesrun.variables

正在测试的代码(例如 main.tf)通常具有变量块,您需要从测试用例中填充这些变量。您可以使用以下任何方法为测试运行提供变量:

顺序来源
1带有 TF_VAR_ 前缀的环境变量。
2在当前目录中指定的 tfvar 文件:terraform.tfvars*.auto.tfvars
3在测试目录中指定的 tfvar 文件:tests/terraform.tfvarstests/*.auto.tfvars
4使用标志 -var 定义的命令行变量,以及标志 -var-file 指定的文件中定义的变量。
5测试文件中的 variables 块中的变量。
6run 块中的 variables 块中的变量。

OpenTofu 按上述列出的顺序评估变量,因此您可以使用它来覆盖先前设置的变量。例如:

代码块
# First, set the variable here:
variables {
name = "OpenTofu"
}

run "basic" {
assert {
condition = output.greeting == "Hello OpenTofu!"
error_message = "Incorrect greeting: ${output.greeting}"
}
}

run "override" {
# Override it for this test case only here:
variables {
name = "OpenTofu user"
}
assert {
condition = output.greeting == "Hello OpenTofu user!"
error_message = "Incorrect greeting: ${output.greeting}"
}
}

run.expect_failures 列表

在某些情况下,您可能希望测试代码的故意失败,例如确保您的验证正在工作。

您可以在 run 块内使用 expect_failures 来指定在使用给定参数运行代码时哪些变量或资源应该失败。

例如,下面的测试用例检查当 instances 变量提供负数时是否会正确失败验证:

代码块
run "main" {
command = plan

variables {
instances = -1
}

expect_failures = [
var.instances,
]
}

您还可以使用 expect_failure 子句来检查生命周期事件(如前提条件或后置条件)以及检查结果。

下面的示例检查配置错误的健康检查是否失败。这确保了即使健康检查针对错误的端点运行,它也不会始终返回。

代码块
run "test-failure" {
variables {
# This healthcheck endpoint won't exist:
health_endpoint = "/nonexistent"
}

expect_failures = [
# We expect this to fail:
check.health
]
}

run.command 设置和 run.plan_options

默认情况下,tofu test 使用 tofu apply 创建真实的基础设施。在某些情况下,例如如果真实基础设施非常昂贵或无法用于测试目的,则仅运行 tofu plan 可能很有用。您可以使用 command = plan 设置来执行计划而不是应用。以下示例测试变量是否正确传递到 docker_image 资源,而无需实际应用计划:

代码块
run "test" {
command = plan
plan_options {
refresh = false
}
variables {
image_name = "myapp"
}
assert {
condition = docker_image.build.name == "myapp"
error_message = "Missing build resource"
}
}

无论 command 设置如何,您都可以使用 plan_options 块为这两种模式指定以下其他选项:

名称描述
mode将此选项从 normal(默认值)更改为 refresh-only 以仅从远程基础设施刷新本地状态。
refresh将此选项设置为 false 以禁用检查与状态文件相关的外部更改。类似于 tofu plan -refresh=false
replace强制替换指定的资源列表,例如上述示例中的 [docker_image.build]。类似于 tofu plan -replace=docker_image.build
target将计划限制为指定的模块或资源列表。类似于 tofu plan -target=docker_image.build

providers

在某些情况下,您可能希望覆盖测试运行的提供程序设置。您可以使用run块之外的provider块为提供程序提供其他配置选项,例如测试帐户的凭据。

代码块
provider "aws" {
// Add additional settings here
}

如果提供程序支持,此功能还可以启用部分或完全离线测试。以下示例说明了使用 AWS 提供程序和 S3 存储桶资源的完全离线测试

代码块
// Configure the AWS provider to run fake credentials and without
// any validations. Not all providers support this, but when they
// do, you can run fully offline tests.
provider "aws" {
access_key = "foo"
secret_key = "bar"

skip_credentials_validation = true
skip_region_validation = true
skip_metadata_api_check = true
skip_requesting_account_id = true
}

run "test" {
// Run in plan mode to skip applying:
command = plan

// Disable the refresh to prevent reaching out to the AWS API:
plan_options {
refresh = false
}

// Test if the bucket name is correctly passed to the aws_s3_bucket
// resource:
variables {
bucket_name = "test"
}
assert {
condition = aws_s3_bucket.test.bucket == "test"
error_message = "Incorrect bucket name: ${aws_s3_bucket.test.bucket}"
}
}

提供程序别名

除了提供程序覆盖之外,您还可以为提供程序设置别名,以便在您的run块中用不同的提供程序替换它们。当您希望在同一个测试文件中拥有两个提供程序配置并在它们之间切换时,这很有用。

在下面的示例中,sockettest测试用例加载了与文件其余部分不同的 Docker 提供程序配置。

代码块
# This is the default "docker" provider for this file:
provider "docker" {
host = "tcp://0.0.0.0:2376"
}

# This will be the override:
provider "docker" {
alias = "unixsocket"
host = "unix:///var/run/docker.sock"
}

run "sockettest" {
# Replace the "docker" provider for this test case only:
providers = {
docker = docker.unixsocket
}

assert {
condition = docker_image.build.name == "myapp"
error_message = "Missing build resource"
}
}

// Add other tests with the original provider here.

mock_provider

mock_provider块允许您用模拟的块替换提供程序配置。在这种情况下,将跳过提供程序资源和数据源的创建和检索。相反,OpenTofu 将自动生成所有计算属性和块以用于测试。

模拟提供程序也支持alias字段以及mock_resourcemock_data块。在某些情况下,您可能希望使用默认值而不是自动生成的数值,方法是将它们传递到mock_resourcemock_data块的defaults字段中。

在下面的示例中,我们测试存储桶名称是否正确传递给资源,而无需实际创建它

代码块
// All resources and data sources provided by `aws.mock` provider
// will be mocked. Their values will be automatically generated.
mock_provider "aws" {
alias = "mock"
}

// The same goes for `local` provider. Also, every `local_file`
// data source will have its `content` set to `test`.
mock_provider "local" {
mock_data "local_file" {
defaults = {
content = "test"
}
}
}

// Test if the bucket name is correctly passed to the aws_s3_bucket
// resource from the local file.
run "test" {
// Use `aws.mock` provider for this test run only.
providers = {
aws = aws.mock
}

assert {
condition = aws_s3_bucket.test.bucket == "test"
error_message = "Incorrect bucket name: ${aws_s3_bucket.test.bucket}"
}
}

override_resourceoverride_data

在某些情况下,您可能希望使用某些被覆盖的资源或数据源来测试您的基础设施。您可以使用override_resourceoverride_data块来跳过使用真实提供程序创建和检索这些资源或数据源。相反,OpenTofu 将自动生成所有计算属性和块以用于测试。

这些块包含以下元素

名称类型描述
target引用必需。要覆盖的目标资源或数据源的地址。
数值对象要用于自动生成的计算属性和块的自定义值。

您可以对整个测试文件或单个run块使用override_resourceoverride_data块。如果两者都为同一个target指定,则后者优先。

在下面的示例中,我们测试存储桶名称是否正确传递给资源,而无需实际创建它

代码块
// This data source will not be called for any run
// in this `.tftest.hcl` file. Instead, `values` object
// will be used to populate `content` attribute. Other
// attributes and blocks will be automatically generated.
override_data {
target = data.local_file.bucket_name
values = {
content = "test"
}
}

// Test if the bucket name is correctly passed to the aws_s3_bucket
// resource from the local file.
run "test" {
// S3 bucket will not be created in AWS for this run,
// but it's available to use in both tests and configuration.
override_resource {
target = aws_s3_bucket.test
}

assert {
condition = aws_s3_bucket.test.bucket == "test"
error_message = "Incorrect bucket name: ${aws_s3_bucket.test.bucket}"
}
}

自动生成的数值

模拟资源和数据源需要 OpenTofu 在不调用相应提供程序的情况下自动生成计算属性。在生成这些值时,OpenTofu 无法遵循自定义提供程序逻辑,因此它使用基于值类型的简单规则

属性类型生成的值
数字0
布尔值
字符串一个随机的字母数字字符串。
列表一个空列表。
映射一个空映射。
集合一个空集合。
对象一个对象,其字段通过相同的逻辑递归填充。
元组一个空元组。

override_module

在某些情况下,您可能希望使用某些被覆盖的模块调用来测试您的基础设施。您可以使用override_module块忽略被调用模块提供的全部配置。在这种情况下,OpenTofu 将使用override_module块中指定的自定义值作为模块输出。

该块包含以下元素

名称类型描述
target引用必需。要覆盖的目标模块调用的地址。
输出对象用作模块调用输出的值。如果未指定输出,则 OpenTofu 默认将其设置为null

您可以对整个测试文件或单个run块使用override_module块。如果两者都为同一个target指定,则后者优先。

在下面的示例中,我们测试存储桶名称是否正确地从模块传递,而无需实际调用它

代码块
// All the module configuration will be ignored for this
// module call. Instead, the `outputs` object will be used
// to populate module outputs.
override_module {
target = module.bucket_meta
outputs = {
name = "test"
tags = {
Environment = "Test Env"
}
}
}

// Test if the bucket name is correctly passed to the aws_s3_bucket
// resource from the module call.
run "test" {
// S3 bucket will not be created in AWS for this run,
// but it's available to use in both tests and configuration.
override_resource {
target = aws_s3_bucket.test
}

assert {
condition = aws_s3_bucket.test.bucket == "test"
error_message = "Incorrect bucket name: ${aws_s3_bucket.test.bucket}"
}

assert {
condition = aws_s3_bucket.test.tags["Environment"] == "Test Env"
error_message = "Incorrect `Environment` tag: ${aws_s3_bucket.test.tags["Environment"]}"
}
}