mcpField
T
terraform-test

terraform-test

github.com/hashicorp测试与质量9098.8k 次安装今天更新
代码仓库

关于 terraform-test

terraform-test 是一项指导使用 Terraform 内置测试框架检查配置行为和变更的技能,面向 Terraform 配置维护者,涵盖 plan、apply、断言、模拟 Provider 及 CI/CD 测试。

主要功能

  1. 01Plan 和 apply 测试场景
  2. 02用于配置检查的断言
  3. 03模拟 Provider 指导
  4. 04变量与失败测试模式
  5. 05测试筛选和清理命令
  6. 06CI/CD 参考指导

适用场景

  1. 01检查模块输出和条件
  2. 02用无效输入测试验证规则
  3. 03将快速单元测试与集成测试分开

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/hashicorp/agent-skills --skill terraform-test

安装后在 Claude Code 中输入/terraform-test确认已加载。

包含的文件

除 SKILL.md 外,安装时一起下载的参考资料和脚本。

references/
CI_CD.md
EXAMPLES.md
MOCK_PROVIDERS.md

详细介绍

Terraform 测试

Terraform 内置的测试框架可验证配置更新不会引入破坏性变更。测试会针对临时资源运行,保护现有基础设施和状态文件。

参考文件

  • references/MOCK_PROVIDERS.md — Mock provider 语法、常见默认值、何时使用 mock(仅适用于 Terraform 1.7.0 及更高版本——如果用户的版本低于 1.7,请跳过)
  • references/CI_CD.md — GitHub Actions 和 GitLab CI 流水线示例
  • references/EXAMPLES.md — 完整的测试套件示例(针对 VPC 模块的单元测试、集成测试和 mock 测试)

用户询问 mock、CI/CD 集成或需要完整示例时,请阅读相关参考文件。

核心概念

  • 测试文件(.tftest.hcl / .tftest.json):包含用于验证配置的 run 块
  • Run 块:单个测试场景,可选包含变量、provider 和断言
  • Assert 块:测试通过所必须满足的条件
  • Mock provider:模拟 provider 行为,无需真实基础设施(Terraform 1.7.0 及更高版本)
  • 测试模式:apply(默认,会创建真实资源)或 plan(仅验证逻辑)

文件结构

my-module/
├── main.tf
├── variables.tf
├── outputs.tf
└── tests/
    ├── defaults_unit_test.tftest.hcl         # plan mode — fast, no resources
    ├── validation_unit_test.tftest.hcl        # plan mode
    └── full_stack_integration_test.tftest.hcl # apply mode — creates real resources

对于 plan 模式测试,请使用 *_unit_test.tftest.hcl;对于 apply 模式测试,请使用 *_integration_test.tftest.hcl,以便在 CI 中分别筛选。

测试文件结构

# Optional: test-wide settings
test {
  parallel = true  # Enable parallel execution for all run blocks (default: false)
}

# Optional: file-level variables (highest precedence, override all other sources)
variables {
  aws_region    = "us-west-2"
  instance_type = "t2.micro"
}

# Optional: provider configuration
provider "aws" {
  region = var.aws_region
}

# Required: at least one run block
run "test_default_configuration" {
  command = plan

  assert {
    condition     = aws_instance.example.instance_type == "t2.micro"
    error_message = "Instance type should be t2.micro by default"
  }
}

Run 块

run "test_name" {
  command  = plan  # or apply (default)
  parallel = true  # optional, since v1.9.0

  # Override file-level variables
  variables {
    instance_type = "t3.large"
  }

  # Reference a specific module
  module {
    source  = "./modules/vpc"  # local or registry only (not git/http)
    version = "5.0.0"          # registry modules only
  }

  # Control state isolation
  state_key = "shared_state"  # since v1.9.0

  # Plan behavior
  plan_options {
    mode    = refresh-only  # or normal (default)
    refresh = true
    replace = [aws_instance.example]
    target  = [aws_instance.example]
  }

  # Assertions
  assert {
    condition     = aws_instance.example.id != ""
    error_message = "Instance should have a valid ID"
  }

  # Expected failures (test passes if these fail)
  expect_failures = [
    var.instance_count
  ]
}

常见测试模式

验证输出
run "test_outputs" {
  command = plan

  assert {
    condition     = output.vpc_id != null
    error_message = "VPC ID output must be defined"
  }

  assert {
    condition     = can(regex("^vpc-", output.vpc_id))
    error_message = "VPC ID should start with 'vpc-'"
  }
}
条件资源
run "test_nat_gateway_disabled" {
  command = plan

  variables {
    create_nat_gateway = false
  }

  assert {
    condition     = length(aws_nat_gateway.main) == 0
    error_message = "NAT gateway should not be created when disabled"
  }
}
资源数量
run "test_resource_count" {
  command = plan

  variables {
    instance_count = 3
  }

  assert {
    condition     = length(aws_instance.workers) == 3
    error_message = "Should create exactly 3 worker instances"
  }
}
标签
run "test_resource_tags" {
  command = plan

  variables {
    common_tags = {
      Environment = "production"
      ManagedBy   = "Terraform"
    }
  }

  assert {
    condition     = aws_instance.example.tags["Environment"] == "production"
    error_message = "Environment tag should be set correctly"
  }

  assert {
    condition     = aws_instance.example.tags["ManagedBy"] == "Terraform"
    error_message = "ManagedBy tag should be set correctly"
  }
}
数据源
run "test_data_source_lookup" {
  command = plan

  assert {
    condition     = data.aws_ami.ubuntu.id != ""
    error_message = "Should find a valid Ubuntu AMI"
  }

  assert {
    condition     = can(regex("^ami-", data.aws_ami.ubuntu.id))
    error_message = "AMI ID should be in correct format"
  }
}
验证规则
run "test_invalid_environment" {
  command = plan

  variables {
    environment = "invalid"
  }

  expect_failures = [
    var.environment
  ]
}
带依赖关系的顺序测试
run "setup_vpc" {
  command = apply

  assert {
    condition     = output.vpc_id != ""
    error_message = "VPC should be created"
  }
}

run "test_subnet_in_vpc" {
  command = plan

  variables {
    vpc_id = run.setup_vpc.vpc_id
  }

  assert {
    condition     = aws_subnet.example.vpc_id == run.setup_vpc.vpc_id
    error_message = "Subnet should be in the VPC from setup_vpc"
  }
}
Plan 选项(仅刷新、指定目标)
run "test_refresh_only" {
  command = plan

  plan_options {
    mode = refresh-only
  }

  assert {
    condition     = aws_instance.example.tags["Environment"] == "production"
    error_message = "Tags should be refreshed correctly"
  }
}

run "test_specific_resource" {
  command = plan

  plan_options {
    target = [aws_instance.example]
  }

  assert {
    condition     = aws_instance.example.instance_type == "t2.micro"
    error_message = "Targeted resource should be planned"
  }
}
并行模块
run "test_networking_module" {
  command  = plan
  parallel = true

  module {
    source = "./modules/networking"
  }

  assert {
    condition     = output.vpc_id != ""
    error_message = "VPC should be created"
  }
}

run "test_compute_module" {
  command  = plan
  parallel = true

  module {
    source = "./modules/compute"
  }

  assert {
    condition     = output.instance_id != ""
    error_message = "Instance should be created"
  }
}
共享状态键
run "create_foundation" {
  command   = apply
  state_key = "foundation"

  assert {
    condition     = aws_vpc.main.id != ""
    error_message = "Foundation VPC should be created"
  }
}

run "create_application" {
  command   = apply
  state_key = "foundation"

  variables {
    vpc_id = run.create_foundation.vpc_id
  }

  assert {
    condition     = aws_instance.app.vpc_id == run.create_foundation.vpc_id
    error_message = "Application should use foundation VPC"
  }
}
清理顺序(先清理 S3 对象,再清理存储桶)
run "create_bucket" {
  command = apply

  assert {
    condition     = aws_s3_bucket.example.id != ""
    error_message = "Bucket should be created"
  }
}

run "add_objects" {
  command = apply

  assert {
    condition     = length(aws_s3_object.files) > 0
    error_message = "Objects should be added"
  }
}

# Cleanup destroys in reverse: objects first, then bucket
多个别名 provider
provider "aws" {
  alias  = "primary"
  region = "us-west-2"
}

provider "aws" {
  alias  = "secondary"
  region = "us-east-1"
}

run "test_with_specific_provider" {
  command = plan

  providers = {
    aws = provider.aws.secondary
  }

  assert {
    condition     = aws_instance.example.availability_zone == "us-east-1a"
    error_message = "Instance should be in us-east-1 region"
  }
}
复杂条件
assert {
  condition = alltrue([
    for subnet in aws_subnet.private :
    can(regex("^10\\.0\\.", subnet.cidr_block))
  ])
  error_message = "All private subnets should use 10.0.0.0/8 CIDR range"
}

清理

测试完成后,资源会按 run 块的逆序销毁。这对依赖关系很重要(例如,先清理 S3 对象,再清理存储桶)。使用 terraform test -no-cleanup 可跳过清理以便调试。

运行测试

terraform test                                        # all tests
terraform test tests/defaults.tftest.hcl             # specific file
terraform test -filter=test_vpc_configuration        # by run block name
terraform test -test-directory=integration-tests     # custom directory
terraform test -verbose                              # detailed output
terraform test -no-cleanup                           # skip resource cleanup

最佳实践

  1. 命名:plan 模式使用 *_unit_test.tftest.hcl,apply 模式使用 *_integration_test.tftest.hcl
  2. 测试命名:使用能说明测试场景的描述性 run 块名称
  3. 默认使用 plan:除非需要测试真实资源行为,否则使用 command = plan
  4. 使用 mock处理外部依赖——速度更快且无需凭据(参见 references/MOCK_PROVIDERS.md)
  5. 错误消息:提供足够具体的信息,以便诊断失败,而无需重新运行测试
  6. 负向测试:使用 expect_failures 验证验证规则会拒绝错误输入
  7. 变量覆盖:测试不同的变量组合,以验证所有代码路径——测试变量的优先级最高,会覆盖所有其他来源
  8. 模块源:测试文件仅支持本地路径和 registry 模块,不支持 git 或 HTTP URL
  9. 并行执行:对于使用不同状态文件的独立测试,请使用 parallel = true
  10. 清理:集成测试会自动按 run 块的逆序销毁资源;使用 -no-cleanup 进行调试
  11. CI/CD:每个 PR 都运行单元测试,合并时运行集成测试(参见 references/CI_CD.md)

故障排查

问题解决方案
断言失败使用 -verbose 查看实际值与预期值
缺少凭据单元测试使用 mock provider
不支持的模块源将 git/HTTP 源转换为本地模块
测试相互干扰使用 state_key 或单独的模块进行隔离
测试速度慢使用 command = plan 和 mock;单独运行集成测试

参考资料

评论

支持 Markdown