terraform testによるコード検証の始め方|1.6以降のネイティブテスト機能でリグレッションを防ぐ

宮崎智広 この記事の監修:宮崎智広(Linux実務・教育歴20年以上・受講者3,100名超)
HOMELinux技術 リナックスマスター.JP(Linuxマスター.JP)Terraform > terraform testによるコード検証の始め方|1.6以降のネイティブテスト機能でリグレッションを防ぐ
「terraform applyを実行したら、先週まで問題なかったモジュールが突然壊れていた」
チームでTerraformモジュールを共有していると、誰かの変更が別のモジュールを静かに壊すリグレッションが避けられません。手動で確認するのには限界があり、本番環境への適用後に発覚するケースも珍しくないのが現実です。

Terraform 1.6から、外部ライブラリなしで使える公式のネイティブテスト機能が正式リリースされました。.tftest.hclファイルを作成してterraform testコマンドを実行するだけで、モジュールの入出力やリソース属性を自動的に検証できます。

この記事では、terraform testの基本構文と実行方法を押さえたうえで、CI/CDへの組み込みやtflintとの役割分担まで実践的に解説します。前提バージョンはTerraform 1.6以降です。

この記事のポイント

・terraform testはTerraform 1.6から正式搭載されたネイティブテスト機能
・.tftest.hclにrunブロックとassertを書くだけでモジュール検証ができる
・tflintの静的解析とterraform testの動的テストは役割が異なる二軸
・GitHub Actionsへの組み込みでリグレッションを自動防止できる


「このままじゃマズい」と感じていませんか?
参考書を開く気力もない、同年代に取り残される不安——
でも安心してください。プロのエンジニアはコマンドを暗記していません。
「現場で使える型」を効率よく使いこなしているだけです。
図解60P/登録10秒/解除も3秒 / 詳細はこちら

terraform testとは何か?1.6以前との違い

Terraformのテスト手法は、バージョンによって大きく変わってきました。

1.6以前は、モジュールのテストにTerraformの標準機能は存在しませんでした。GoのライブラリであるTerratest(HashiCorp外のOSSプロジェクト)や、Conftest(OPA/Regoポリシーで静的検査)を使うのが主流でしたが、どちらも学習コストが高く、Terraformとは別の言語・ツールチェーンが必要でした。

Terraform 1.5でアルファ版として導入されたterraform testコマンドは、1.6で安定版(GA)になりました。特徴をまとめると次のとおりです。

HCLで書ける:テストコードにGoやPythonが不要。Terraformエンジニアがそのまま書ける
実際のプランと適用を実行する:静的解析ではなく、実際にplan/applyを走らせて結果を検証する
assertブロックで条件チェック:outputの値やリソース属性を条件式で検証し、失敗時にエラーメッセージを表示する
独立したstate管理:テスト用に別stateが自動作成され、テスト終了後にdestroyされる

「テストのためだけにGoを覚えなければならない」という壁がなくなったのは、現場での採用ハードルを大きく下げています。

基本構文と動かし方

1. テストファイルを作る

テストファイルの拡張子は.tftest.hclです。モジュールのルートディレクトリにtests/ディレクトリを作り、その中に配置するのが慣例です。

# ディレクトリ構成例 modules/ s3_bucket/ main.tf variables.tf outputs.tf tests/ main.tftest.hcl # テストファイル

terraform testコマンドは、実行ディレクトリ内のtests/フォルダと*.tftest.hclファイルを自動で検索します。明示的にパスを指定する場合は-filterオプションを使います。

2. runブロックとassertブロックを書く

テストケースはrunブロックで定義します。runブロックの中にassertブロックを置いて条件を記述します。

# tests/main.tftest.hcl # テスト用変数の設定 variables { bucket_name_prefix = "test-app" environment = "stg" } run "s3_bucket_name_check" { command = plan # plan のみ実行(実リソースは作成しない) assert { condition = output.bucket_name == "test-app-stg" error_message = "バケット名が期待値と一致しません: ${output.bucket_name}" } } run "s3_versioning_enabled" { command = apply # 実際に apply して属性を確認 assert { condition = aws_s3_bucket_versioning.this.versioning_configuration[0].status == "Enabled" error_message = "バージョニングが有効になっていません" } }

command = planにするとterraform planの結果を検証し、実際のリソースは作成されません。command = applyにすると実際にリソースを作成してからassertを評価します。コストを抑えたい場合はplanで済む範囲はplanに留めるのが実務上の鉄則です。

3. terraform testを実行する

テストの実行はterraform testコマンドのみです。通常のterraform init完了後に実行できます。

$ cd modules/s3_bucket $ terraform init $ terraform test tests/main.tftest.hcl... in progress run "s3_bucket_name_check"... pass run "s3_versioning_enabled"... pass tests/main.tftest.hcl... timed out Success! 2 passed, 0 failed.

テストが失敗した場合は、error_messageに設定したメッセージとともにどのassertが失敗したかが明示されます。

tests/main.tftest.hcl... in progress run "s3_bucket_name_check"... fail Failure! 0 passed, 1 failed. --- Failure in run "s3_bucket_name_check" in tests/main.tftest.hcl Error: Test assertion failed on tests/main.tftest.hcl line 15, in run "s3_bucket_name_check": 15: condition = output.bucket_name == "test-app-stg" バケット名が期待値と一致しません: my-app-stg

モジュールテストの実践パターン

1. 入力変数のバリデーションをテストで確認する

variableブロックにvalidationを書いて入力制約を設けている場合、その制約が正しく機能するかをterraform testで確認できます。

# variables.tf(テスト対象モジュール側) variable "environment" { type = string description = "デプロイ環境(prod/stg/dev のみ許可)" validation { condition = contains(["prod", "stg", "dev"], var.environment) error_message = "environment は prod / stg / dev のいずれかを指定してください。" } }

# tests/validation.tftest.hcl run "invalid_environment_should_fail" { command = plan variables { environment = "production" # 無効な値 } expect_failures = [ var.environment, ] }

expect_failuresに失敗を期待する変数・リソースを指定することで、「この値を渡したらエラーになるはず」という否定ケースのテストも書けます。バリデーション漏れを防ぐ重要な検証パターンです。

2. outputの値をassertで確認する

モジュールが返すoutputが仕様どおりかを確認するのが、最も基本的なテストパターンです。

# tests/outputs.tftest.hcl variables { bucket_name_prefix = "myapp" environment = "dev" } run "check_bucket_arn_format" { command = plan assert { condition = startswith(output.bucket_arn, "arn:aws:s3:::") error_message = "バケットARNのフォーマットが不正です: ${output.bucket_arn}" } assert { condition = output.bucket_name == "myapp-dev" error_message = "バケット名が命名規則に従っていません: ${output.bucket_name}" } }

1つのrunブロックに複数のassertを並べることができます。論理的にひとまとまりの検証はまとめておくと、失敗時の原因特定が楽になります。

3. Terraform 1.7以降のmock_providerを活用する

Terraform 1.7からmock_providerブロックが追加されました。プロバイダーをモック化することで、実際のAWSリソースを一切作成せずにテストを実行できます。

# tests/mock.tftest.hcl mock_provider "aws" { mock_resource "aws_s3_bucket" { defaults = { arn = "arn:aws:s3:::mock-bucket" region = "ap-northeast-1" } } } run "name_check_with_mock" { command = plan assert { condition = output.bucket_name == "myapp-dev" error_message = "バケット名が期待値と異なります: ${output.bucket_name}" } }

AWSクレデンシャルが不要になるため、ローカル開発環境やCI/CDでの実行コストを大幅に削減できます。ただし、モックは実際のAPIの挙動を模倣しないため、実リソースでの検証は別途行う必要があります。command = planとの組み合わせで「ロジックのテスト」に特化するのが実用的な使い方です。

なお、mock_providerはTerraform 1.7以降の機能です。本記事のrunブロック・assertブロック・expect_failuresはTerraform 1.6以降で使用できます。バージョンはterraform versionコマンドで確認してください。
現場で通用する安全なLinuxサーバー構築の「型」を体系的に身につけたい方へ、terraform testによるモジュール品質保証からCI/CD組み込みまで、実務で使えるTerraformスキルをハンズオン形式で習得できるセミナーを開催しています。
>> Terraform実践セミナーの詳細はこちら

tflintとの使い分け — 静的解析と動的テストの二軸

terraform testと混同されがちなのが、Terraformの静的解析ツールであるtflintです。両者は役割が根本的に異なります。
ツール 実行タイミング 確認できること 実リソース作成
tflint plan前(コード解析) 命名規則違反・非推奨引数・型ミス なし
terraform test(plan) plan時 planの出力値・output値の正確さ なし
terraform test(apply) apply時 実際のリソース属性・APIの応答 あり(テスト後destroy)
tflintはコードの書き方を静的にチェックし、terraform testはコードの動作を動的に検証します。「文法的に正しいかどうか」はtflintが担い、「期待した値が返ってくるかどうか」はterraform testが担う、という役割分担です。

実務では、CI/CDパイプラインに両方を組み込むのが理想的です。tflintでコード品質を担保しながら、terraform testでモジュールの契約(インターフェース)が壊れていないことを確認します。

CI/CDパイプラインへの組み込み

1. GitHub Actionsに組み込む

# .github/workflows/terraform-test.yml name: Terraform Test on: pull_request: paths: - 'modules/**' jobs: test: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - name: Setup Terraform uses: hashicorp/setup-terraform@v3 with: terraform_version: "1.9.0" - name: Terraform Init run: terraform init working-directory: modules/s3_bucket - name: Terraform Test run: terraform test working-directory: modules/s3_bucket env: AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} AWS_DEFAULT_REGION: ap-northeast-1

pathsフィルターでmodules/**に変更があったプルリクエストのみテストを実行するのがポイントです。全ブランチの全コミットでapplyテストを走らせると、クラウドコストが膨らみます。

2. mock_providerを使うとシークレット不要になる

Terraform 1.7以降でmock_providerを使うケースでは、AWSのクレデンシャルがなくてもテストを実行できます。

- name: Terraform Test (mock) run: terraform test -filter=tests/mock.tftest.hcl working-directory: modules/s3_bucket # AWS クレデンシャル不要(mock_provider 使用時)

-filterオプションで特定のテストファイルだけを実行できます。mockテストと実リソーステストを分離しておくと、CI/CDの段階ごとに使い分けられます。

よくあるエラーと対処法

「No test files」と表示される

terraform testを実行したがテストファイルが見つからないパターンです。

$ terraform test No test files

原因は次のどれかです。

.tftest.hclの拡張子が間違っている(例:.hcl.tftestのみ)
・テストファイルがtests/ディレクトリ外に置かれている
・コマンドを実行しているディレクトリがモジュールのルートでない

拡張子は必ず.tftest.hclにし、tests/ディレクトリはモジュールのルートディレクトリに置くことを確認してください。

「Reference to undeclared output value」が出る

assertブロックで参照しているoutput名がモジュールのoutputs.tfに定義されていない場合に発生します。

Error: Reference to undeclared output value on tests/main.tftest.hcl line 15, in run "check_name": 15: condition = output.bucket_id == "..." An output value with the name "bucket_id" has not been declared.

outputs.tfを確認し、テストで参照したいoutputが定義されているかを確認します。モジュールのリファクタリング後にoutput名が変わった場合、テストコードも同時に更新する必要があります。これ自体がterraform testの有用性を示す典型例です。

applyテスト後にリソースが残る

command = applyのテストでエラーが発生した場合、destroyフェーズが走らずにリソースが残ることがあります。

# テスト用stateファイルの確認 $ ls tests/*.tfstate # テスト用リソースを手動でdestroy $ terraform destroy -state=tests/terraform.tfstate

CI/CDでterraform testが失敗したあとはクラウドコンソールを確認し、テスト用リソースが残っていないかを確認する運用ルールを設けることを推奨します。

本記事のまとめ

やりたいこと terraform testの書き方
planの出力値を検証する run "name" { command = plan; assert { ... } }
実リソースの属性を確認する run "name" { command = apply; assert { ... } }
変数バリデーションの失敗を確認する run "name" { expect_failures = [var.xxx] }
実リソースなしでテストする mock_provider "aws" { ... }(Terraform 1.7以降)
特定のテストファイルだけ実行する terraform test -filter=tests/xxx.tftest.hcl
terraform testは、Terraform 1.6から使えるネイティブのテスト機能です。.tftest.hclファイルを用意するだけで、モジュールの契約をHCLで表現できます。tflintが「コードの書き方」を静的に検査するのに対して、terraform testは「コードの動作」を動的に検証する、という二軸の品質保証体制を整えることが、チームでのTerraform運用の安定性につながります。

CI/CDに組み込んでプルリクエストのたびにテストが走るようにしておけば、「前は動いていたのに」というリグレッションを未然に防げます。まずは小さなモジュールに1本テストを書いてみるところから始めてみてください。
現場で通用する安全なLinuxサーバー構築の「型」を体系的に身につけたい方へ、terraform testを活用したモジュール品質保証・テスト設計から実運用まで、実務直結のTerraformスキルをハンズオン形式で習得できるセミナーを開催しています。
>> Terraform実践セミナーの詳細はこちら

無料メルマガで学習を続ける

Linuxの実践スキルをメールで毎週お届け。
登録は1分、解除もいつでも可。

登録無料・いつでも解除できます

暗記不要・1時間後にはサーバーが動く

3,100名以上が実践した「型」を無料で公開中

プロのエンジニアはコマンドを暗記していません。
「現場で使える型」を効率よく使いこなしているだけです。
その「型」を図解60Pにまとめた入門マニュアルを、完全無料でプレゼントしています。

登録10秒/合わなければ解除3秒 / 詳細はこちら

Linux無料マニュアル(図解60P) 名前とメールで30秒登録
宮崎 智広

この記事を書いた人

宮崎 智広(みやざき ともひろ)

株式会社イーネットマーキュリー代表。現役のLinuxサーバー管理者として20年以上の実務経験を持ち、これまでに累計3,100名以上のエンジニアを指導してきたLinux教育のプロフェッショナル。「現場で本当に使える技術」を体系的に伝えることをモットーに、実践型のLinuxセミナーの開催や無料マニュアルの配布を通じてLinux人材の育成に取り組んでいる。

趣味は、キャンプにカメラ、トラウト釣り。好きな食べ物は、ラーメンにお酒。休肝日が作れない、酒量を減らせないのが悩み。最近、ドラマ「フライトエンジェル」を観て涙腺が崩壊しました。