実践!AWS CDK #3 テスト

題字・息子たち
2021.05.20

この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。

はじめに

今回は AWS CDK ならではの機能、テスト について学んでいきましょう。前回作成した VPC に関するテストコードも書いていきます。

前回の記事はこちら。

AWS CDK におけるテスト

AWS CDK ではテストは以下 3 つのカテゴリに分類されます。

Snapshot tests

テスト実行時点で作成される CFn テンプレート(スナップショット)と事前に保存されている ベースラインテンプレート を比較してテストします。以下のコードでこの比較処理を実施します。(初回は比較なしで passed)

expect(SynthUtils.toCloudFormation(stack)).toMatchSnapshot();

比較した結果、マッチしていれば passed, していなければ failed となります。failed となってもそれが意図した変更であれば次のコマンドでベースラインテンプレートを更新できます。

$ npm test -- -u

「あなたの変更でここが変わってるけど、意図してる?」ということが確認できるテストです。L2 の Construct を使用する際は便利かもしれませんね。(意図しないリソース作成防止)
当然ながら設定値を変更した場合や新たなリソースを追加した場合は必ず failed となってしまいます(passed にするためには failed > 更新 > passed という手順が必要)。私はこの挙動が少し気になり、かつ L2 の Construct は扱わないため本シリーズにおいてこのテストは採用しません。

Fine-grained assertions

生成された CFn テンプレートの一部をテストできます。新たに追加したリソースのプロパティのチェックやリファクタリング等で既存のリソースに影響がないことを確認できます。
以下のような項目をチェックできます。

  • どんなリソースがあるか
  • リソースのプロパティ
  • リソースの数
  • 出力(Outputs)の内容

本シリーズではこのテストを採用します。

Validation tests

Construct が無効なデータを受け取った時にエラー(例外)を発生させることを確認するテストです。以下のサンプルは retentionDays に無効な値を入れた場合に意図した例外が発生することを確認しています。

test('configurable retention period cannot exceed 14 days', () => {
  const stack = new Stack();

  expect(() => {
    new dlq.DeadLetterQueue(stack, 'DLQ', {
      retentionDays: 15
    });
  }).toThrowError(/retentionDays may not exceed 14 days/);
});

一般的な 単体テスト に近い印象を受けました。
複雑な Construct を使用する予定はないので本シリーズにおいてこのテストは採用しません。

なお私の環境だと、この toThrowError() メソッドが利用できません(?)

インストール

テスト用のフレームワークをインストールするには以下のコマンドを実行します。

$ npm install --save-dev jest @types/jest @aws-cdk/assert

続いてルートディレクトリ直下にある package.json を以下のように編集します。

package.json

{
  ...
 "scripts": {
    ...
    "test": "jest"
  },
  "devDependencies": {
    ...
    "@types/jest": "^24.0.18",
    "jest": "^24.9.0",
  },
  "jest": {
    "moduleFileExtensions": ["js"]
  }
}

私の環境では scriptsdevDependencies は既に項目が存在したので、jest 部分だけ追記しました。

これで準備完了です。

一度以下のコマンドでテストを実行してみましょう。

$ npm run build && npm test

実行できて failed になれば OK です。

~ 省略 ~

Test Suites: 1 failed, 1 total
Tests:       1 failed, 1 total
Snapshots:   0 total
Time:        4.148 s
Ran all test suites.

実装

それではテストコードを書いていきましょう。
今回編集するファイルは test ディレクトリにある devio.test.ts となります。現在の状態がこちら。

test/devio.test.ts

import { expect as expectCDK, matchTemplate, MatchStyle } from '@aws-cdk/assert';
import * as cdk from '@aws-cdk/core';
import * as Devio from '../lib/devio-stack';

test('Empty Stack', () => {
    const app = new cdk.App();
    // WHEN
    const stack = new Devio.DevioStack(app, 'MyTestStack');
    // THEN
    expectCDK(stack).to(matchTemplate({
      "Resources": {}
    }, MatchStyle.EXACT))
});

このテストはプロジェクト作成時に自動生成されるもので、リソースが空({})であることを確認するテストです。しかし前回 VPC リソースを作成しており、リソースが空ではなくなったため先程のテストに失敗したというわけです。

今回書く(書いた)テストコードはこちら。

test/devio.test.ts

import { expect, countResources, haveResource } from '@aws-cdk/assert';
import * as cdk from '@aws-cdk/core';
import * as Devio from '../lib/devio-stack';

test('Vpc', () => {
  const app = new cdk.App();
  const stack = new Devio.DevioStack(app, 'DevioStack');

  expect(stack).to(countResources('AWS::EC2::VPC', 1));
  expect(stack).to(haveResource('AWS::EC2::VPC', {
    CidrBlock: '10.0.0.0/16',
    Tags: [{ 'Key': 'Name', 'Value': 'devio-stg-vpc' }]
  }));
});

まず以下のコードで VPC のリソースが 1 つであることを確認しています。1 の部分を他の値に変更するとテストは失敗します。

expect(stack).to(countResources('AWS::EC2::VPC', 1));

次に作成した VPC リソースのプロパティをチェックしています。

expect(stack).to(haveResource('AWS::EC2::VPC', {
  CidrBlock: '10.0.0.0/16',
  Tags: [{ 'Key': 'Name', 'Value': 'devio-stg-vpc' }]
}));

haveResource メソッドや countResources メソッドの第一引数にはリソースタイプを string 型で指定します。ここに指定すべき文字列は CFn でサポートされている リソースタイプ識別子 となります。 これらが string 型となっていることから予想できますが、プロパティ部分に関しては IDE の補完が効きません。またここで指定すべきプロパティのキー値は CFn のドキュメントに書かれているもの となります。具体的に言うと CfnVPCPropscidrBlock は無効であり、AWS::EC2::VPCCidrBlock(先頭大文字!)が有効な値となるのでご注意ください。テスト時は生成された CFn のテンプレートと比較を行っているので、CFn の仕様が正となります。

今回実装してはいませんが、あるプロパティが 指定されていないこと を確認することもできます。(ABSENT

expect(stack).to(haveResource('AWS::EC2::VPC', {
  CidrBlock: ABSENT
}));

CidrBlock が指定されていなければ passed。今回は指定しているので failed となります。

本シリーズでは今後これくらいのレベルでテストコードを書いていこうと思います。

確認

それでは改めてテストを実行してみましょう。

$ npm run build && npm test

~ 省略 ~

Test Suites: 1 passed, 1 total
Tests:       1 passed, 1 total
Snapshots:   0 total
Time:        1.69 s, estimated 3 s
Ran all test suites.

無事にテストが通りました。この結果により VPC が意図通りの数とプロパティで生成されることが保証されます。これで安心してリファクタリングができますね!

GitHub

今回のソースコードは コチラ です。

おわりに

以上が AWS CDK におけるテストの概要となります。
テストに正解はありませんが、自身が安心して実装できるよう常にメンテナンスしていきたいですね。興味がある方は以下のリンクから、自分に合うテスト方法を見つけてみてください。

リンク