AWS CDK テスト自動化（CDK assertions）をしてみる（Part3）

本記事では[Part2](https://qiita.com/kennyQiita/items/7d684a0e9792c8e808a7)までで作成したスタックに対してAWS-CDKの`assertions`を用いてテスト用のスクリプトを作成していきます。

## 環境情報
* 作業環境はAWS Cloud9を利用
* cdk versionは2.63.2

## はじめに
今回はAWS公式ドキュメントで記されている[Fine-Grained Assertions](https://docs.aws.amazon.com/cdk/v2/guide/testing.html#testing_fine_grained)のテスト(Assertion Test)を実施していきます。
このテストスクリプトの中では主に`hasResourceProperties`関数を用います。このヘルパー関数はCDKでリソースを作成する際に、"リソースが作成されていること”や"リソースに設定されているプロパティ値"の確認をする際に利用されます。

## Assertion Tests
### DynamoDB tableが作成されたことを確認するテスト
まずは不要な`test/cdk-workshop.test.ts`ファイルを削除してしまいましょう。その後`test/hitcounter.test.ts`ファイルを作成し、以下のコードを記述します。
```typescript:hitcounter.test.ts
import { Template, Capture } from 'aws-cdk-lib/assertions';
import * as cdk from 'aws-cdk-lib';
import * as lambda from 'aws-cdk-lib/aws-lambda';
import { HitCounter } from '../lib/hitcounter';

test('DynamoDB Table Created', () => {
  const stack = new cdk.Stack();
  
  // WHEN
  new HitCounter(stack, 'MyTestConstruct', {
    downstream: new lambda.Function(stack, 'TestFunction', {
      runtime: lambda.Runtime.NODEJS_14_X,
      handler: 'hello.handler',
      code: lambda.Code.fromAsset('lambda')
    })
  });
  
  // THEN
  const template = Template.fromStack(stack);
  template.resourceCountIs("AWS::DynamoDB::Table", 1);
});
```
このテストでは単純にスタックの中にDynamoDBのテーブルが含まれることをテストしています。
テストを実行します。
```
$ npm run test
```
以下のような結果が表示されます。
```
$ npm run test

> cdk-workshop@0.1.0 test
> jest

 PASS  test/hitcounter.test.ts (12.242 s)
  ✓ DynamoDB Table Created (177 ms)

Test Suites: 1 passed, 1 total
Tests:       1 passed, 1 total
Snapshots:   0 total
Time:        12.439 s
Ran all test suites.
```
### Lambda関数が作成されたことのテスト
次にLambda関数用のテストケースを追記していきます。今回はLambda関数が作成されたことに加えて、2つの環境変数（`DOWNSTREAM_FUNCTION_NAME`と`HITS_TABLE_NAME`）の値についてもテストしていきます。この2つの環境変数は[Part2](https://qiita.com/kennyQiita/items/7d684a0e9792c8e808a7)でLambda関数を作る際に指定しましたね。
この時点ではこの2つの環境変数にどの値がセットされるかわからない（この値はデプロイ時に決定されているものでしたね）ので、一旦ダミーの値をセットします。そのため初回のテストは失敗しますが、その失敗のログに実際の環境変数の値が出力されているので、その値を後ほど使います。
新規のテストケースを以下のように追記します。
```typescript:hitcounter.test.ts
test('Lambda Has Environment Variables', () => {
  const stack = new cdk.Stack();
  
  // WHEN
  new HitCounter(stack, 'MyTestConstruct', {
    downstream: new lambda.Function(stack, 'TestFunctioin', {
      runtime: lambda.Runtime.NODEJS_14_X,
      handler: 'hello.handler',
      code: lambda.Code.fromAsset('lambda')
    })
  });
  
  // THEN
  const template = Template.fromStack(stack);
  const envCapture = new Capture();
  template.hasResourceProperties("AWS::Lambda::Function", {
    Environment: envCapture,
  });
  
  expect(envCapture.asObject()).toEqual(
    {
      Variables: {
        DOWNSTREAM_FUNCTION_NAME: {
          Ref: "TestFunctionXXXX",
        },
        HITS_TABLE_NAME: {
          Ref: "MyTestConstructHitsXXXX",
        }
      },
    }
  );
});
```
ファイルを保存したのち、テストを実行します。
```
$ npm run test
```
以下のようにテスト結果にエラー1件が含まれています。エラーの中に指定すべき環境変数の値が記載されているので、こちらを使っていきます。
```
$ npm run test

> cdk-workshop@0.1.0 test
> jest

 FAIL  test/hitcounter.test.ts (13.577 s)
  ✓ DynamoDB Table Created (198 ms)
  ✕ Lambda Has Environment Variables (99 ms)

  ● Lambda Has Environment Variables

    expect(received).toEqual(expected) // deep equality

    - Expected  - 2
    + Received  + 2

      Object {
        "Variables": Object {
          "DOWNSTREAM_FUNCTION_NAME": Object {
    -       "Ref": "TestFunctionXXXX",
    +       "Ref": "TestFunctioin03257049",
          },
          "HITS_TABLE_NAME": Object {
    -       "Ref": "MyTestConstructHitsXXXX",
    +       "Ref": "MyTestConstructHits24A357F0",
          },
        },
      }

      41 |   });
      42 |   
    > 43 |   expect(envCapture.asObject()).toEqual(
         |                                 ^
      44 |     {
      45 |       Variables: {
      46 |         DOWNSTREAM_FUNCTION_NAME: {

      at Object.<anonymous> (test/hitcounter.test.ts:43:33)

Test Suites: 1 failed, 1 total
Tests:       1 failed, 1 passed, 2 total
Snapshots:   0 total
Time:        13.766 s
Ran all test suites.
```
では上記エラーで表示された値を使ってテストケースの内容を書き換えます。
```typescript:hitcounter.test.ts
...
expect(envCapture.asObject()).toEqual(
    {
      Variables: {
        DOWNSTREAM_FUNCTION_NAME: {
          Ref: "ここにログで確認した値をセット",
        },
        HITS_TABLE_NAME: {
          Ref: "ここにログで確認した値をセット",
        }
      },
    }
  );
...
```
再びテストを実行すると、今度は成功しました！
```
 $ npm run test

> cdk-workshop@0.1.0 test
> jest

 PASS  test/hitcounter.test.ts (12.738 s)
  ✓ DynamoDB Table Created (150 ms)
  ✓ Lambda Has Environment Variables (94 ms)

Test Suites: 1 passed, 1 total
Tests:       2 passed, 2 total
Snapshots:   0 total
Time:        12.942 s, estimated 14 s
Ran all test suites.
```
### TDD (Test Driven Development）
CDKでスタックを作成する際にTDDの手法を用いることも可能です。簡単な例として、既存のDynamoDBテーブルを暗号化するという新規の要件があったとします。
TDDアプローチとしては、まずこの要件をテストスクリプトに反映させます。
```typescript:hitcounter.test.ts
test('DynamoDB Table Created With Encryption', () => {
  const stack = new cdk.Stack();
  
  // WHEN
  new HitCounter(stack, 'MyTestConstruct', {
    downstream: new lambda.Function(stack, 'TestFunction', {
      runtime: lambda.Runtime.NODEJS_14_X,
      handler: 'hello.handler',
      code: lambda.Code.fromAsset('lambda')
    })
  });
  
  // THEN
  const template = Template.fromStack(stack);
  template.hasResourceProperties("AWS::DynamoDB::Table", {
    SSESpecification: {
      SSEEnabled: true
    }
  });
});
```
テストを実行してみるとエラーとなると思います。暗号化する設定をコンストラクタに記載していないので、想定通りですね。
ではコンストラクタをアップデートして、DynamoDBテーブルに対して暗号化されるように記載していきましょう。
```typescript:hitcounter.ts
...
 constructor(scope: Construct, id: string, props: HitCounterProps) {
    super(scope, id);
    
    const table = new dynamodb.Table(this, 'Hits', {
      partitionKey: { name: 'path', type: dynamodb.AttributeType.STRING },
      encryption: dynamodb.TableEncryption.AWS_MANAGED
    });
...
```
再度テストを実施してみると、今回は成功するかと思います。
このようにCDKを用いることでTDDのアプローチも可能ですね！

## まとめ
いかがでしたでしょうか。クラウドインフラに対して、構築物をテストする際にコンソールでの目視確認を実施しているようなプロジェクトも多いかと思います。CDKを用いれば、AWSサービス構築時に想定通りの内容が反映されているかテストもできるので、DevOpsのサイクルもより迅速に回せそうな気がしますね！

## 参考サイト
https://cdkworkshop.com/20-typescript/70-advanced-topics/100-construct-testing.html