🧪

Terraform のテスト機能を試してみた

2023/11/09に公開

こんにちは、クラウドエースの佐久間です。2023 年 10 月 5 日にリリースされた GA 版 Terraform v1.6.0 において、terraform test コマンドを用いたテスト機能が正式に実装されました。この記事では、公式ページの説明をまとめるとともに、Google Cloud のリソースを用いてそのテスト機能を簡単に試していきます。なお、Terraform の基本的な使い方には触れませんのでご了承ください。

機能概要

この機能は、テストファイルに書かれたテストコードに基づき、モジュール構成の更新にミスがないかテストするための機能です。terraform test コマンドを実行することでテストが走ります。

これまで Terraform コードのテストには、TerratestKitchen-Terraform といった外部ツールを利用する必要がありました。これらのツールは HCL 以外の言語でテストコードを作成する必要があるため、テストを行うにも学習コストがかかります。

今回のテスト機能が実装されたことで、テストコードを HCL で作成できるようになり、テストを Terraform 内で完結できるようになりました。テストを実行すると、テスト用のリソースが一時的に作成され、それらのリソースに対してテストが実行されます。これにより、コード編集による重大な変更の防止や、設定値の誤りの検証ができます。

使い方

基本的な使い方は、テストコードが書かれたテストファイルを作成し、terraform test コマンドを実行するだけです。デフォルト設定時の terraform test コマンドの大まかな動作は以下のようになります。

  1. テストファイルを検出する
  2. テストファイルに記載されているテスト用リソースを作成する
  3. 作成されたリソースに対して、テストファイルで指定した結果になっているかチェックする (アサーション)
  4. テスト用リソースを削除する

テストファイル

Terraform は .tftest.hcl または .tftest.json のファイル拡張子に基づいてテストファイルを検出します。テストファイルは以下のブロックを含みます。

  • 1 つ以上の run ブロック (詳細は次のセクションで説明します)
  • variables ブロック (省略可)|構成内の入力変数の値を指定
  • provider ブロック (省略可)|構成内に必要なプロバイダーを設定または上書き

run ブロック

run ブロックは以下のフィールドまたはブロックを含みます。

フィールドまたはブロック名 説明 デフォルト値
command apply または planを指定。plan を指定するとリソースの作成を行わず、plan の結果のみでテストを行う。 apply
plan_options.mode command = plan で実行したときのモード選択。normal または refresh-only を指定。詳細はこちら normal
plan_options.refresh command = plan で実行したときのコマンド オプション。true または false を指定。詳細はこちら true
plan_options.replace command = plan で実行したときのコマンド オプション。詳細はこちら
plan_options.target command = plan で実行したときのコマンド オプション。詳細はこちら
variables 変数を記載。メインの構成やrun ブロックの外で定義した変数を上書きできる。詳細はこちら
module run ブロックを実行するモジュールの変更。詳細はこちら
providers メインの構成に必要なプロバイダーを設定または上書きする。詳細はこちら
assert condition (テストをパスするための条件) や error_message (テスト失敗時のエラー メッセージ) を記載。
expect_failures あらかじめブロック内のテストが失敗すると分かっている場合に、そのテストをパスさせる。詳細はこちら

使用例

今回は以下のような main.tf を作成し、 プロジェクトに Google Cloud Storage バケットを作成することを前提とします。

main.tf
variable "project" {
  type = string
}

resource "google_storage_bucket" "main" {
  name          = "ca-terraform-test-bucket"
  location      = "asia-northeast1"
  force_destroy = true
  project       = var.project
}

また、作成したテストファイルは以下の通りです。今回は main.tftest.hcl というファイル名で作成しました。

main.tftest.hcl
run "check_bucket_name" {
  command = plan

  assert {
        condition     = google_storage_bucket.main.name == "ca-terraform-test-bucket"
        error_message = "Bucket name is not ca-terraform-test-bucket"
  }
}

run "check_bucket_location" {
  command = plan

  assert {
        condition     = google_storage_bucket.main.location == "ASIA-NORTHEAST1"
        error_message = "Bucket location is not ASIA-NORTHEAST1"
  }
}

2 つの run ブロックを用意しました。1 つ目のブロックではバケット名をチェックし、2 つ目のブロックではバケットのロケーションをチェックしています。

それでは早速テストを実行してみます。

$ terraform test
main.tftest.hcl... in progress
  run "check_bucket_name"... pass
  run "check_bucket_location"... pass
main.tftest.hcl... tearing down
main.tftest.hcl... pass

Success! 2 passed, 0 failed.

いずれのブロックもテストをパスしました。次に、main.tf を以下のように変更してみます。

main.tf
resource "google_storage_bucket" "main" {
  name          = "ca-terraform-test-bucket"
  location      = "us-central1"
  force_destroy = true
}

ロケーションを asia-northeast1 から us-central1 に変更しました。この状態で再度テストを実行してみます。

$ terraform test
main.tftest.hcl... in progress
  run "check_bucket_name"... pass
  run "check_bucket_location"... fail
╷
│ Error: Test assertion failed
│ 
│   on main.tftest.hcl line 10, in run "check_bucket_location":
│   10:     condition     = google_storage_bucket.main["ca-terraform-test-bucket"].location == "ASIA-NORTHEAST1"
│     ├────────────────
│     │ google_storage_bucket.main["ca-terraform-test-bucket"].location is "US-CENTRAL1"
│ 
│ Bucket location is not ASIA-NORTHEAST1
╵
main.tftest.hcl... tearing down
main.tftest.hcl... fail

Failure! 1 passed, 1 failed.

main.tf とテストファイルで指定したロケーションが異なるため、期待通りエラー メッセージとともにテストは失敗しました。1 つ目のブロックのテストはパスしていますが、2 つ目で失敗しているので、全体としてもテストは失敗したことになります。

まとめ

新しく実装された Terraform のテスト機能について簡単に説明しました。これまで他のツールを用いて行われてきた Terraform コードのテストが Terraform だけで完結するので、かなり便利だと感じました。例として実践したテストはかなり簡単なものですが、より複雑なテストのやり方も今後調べていきたいと思います。

Discussion