🏂 AWS CDK 101🌺 - 我々の構築のためのTDDアプローチによるJest試験


🔰 AWS CDKに初心者は、このシリーズで私の前の記事を1つずつ見てください.
場合は、前の記事を逃した場合は、以下のリンクでそれを見つけるか.
🔁 前のポスト🔗 Dev Post
🔁 前投稿を転載🔗
この記事では、我々が以前のCDK記事で作成した我々の構成をテストするのに役立つJestテストケースを書くことを紹介しましょう
簡単にするために、我々は我々が導入した構成のテストケースを作成するだけです.それで、これはあなたを制限しません.したがって、あなたはプロジェクトを通してこれを広げるために自由です.

ジェスセットアップ🔅


新しいフォルダを作成することから始めますtest 我々の現在のプロジェクトの根源で.
新しいファイルの追加event-counter.test.ts以下に示すように、JEST設定ファイルを作成しなければならないことを確認してくださいjest.config.js プロジェクトのルートで.
module.exports = {
  testEnvironment: 'node',
  roots: ['<rootDir>/test'],
  testMatch: ['**/*.test.ts'],
  transform: {
    '^.+\\.tsx?$': 'ts-jest'
  }
};

また、パッケージのJSONに以下のスクリプトを使用して、テストケースをビルドして実行することができますnpm run test .
"test": "npm run build && jest --coverage",

CDKプロジェクトにおける利点のテスト🎈

  • テストの1つの利点は、我々のスタックを開発することができ、テストスイートを使用して、dev環境に展開する前にスタックを有効にすることです.
  • また、私たちが非常に確信している些細なことは、オーバーライドされることができます、そして、したがって、いつも前にテストケースを書くことは、配備の前に常に最も単純なものが検証されることを確認します.
  • 我々は、テストケース自体の機能テストのほとんどをカバーすることができますし、我々は我々が展開する前に我々の期待に非常に近いことを確認できるようになります.
    私は既にテストケースを構成しているのでjest.only 我々は一度に1つを試してください.

    コード重複を削除するヘルパー関数🎻


    また、いくつかの再利用可能ヘルパー関数をハンドラー関数を作成し、次のように作成した構成を初期化しました.

    イベントカウンタラムダ関数を初期化するには♦️


    const initHandler = (stack: cdk.Stack): lambda.Function => {
      return new lambda.Function(stack, 'TestFunction', {
          runtime: lambda.Runtime.NODEJS_14_X,
          handler: 'event-counter.counter',
          code: lambda.Code.fromAsset('lambda')
        });
    }
    

    テスト用の構成を初期化するには💎


    const initEventCounter = (stack: cdk.Stack, handler: lambda.Function): EventCounter => {
      return new EventCounter(stack, 'MyTestConstruct', {
        backend: handler ,
         tableName: 'Event Counters',
          partitionKeyName: 'Counter Name'
      });
    }
    
    また、私たちの構築を含むインポートされたモジュール、アサーションライブラリ、標準的なCDKライブラリが必要です.
    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 { EventCounter }  from '../constructs/event-counter'
    

    1 .ラムダ環境変数💍


    以下に示すように最初のテストケースから始めましょう.リソースがプロビジョニングされるかどうかチェックする簡単な方法です.
    test.only('1. Lambda Has Environment Variables', () => {
      const stack = new cdk.Stack();
      //WHEN
      let handler = initHandler(stack);
    
      let eventCounter = initEventCounter(stack, handler);
    
      //THEN
      const template = Template.fromStack(stack);
    
      console.log(template);
      console.log(JSON.stringify(template));
    
    
    });
    
    このテストケースでは、スタックを初期化し、新しいハンドラ関数でイベントカウンタを初期化しました.

    CDKのための試験戦略📝


    アサーションについて議論する前に、コンソール出力を記録して、ここで我々のテスト戦略となるものを示しました.
    console.log
        Template {
          template: {
            Resources: {
              TestFunctionServiceRole6ABD93C7: [Object],
              TestFunction22AD90FC: [Object],
              MyTestConstructEventCountersF7DBB021: [Object],
              MyTestConstructEventCounterHandlerServiceRole7ABC2462: [Object],
              MyTestConstructEventCounterHandlerServiceRoleDefaultPolicy46018C23: [Object],
              MyTestConstructEventCounterHandler383414CF: [Object],
              MyTestConstructEventCounterHandlerLogRetention2D503F76: [Object],
              LogRetentionaae0aa3c5b4d4f87b02d85b201efdd8aServiceRole9741ECFB: [Object],
              LogRetentionaae0aa3c5b4d4f87b02d85b201efdd8aServiceRoleDefaultPolicyADDA7DEB: [Object],
              LogRetentionaae0aa3c5b4d4f87b02d85b201efdd8aFD4BFC8A: [Object]
            },
            Parameters: { BootstrapVersion: [Object] },
            Rules: { CheckBootstrapVersion: [Object] }
          }
        }
    
          at Object.<anonymous> (test/event-counter.test.ts:33:11)
    
    これが実際のCDK合成テンプレートであり、この出力参照オブジェクトをターゲットとしたテストケースを作ることができます.
    私がここで意味することを理解しなかったあなたのそれらは、詳細テンプレートテンプレートを見ることができますconsole.log(JSON.stringify(template));
    これは、我々が作成しようとしているリソースのスナップショットを持っています.今、我々はこれの部分を捕えて、テストケースを解決するために我々の主張を実行することができました.
     template.hasResourceProperties("AWS::Lambda::Function", {
        Environment: envCapture,
      });
    
      expect(envCapture.asObject()).toEqual(
        {
          Variables: {
            BACKEND_FUNCTION_NAME: {
              Ref: "TestFunction22AD90FC",
            },
            EVENT_COUNTER_TABLE_NAME: {
              Ref: "MyTestConstructEventCountersF7DBB021",
            },
          },
        }
      );
    
    上記のブロックでは、型のプロパティを見つけますAWS::Lambda::Function オブジェクト環境の値を取得する.
    そして、我々はこれを我々の断言に適用します、ここで我々が検証する最初の時、我々はそれが失敗すると予想します、そして、一旦我々がリソース提供されると確信するならば、我々は.toEqual を返します.Ref: "********" , これはテストケースと現在の環境ブートストラップテンプレートに基づいて生成されます.

    2 .作成したDynamodbテーブル⚽


    もう一つのテストケースを書きましょう.resourceCountIs メソッドは、同様のリソースのカウントをプロビジョニングするために使用されます.
    test.only('2. DynamoDB Table Created', () => {
      const stack = new cdk.Stack();
      // WHEN
    
      let handler = initHandler(stack);
    
      let eventCounter = initEventCounter(stack, handler);
    
      // THEN
    
      const template = Template.fromStack(stack);
      template.resourceCountIs("AWS::DynamoDB::Table", 1);
    });
    
     PASS  test/event-counter.test.ts (15.434 s)
      ✓ 1. Lambda Has Environment Variables (427 ms)
      ✓ 2. DynamoDB Table Created (92 ms)
    

    暗号化で作成したDynamodbテーブル🏄


    さあ、ちょっとやりましょうTDD 最初のテストケースを作成し、それを修正することによって、ベースの開発.
    test('3. DynamoDB Table Created With Encryption', () => {
      const stack = new cdk.Stack();
      // WHEN
      let handler = initHandler(stack);
    
      let eventCounter = initEventCounter(stack, handler);
      // THEN
      const template = Template.fromStack(stack);
      template.hasResourceProperties('AWS::DynamoDB::Table', {
        SSESpecification: {
          SSEEnabled: true
        }
      });
    });
    
    ここでは、私たちは、主張において言及されたいくつかの仕様で、proviodedされるダイナモテーブルを期待していました.これは暗号化機能をテーブルのためにオンにすることを期待しています.
     FAIL  test/event-counter.test.ts (15.953 s)
      ✓ 1. Lambda Has Environment Variables (143 ms)
      ✓ 2. DynamoDB Table Created (89 ms)
      ✕ 3. DynamoDB Table Created With Encryption (90 ms)
    
    
      ● 3. DynamoDB Table Created With Encryption
    
        The template has 1 resource with the type AWS::DynamoDB::Table, but none match as expected.
        The closest result is:
          {
            "Type": "AWS::DynamoDB::Table",
            "Properties": {
              "KeySchema": [
                {
                  "AttributeName": "Counter Name",
                  "KeyType": "HASH"
                }
              ],
              "AttributeDefinitions": [
                {
                  "AttributeName": "Counter Name",
                  "AttributeType": "S"
                }
              ],
              "ProvisionedThroughput": {
                "ReadCapacityUnits": 5,
                "WriteCapacityUnits": 5
              }
            },
            "UpdateReplacePolicy": "Delete",
            "DeletionPolicy": "Delete"
          }
        with the following mismatches:
          Missing key at /Properties/SSESpecification (using objectLike matcher)
    
          74 |   // THEN
          75 |   const template = Template.fromStack(stack);
        > 76 |   template.hasResourceProperties('AWS::DynamoDB::Table', {
             |            ^
          77 |     SSESpecification: {
          78 |       SSEEnabled: true
          79 |     }
    
          at Template.hasResourceProperties (node_modules/aws-cdk-lib/assertions/lib/template.ts:62:36)
          at Object.<anonymous> (test/event-counter.test.ts:76:12)
    
    予期していたように、テストケースが失敗したので、すぐに必要な機能を追加することができましたconstruct\event-counter.ts 次のようにします.
    
     const Counters = new dynamodb.Table(this, tableName, {
            partitionKey: { name: partitionKeyName, type: dynamodb.AttributeType.STRING },
           encryption: dynamodb.TableEncryption.AWS_MANAGED, //added for TDD
    
        });
    
    
    はい、我々は今その権利を持っている.
     PASS  test/event-counter.test.ts (5.077 s)
      ✓ 1. Lambda Has Environment Variables (138 ms)
      ✓ 2. DynamoDB Table Created (93 ms)
      ✓ 3. DynamoDB Table Created With Encryption (84 ms)
    
    デフォルトでは、DynamoDBコンストラクターは、5つの読み込み単位と5つのライトユニットのような既定の仕様でリソースを提供します.しかし、我々はそれが環境と我々が予想する読み書きパターンの特定のピーク負荷期待に基づいて少し微調整する必要があります.
    つ以上のテストケースを次のように追加します.

    読み取り容量を設定することができます🎼


    最初の2つの障害原因は、制限される容量が欲しい場所です.
    test('4. read capacity can be configured', () => {
      const stack = new cdk.Stack();
    
      expect(() => {
        let handler = 
        new EventCounter(stack, 'MyTestConstruct', {
        backend: handler ,
         tableName: 'Event Counters',
          partitionKeyName: 'Counter Name',
          readCapacity: 30,
          writeCapacity: 15
      });
    
      }).toThrowError(/readCapacity must be greater than 5 and less than 20/);
    });
    

    書き込み能力は5〜10の範囲でなければなりません🎺


    test('5. write capacity should be in the range of 5 to 10', () => {
      const stack = new cdk.Stack();
    
      expect(() => {
        let handler = initHandler(stack);
        new EventCounter(stack, 'MyTestConstruct', {
        backend: handler ,
         tableName: 'Event Counters',
          partitionKeyName: 'Counter Name',
          readCapacity: 5,
          writeCapacity: 15
      });
    
      }).toThrowError(/writeCapacity must be greater than 5 and less than 10/);
    });
    
    
    これを有効にするには、constructs\event-counter.ts 必要な例外をスローします.
    
    export interface EventCounterProps {
      /** the function for which we want to count Event messages**/
      backend: lambda.IFunction,
      tableName: string,
      partitionKeyName: string,
      readCapacity?: number,
      writeCapacity?: number
    
    }
        constructor(scope: Construct, id: string, props: EventCounterProps) {
    
        if (props.readCapacity !== undefined && (props.readCapacity < 5 || props.readCapacity > 20)) {
          throw new Error('readCapacity must be greater than 5 and less than 20');
        }
    
        if (props.writeCapacity !== undefined && (props.writeCapacity < 5 || props.writeCapacity > 10)) {
          throw new Error('writeCapacity must be greater than 5 and less than 10');
        }
    
        super(scope, id);
    
        .......
    
    const Counters = new dynamodb.Table(this, tableName, {
            partitionKey: { name: partitionKeyName, type: dynamodb.AttributeType.STRING },
            encryption: dynamodb.TableEncryption.AWS_MANAGED,
            readCapacity: props.readCapacity ?? 5,
            writeCapacity: props.writeCapacity ?? 5
        });
    
    あなたは、我々の予想された範囲がアサーション声明の読取りと右の能力のために満たされないとき、我々が例外を投げるために論理を加えた今、我々が特定することができましたtoThrowError .
     PASS  test/event-counter.test.ts (14.978 s)
      ✓ 1. Lambda Has Environment Variables (145 ms)
      ✓ 2. DynamoDB Table Created (87 ms)
      ✓ 3. DynamoDB Table Created With Encryption (94 ms)
      ✓ 4. read capacity can be configured (34 ms)
      ✓ 5. write capacity should be in the range of 5 to 10 (4 ms)
    

    サンプル読み込みと書き込み単位で作成したDynamoDBテーブル🎯


    同様に、読み書き能力ユニットに対しても、正のテストケースをシミュレートするために、例外なくテストケースを追加できます.
    
    test.only('6. DynamoDB Table Created With sample read and write units as well', () => {
      const stack = new cdk.Stack();
      // WHEN
       let handler = initHandler(stack);
      new EventCounter(stack, 'MyTestConstruct', {
        backend: handler ,
         tableName: 'Event Counters',
          partitionKeyName: 'Counter Name',
          readCapacity: 10,
          writeCapacity: 5
      });
      // THEN
      const template = Template.fromStack(stack);
      template.hasResourceProperties('AWS::DynamoDB::Table', 
     {
              "KeySchema": [
                {
                  "AttributeName": "Counter Name",
                  "KeyType": "HASH"
                }
              ],
              "AttributeDefinitions": [
                {
                  "AttributeName": "Counter Name",
                  "AttributeType": "S"
                }
              ],
              "ProvisionedThroughput": {
                "ReadCapacityUnits": 10,
                "WriteCapacityUnits": 5
              },
              "SSESpecification": {
                "SSEEnabled": true
              }
            }
      );
    });
    
     PASS  test/event-counter.test.ts (14.978 s)
      ✓ 1. Lambda Has Environment Variables (145 ms)
      ✓ 2. DynamoDB Table Created (87 ms)
      ✓ 3. DynamoDB Table Created With Encryption (94 ms)
      ✓ 4. read capacity can be configured (34 ms)
      ✓ 5. write capacity should be in the range of 5 to 10 (4 ms)
      ✓ 6. DynamoDB Table Created With sample read and write units as well(60 ms)
    
    第6のテストケースも、我々のテストケースが以下の通り100パーセントのテスト報道に達するのを助けました.
    
    ------------------|---------|----------|---------|---------|-------------------
    File              | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s
    ------------------|---------|----------|---------|---------|-------------------
    All files         |   90.47 |       50 |     100 |   90.47 |
     event-counter.ts |   90.47 |       50 |     100 |   90.47 | 28,32
    ------------------|---------|----------|---------|---------|-------------------
    
    ------------------|---------|----------|---------|---------|-------------------
    File              | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s
    ------------------|---------|----------|---------|---------|-------------------
    All files         |     100 |      100 |     100 |     100 |
     event-counter.ts |     100 |      100 |     100 |     100 |
    ------------------|---------|----------|---------|---------|-------------------
    
    それでも、我々は、構造の重要な側面に関する機能的期待に基づいて、より多くのテストケースを持っています.

    ラムダは、30日で指定されるログ保持を持ちます🎸


    
    test.only('7. Lambda Has logRetention specified with 30 days', () => {
      const stack = new cdk.Stack();
      // WHEN
       let handler = initHandler(stack);
      new EventCounter(stack, 'MyTestConstruct', {
        backend: handler ,
         tableName: 'Event Counters',
          partitionKeyName: 'Counter Name',
          readCapacity: 10,
          writeCapacity: 5
      });
      // THEN
      const template = Template.fromStack(stack);
      const cp1 = new Capture();
      const cp2 = new Capture();
      template.hasResourceProperties("Custom::LogRetention", {
        LogGroupName: cp1,
        RetentionInDays: cp2
    
      });
    
      expect(cp1.asObject()).toEqual(
        {
              "Fn::Join": [
                "",
                [
                  "/aws/lambda/",
                  {
                    "Ref": "MyTestConstructEventCounterHandler383414CF"
                  }
                ]
              ]
            }
      );
      expect(cp2.asNumber()).toEqual(
        30
      );
    });
    
    上記のテストケースは、我々がどのように数値とオブジェクトレベルのアサーションを行うことができるかを示し、また、コードのセグメントをアサーションを実行する変数に取り込む方法を教えてください.

    8 .ラムダは、DynamoDB上で読み書き可能なアクセスを持ち、バックエンド関数を呼び出すことができます🎀


    以下のテストケースは、RAM機能が作成されたDynamoDBの書き込み操作を実行し、バックエンドハンドラ関数を呼び出すことができることを確認するIAMポリシーを検証するために使用されます.
    ここで、アサーションは、キャプチャされた変数asArray
    
    test.only('8. Lambda Has read write access on dynamodb and can invoke backend function ', () => {
      const stack = new cdk.Stack();
      // WHEN
       let handler = new lambda.Function(stack, 'TestFunction', {
          runtime: lambda.Runtime.NODEJS_14_X,
          handler: 'event-counter.counter',
          code: lambda.Code.fromAsset('lambda')
        });
      let counter = new EventCounter(stack, 'MyTestConstruct', {
        backend: handler ,
         tableName: 'Event Counters',
          partitionKeyName: 'Counter Name',
          readCapacity: 10,
          writeCapacity: 5
      });
    
    
      // THEN
      const template = Template.fromStack(stack);
      const cp = new Capture();
    
      template.hasResourceProperties("AWS::IAM::Policy", {
        PolicyDocument: {Statement: cp}
      });
    
      expect(cp.asArray()).toEqual(
          [
          {
            "Action": [
              "dynamodb:BatchGetItem",
              "dynamodb:GetRecords",
              "dynamodb:GetShardIterator",
              "dynamodb:Query",
              "dynamodb:GetItem",
              "dynamodb:Scan",
              "dynamodb:ConditionCheckItem",
              "dynamodb:BatchWriteItem",
              "dynamodb:PutItem",
              "dynamodb:UpdateItem",
              "dynamodb:DeleteItem"
            ],
            "Effect": "Allow",
            "Resource": [
              {
                "Fn::GetAtt": [
                  "MyTestConstructEventCountersF7DBB021",
                  "Arn"
                ]
              },
              {
                "Ref": "AWS::NoValue"
              }
            ]
          },
          {
            "Action": "lambda:InvokeFunction",
            "Effect": "Allow",
            "Resource": {
              "Fn::GetAtt": [
                "TestFunction22AD90FC",
                "Arn"
              ]
            }
          }
        ]
    
      );
    
    });
    
      PASS  test/event-counter.test.ts (14.928 s)
      ✓ 1. Lambda Has Environment Variables (139 ms)
      ✓ 2. DynamoDB Table Created (91 ms)
      ✓ 3. DynamoDB Table Created With Encryption (76 ms)
      ✓ 4. read capacity can be configured (29 ms)
      ✓ 5. write capacity should be in the range of 5 to 10 (9 ms)
      ✓ 6. DynamoDB Table Created With sample read and write units as well (61 ms)
      ✓ 7. Lambda Has logRetention specified with 30 days (50 ms)
      ✓ 8. Lambda Has read write access on dynamodb and can invoke backend function  (31 ms)
    
    ------------------|---------|----------|---------|---------|-------------------
    File              | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s
    ------------------|---------|----------|---------|---------|-------------------
    All files         |     100 |      100 |     100 |     100 |
     event-counter.ts |     100 |      100 |     100 |     100 |
    ------------------|---------|----------|---------|---------|-------------------
    Test Suites: 1 passed, 1 total
    Tests:       8 passed, 8 total
    Snapshots:   0 total
    Time:        15.068 s
    Ran all test suites.
    

    CDKにおけるテストの結論🏂


    JESTを使用して、構築モジュールとプロジェクトモジュールの広範なテストケースをどのように書くかを示しました.
    ファイルを使用することもできます./cdk.out/CommonEventStack.template.json 参照として、実際の環境に展開することなくプロジェクトレベルでより類似した統合されたテストケースを作成するために、スタックの完全なテンプレートを提供します.
    我々は、このAPIゲートウェイとラムダスタックに多くの接続を追加し、それが今後の記事でより使用可能にするので、以下を検討し、私のニュースレターを購読してください.
    ⏭ 次の記事はServerlessにあります
    🎉 サポートに感謝!🙏
    あなたが好きならば☕ Buy Me a Coffee , 私の努力を高めるために.

    🔁 オリジナルポスト🔗 Dev Post
    🔁 で転載🔗