🧪

terraform test: 細かい挙動

2023/12/18に公開

この記事は 3-shake Advent Calendar 2023 19 日目の記事です!

この記事に書いてあること

この記事を含め 3 回に渡って terraform test の機能を紹介します。

  1. terraform test: 基本機能
  2. terraform test: 応用機能
  3. terraform test: 細かい挙動 <- 今ここ

はじめに

前回の記事では、 terraform test の応用的な機能の紹介をしました。

この記事では、 terraform test の挙動について説明します。

terraform test: 細かい挙動

state

terraform test 実行の state は下記のような特徴があります。

  1. state はメモリ内に保持する
  2. state は基本的に .tftest.hcl ごとに作成される
  3. 2 の例外として、module ブロックを使用している run ブロックがある場合、別の state となる。
    • この場合、source が同じ run ブロックは同じ state となります。

下記のサンプルコードの場合、state の数は 3 となります。

# file_count.tftest.hcl

variables {
  bucket = "saikyo-tftest-bucket"
  files = {
    "file-one.txt": "data/files/file_one.txt"
    "file-two.txt": "data/files/file_two.txt"
  }
}

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

run "setup" {
  # Create the S3 bucket we will use later.

  module {
    source = "./testing/setup"
  }
}

run "execute" {
  assert {
    condition = length(aws_s3_object.object) == 2
    error_message = "wrong number of files"
  }
}

run "verify" {
  # Load and count the objects created in the "execute" run block.

  module {
    source = "./testing/loader"
  }

  assert {
    condition = length(data.aws_s3_objects.objects.keys) == 2
    error_message = "created the wrong number of s3 objects"
  }
}

実行順序

terraform test では、.tftest.hcl および run ブロック単位でシリアルに処理が行われます。

  1. ファイルの実行順序
    • ファイル名で昇順に一つずつ実行されます。
  2. ファイル内の実行順序
    • ファイル内の run ブロックを上から順に実行していきます。

サンプルコードを用いて実行順序の例を示します。

run "setup" {
    module {
    source = "./testing/setup"
    }
}

run "first" {
    command = plan

    assert {
    # In practice we'd do some interesting checks and tests here but the
    # assertions aren't important for this example.
    }

    # ... more assertions ...
}

run "second" {
    command = apply

    assert {
    # In practice we'd do some interesting checks and tests here but the
    # assertions aren't important for this example.
    }

    # ... more assertions ...
}

run "third" {
    command = plan

    assert {
    # In practice we'd do some interesting checks and tests here but the
    # assertions aren't important for this example.
    }

    # ... more assertions ...
}

run "fourth" {
    command = apply

    assert {
    # In practice we'd do some interesting checks and tests here but the
    # assertions aren't important for this example.
    }

    # ... more assertions ...
}

上記のサンプルコードの場合、terraform test を実行すると下記の順序に処理が行われます。

  1. run "setup" の実行 (apply)
  2. run "first" の実行 (plan)
  3. run "second" の実行 (apply)
  4. run "third" の実行 (plan)
  5. run "fourth" の実行 (apply)

削除順序

リソースの削除順序は「実行順序」とは異なります。

仕様としては下記のような形となります。

  1. メインのリソース (module ブロックが定義されないリソース) が削除される
  2. module ブロックで定義されたリソースが逆順で削除される。
run "setup" {
    module {
    source = "./testing/setup"
    }
}

run "first" {
    command = apply

    assert {
    # In practice we'd do some interesting checks and tests here but the
    # assertions aren't important for this example.
    }

    # ... more assertions ...
}

run "loader" {
    module {
    source = "./testing/loader"
    }
}

上記のサンプルコードの場合、下記の順序で削除処理が行われます。

  1. run "first" の state を destroy
  2. run "loader" の state を destroy
  3. run "setup" の state を destroy

terraform 1.7 以降の削除順序

terraform 1.6 で terraform test が登場した時点では、削除の順番は上述のように直感的ではありません。
これに対して、terraform 1.7 では削除順序に修正が入る予定です。

https://github.com/hashicorp/terraform/pull/34293

上記の issue により、 run ブロックを逆順で削除する動きに変わります。

先程のサンプルコードの場合、下記の順序で削除処理が行われます。

  1. run "loader" の state を destroy
  2. run "first" の state を destroy
  3. run "setup" の state を destroy

ファイル、 run ブロックの構成

この節では上記までの terraform test の挙動を考慮して、ファイルや run ブロックの構成をどうしたほうが良いと考えているかを記載します。

.tftest.hcl ファイル

成功 / 失敗やユースケースごとにファイルを分けています。

ユースケースの違いは、 variable の設定によって特定のリソースの作成有無が変わったりするような場合を指します。

上記のようなファイル分割をする理由として terraform test の実行単位が .tftest.hcl 単位となっているためです。

Go 言語だと .go ファイルに対してテストファイルがあるケースが一般的なようだったので、同様に .tf ファイルに対して .tftest.hcl ファイルを作成してみたのですが、terraform test の場合 state 管理が .tftest.hcl 単位となるため適していないと判断しました。

適していないと判断した具体例として、下記のような module をテストするとします。

  • リソースごとに .tf ファイルを作成するようなファイル構成を取っている。
  • resource A (resource_a.tf) と B (resource_b.tf) を作成する。
  • variable によって resource A の作成有無を制御している (resource B は毎回作る)

.tf ファイルの単位で .tftest.hcl ファイルを作成し、resource A に関するテストを resource_a.tftest.hcl に定義する構成とした場合、resource A の作成有無のケースは run ブロックを分けてテストすることになります。
variable によるリソースの作成有無を同じ .tftest.hcl でテストすると同じ state に対して作成する/しないを切り替えることになり、実際のユースケースとは異なる挙動となります。

run ブロック

run ブロックの単位についても、実際のユースケースにおいて plan / apply する単位で分けています。

terraform test の結果の success / fail の数が run ブロック単位となっていることから、リソースごとなどテスト対象ごとに run ブロックを分けたくなったのですが、下記の理由からユースケースと異なる分離をしていません。

  • run ブロックを細かく分けると、同じ state に対する変更の apply を行うため、実際のユースケースと異なる
  • run ブロックを細かく分けると、 run ブロック単位で plan / apply を実行することによりテスト時間が長くなる

以上となります。

この記事では terraform test 独自の挙動について説明しました。
挙動を理解しないと、予期せぬエラーに遭遇したり、テストの時間が長くなってしまったりするので、特に大きな module をテストする際は気をつけたほうが良いです。

ここまで 3 回に渡って terraform test の機能紹介をしてきましたが、terraform test を使う方の役に立てば幸いです。

Discussion