TT Lab
はじめる
学ぶ 学習パス コース

Terraform実戦

インフラのコードにもテストを付ける

TT Labで続きを見る

一言でいうと

.tftest.hclは、安価なテスト(プランだけ)と高価なテスト(実際に作る)を、1つの文法で書かせてくれます。両者を混ぜて使う感覚がすべてです。

なぜ必要なのか: 直せないモジュールになる過程

モジュールを最初に作るときは、呼び出す場所が1つです。直して回してみれば終わりです。半年後には、12か所が呼び出しています。もう、1行を直すたびに、12か所がどうなるかを確認する必要がありますが、その時間がないので、人々は別の道を選びます。直さないのです。新しい要求が来ると、モジュールを直す代わりに、隣に似たモジュールをもう1つ作ります。そうしてモジュールが増え、どれが最新なのか、誰にもわからなくなります。

テストは、この悪循環を断ち切る仕組みです。「この値を変えると、どこが壊れるか」を、コマンド1行に変えてくれるからです。ところが、インフラコードのテストには、悩みがもう1つあります。実際に作ってみないとわからないものと、値を見ればわかるものが、混在している点です。

どう動くのか

テストファイルは、.tftest.hcl拡張子を使い、現在のディレクトリかtestsディレクトリから探されます。ファイルの中には、runブロックが順番に入ります。

variables {          # 이 파일의 모든 run 이 쓰는 기본 입력
  env = "dev"
}

run "name_is_prefixed" {
  command = plan     # 아무것도 만들지 않는다

  assert {
    condition     = output.name == "app-dev"
    error_message = "이름 규칙이 깨졌습니다"
  }
}

command = planとcommand = applyの違いが核心です。プランだけを立てるテストは、速くて何も作らないので、名前のルール・条件分岐・デフォルト値・計算された値のような、値を見ればよいものを、すべてカバーできます。実際に作るテストは、遅くてリソースを使いますが、「本当にそのファイルが、その内容でできるのか」は、この方法でしかわかりません。終わると、ツールが自分で作ったものを削除します。

runは、つなげられます。後ろのrunで、前のrunの出力を名前で参照します。「まずネットワークを作り、そのIDでアプリをプランする」のようなステップが、こう表現されます。

expect_failuresは、逆から言う仕組みです。このrunは、失敗して初めて合格だと宣言します。validationが本当に止めるのか、誤った入力が本当に拒否されるのかを、テストするときに使います。止める仕組みを作っておいて確認しないと、ある日条件を誤って直しても、誰も気づきません。

mock_providerは、プロバイダーのまねをします。実際には作らず、もっともらしい値を返すので、遅かったりお金がかかったりするプロバイダーをテストするときに、価値が大きいです。その代わり、まねなので、「本当にそのようにできるのか」は確認できません。本物のapplyのテストとは、役割が違うことを、はっきりさせておく必要があります。

失敗したときのメッセージも、見ておく価値があります。アサーションがあった行と、そのとき値が実際に何だったかを、一緒に示してくれます。そのため、error_messageに値を繰り返し書く必要がありません。

現場での姿

最もよくある事故は、CIスクリプトが終了コードを握りつぶすことです。テストは失敗したのに、スクリプトが0で終われば、パイプラインは緑のまま通過します。これは、テストがないより悪いです。誰も確認していないのに、確認したと信じ込ませるからです。そのため、スクリプトを作ったら、わざと失敗するテストを1つ挟んで、赤信号が点灯するかを一度見る必要があります。

2つ目は、高価なテストだけを使うことです。すべてapplyで書くと、テスト1周が長くなり、長くなると、人々は回さなくなります。大部分をplanに移し、実際の作成は、どうしても必要な数個だけを残すのが、実務のバランスです。

3つ目は、テストが残すゴミです。ツールは、作ったものを削除しますが、テストが途中で落ちると、残ることがあります。共用アカウントでテストを回すなら、名前に、区別するための目印を入れておくほうが安全です。

そして、忘れやすい事実が1つあります。テストファイルもコードです。レビューを受け、モジュールと同じリポジトリにあり、モジュールを直すときに一緒に直されます。テストがモジュールの外のどこかに別に置かれていると、半年後には、誰も回さない状態になります。

4つ目は、何をテストしないかを決めないことです。プロバイダーがすでに保証していること(値を入れれば、その値が入る)を繰り返しテストしても、テストの数が増えるだけで、得るものがありません。インフラコードのテストが守るべきものは、私たちが作ったルールです。名前のルール、環境ごとの分岐、許可しない入力、層の間の接続。プロバイダーの動作ではなく、私たちの決定がテストの対象です。

最後に、テストはモジュールを直せるようにするための仕組みだという点を覚えておけば、優先順位が決まります。最初にテストを付ける場所は、最も多く呼び出されるモジュール、その中でも、最近事故が起きた箇所です。完璧なカバレッジを目標にすると、始められませんが、事故が起きた場所1か所にテストを付けることは、今日できます。

次のラボですること

/root/tfa-testに、変数2つと出力3つを持つモジュールを作り、プランだけを立てるテストと、実際に作るテストを、それぞれ付けます。runをつなげて、前の結果を次の入力として使い、expect_failuresでvalidationが止めることを確認し、わざと失敗するテストを回して、メッセージと終了コードを見て、mock_providerで値だけを確認したあと、CIが呼び出すスクリプトにまとめて、両方向(成功・失敗)がともに正しく終わることを確認します。