Terraformのモジュールを修正するとき、変更が他の環境や呼び出し元に影響しないかを、毎回planの結果を目で追って確かめていないでしょうか。アプリケーションのコードにはテストを書くのに、インフラのコードはテストなしで変更している、という現場は少なくありません。
この記事では、Terraform 1.6で追加された terraform test を使い、モジュールのテストを書く方法を紹介します。Terraform 1.7以降のモックプロバイダーを使えば、AWSにリソースを作らずにテストできます。
terraform testでできること
- planの結果を検証する:変数を渡してplanし、リソースの属性が期待どおりかを確かめる
- 入力のチェックを検証する:不正な値を渡したときに、variableのvalidationで正しくエラーになるかを確かめる
- applyして検証する:実際にリソースを作り、出力値などを確かめてから自動で削除する
- モックで検証する:プロバイダーをモックに差し替え、AWSにアクセスせずにテストする
テストは .tftest.hcl という拡張子のファイルに書き、terraform test コマンドで実行します。
テスト対象のモジュール
例として、命名規則とタグを強制するS3バケットのモジュールを用意します。
# modules/s3_bucket/main.tf
variable "project" {
type = string
}
variable "env" {
type = string
validation {
condition = contains(["dev", "stg", "prod"], var.env)
error_message = "env は dev / stg / prod のいずれかを指定してください。"
}
}
variable "purpose" {
type = string
}
locals {
bucket_name = "${var.project}-${var.env}-${var.purpose}"
}
resource "aws_s3_bucket" "this" {
bucket = local.bucket_name
tags = {
Project = var.project
Env = var.env
}
}
resource "aws_s3_bucket_versioning" "this" {
bucket = aws_s3_bucket.this.id
versioning_configuration {
status = var.env == "prod" ? "Enabled" : "Suspended"
}
}
output "bucket_name" {
value = aws_s3_bucket.this.bucket
}
このモジュールには、守りたいルールが3つあります。
- バケット名は「プロジェクト名-環境名-用途」の形式にする
- 環境名はdev・stg・prodのいずれかに限る
- 本番環境(prod)だけバージョニングを有効にする
テスト1:planの結果を検証する
モジュールのディレクトリに tests/ を作り、テストファイルを置きます。
# modules/s3_bucket/tests/s3_bucket.tftest.hcl
variables {
project = "myapp"
purpose = "assets"
}
run "dev_bucket_name" {
command = plan
variables {
env = "dev"
}
assert {
condition = aws_s3_bucket.this.bucket == "myapp-dev-assets"
error_message = "バケット名が命名規則に従っていません。"
}
assert {
condition = aws_s3_bucket_versioning.this.versioning_configuration[0].status == "Suspended"
error_message = "dev環境でバージョニングが有効になっています。"
}
}
run "prod_versioning_enabled" {
command = plan
variables {
env = "prod"
}
assert {
condition = aws_s3_bucket_versioning.this.versioning_configuration[0].status == "Enabled"
error_message = "prod環境でバージョニングが有効になっていません。"
}
}
- variables:ファイルの先頭に書いた値は、すべてのrunブロックで共通に使われる。runブロックの中のvariablesで個別に上書きできる
- run:テストの単位。上から順に実行される
- command = plan:applyせず、planの結果だけで検証する。AWSにリソースは作られない
- assert:conditionがfalseになったら、error_messageを表示してテストを失敗にする
テスト2:不正な入力がエラーになることを検証する
validationが正しく働くかも、テストで確かめられます。
run "invalid_env_is_rejected" {
command = plan
variables {
env = "production"
}
expect_failures = [
var.env,
]
}
expect_failuresに指定した変数のvalidationでエラーになれば、テストは成功です。逆に、エラーにならなければテストは失敗します。「誤った値をきちんと弾けるか」は、レビューでは見落としやすい部分です。
テスト3:モックプロバイダーで検証する
command = planでは、AWSが決める値(ARNなど)は「apply後に決まる値」になり、検証できません。一方、applyで検証すると実際にリソースが作られ、時間も料金もかかります。
Terraform 1.7以降では、モックプロバイダーを使うと、AWSにアクセスせずにapplyまでのテストができます。
# modules/s3_bucket/tests/s3_bucket_mock.tftest.hcl
mock_provider "aws" {}
variables {
project = "myapp"
env = "stg"
purpose = "logs"
}
run "output_bucket_name" {
assert {
condition = output.bucket_name == "myapp-stg-logs"
error_message = "出力のバケット名が正しくありません。"
}
}
- mock_provider "aws" {}:AWSプロバイダーを、何もしないモックに差し替える。認証情報も不要になる
- commandの省略:省略するとapplyになるが、モックなのでAWSには何も作られない
- output:モジュールの出力値も検証できる
モックでは、ARNやIDのようにAWSが決める値は、ランダムな文字列で埋められます。特定の値が必要なら、mock_resourceブロックで既定値を指定できます。
テストを実行する
モジュールのディレクトリで、次のコマンドを実行します。
cd modules/s3_bucket
terraform init
terraform test
すべてのテストが通ると、次のような結果が表示されます(表示は例です)。
tests/s3_bucket.tftest.hcl... in progress
run "dev_bucket_name"... pass
run "prod_versioning_enabled"... pass
run "invalid_env_is_rejected"... pass
tests/s3_bucket.tftest.hcl... tearing down
tests/s3_bucket.tftest.hcl... pass
tests/s3_bucket_mock.tftest.hcl... in progress
run "output_bucket_name"... pass
tests/s3_bucket_mock.tftest.hcl... tearing down
tests/s3_bucket_mock.tftest.hcl... pass
Success! 4 passed, 0 failed.
plan用のテストファイルにはmock_providerを書いていないため、実際のAWSプロバイダーが初期化されます。実行には、AWSの認証情報と、環境変数AWS_REGIONなどでのリージョンの指定が必要です(リソースは作られません)。CIで認証情報を使いたくない場合は、すべてのテストファイルにmock_providerを書く方法もあります。
どこまでテストを書くか
すべてのリソースの全属性をテストする必要はありません。次のような「壊れると困るルール」に絞ると、少ない手間で効果が出ます。
- 命名規則:リソース名やタグの付け方
- 環境ごとの分岐:本番だけ有効にする設定(バージョニング、削除保護、Multi-AZなど)
- 入力のチェック:validationで弾くべき値
- セキュリティ上の前提:暗号化が有効か、パブリックアクセスがブロックされているか
まとめ
- terraform testで、モジュールのplan結果や出力値を自動で検証できる
- expect_failuresで、不正な入力がvalidationで弾かれることを確かめられる
- モックプロバイダーを使えば、AWSにリソースを作らずにapplyまでテストできる
- テストは「壊れると困るルール」に絞って書く
テストがあれば、モジュールを修正したときに「どこかの環境を壊していないか」をplanの目視ではなく、コマンド1つで確かめられるようになります。