タダです.
来年の技術書典8に出ることが決まりました! テーマは AWS CDK の本です.AWS CDK の本を出すからこそむちゃくちゃ詳しくなれるようにより一層使い込みと勉強をしていきます.今回の記事では,テストフレームワーク「Jest」の使い方を学びます.
1日目でAWS CDK本出す為に頑張るぞぃ💪 #技術書典
— SADAYOSHI TADA🎉 (@tada_infra) 2019年12月15日
自分がたくさん学ぶきっかけをもらったイベントだから同じ感情持ってもらえる本作りたいな😌 https://t.co/c274QS9U1P
AWS CDK におけるテストの現状
ドキュメントを見ると,テストでサポートされているのは TypeScript のみになります.「Jest」は JavaScript テストフレームワークで AWS CDK の TypeScript のテストも可能です.
テストの種類について
AWS CDK のテストには次の3種類の方法があります.今回は 「Snapshot tests」のやり方を学びます.「Snapshot tests」とは 作りたい CloudFormation テンプレート全体を準備して AWS CDK で作成したテンプレートが一致することを確認するためのテストです.AWS CDK の開発においては Integration test として利用されているそうです.
- Snapshot tests(Golden master tests)
- Fine-grained assertions tests
- Validation tests
「Jest」の導入
それでは「Jest」の実践を公式ブログのチュートリアルに沿って使ってみます.公式ブログによると,AWS CDK の TypeScript の Construct ライブラリには @aws-cdk/assert
というアサーションライブラリがあります.
なお,環境は以下になります.
$ sw_vers ProductName: Mac OS X ProductVersion: 10.14.6 BuildVersion: 18G2022 $ cdk --version 1.19.0 (build 5597bbe)
チュートリアル用プロジェクトの準備
チュートリアル用のプロジェクトを準備します.チュートリアルでは,SQS のデッドレターキューにアイテムがある場合にアラーム通知するよう CloudWatch を設定するケースのインフラとテストのコードを作ります.
$ cdk init --language typescript lib Applying project template lib for typescript Executing npm install... npm WARN deprecated left-pad@1.3.0: use String.prototype.padStart() npm notice created a lockfile as package-lock.json. You should commit this file. npm WARN jest-handson@0.1.0 No repository field. npm WARN jest-handson@0.1.0 No license field. # Welcome to your CDK TypeScript Construct Library project! You should explore the contents of this project. It demonstrates a CDK Construct Library that includes a construct (`JestHandson`) which contains an Amazon SQS queue that is subscribed to an Amazon SNS topic. The construct defines an interface (`JestHandsonProps`) to configure the visibility timeout of the queue. ## Useful commands * `npm run build` compile typescript to js * `npm run watch` watch for changes and compile * `npm run test` perform the jest unit tests $ npm install @aws-cdk/aws-sqs @aws-cdk/aws-cloudwatch npm WARN jest-handson@0.1.0 No repository field. npm WARN jest-handson@0.1.0 No license field. + @aws-cdk/aws-sqs@1.19.0 + @aws-cdk/aws-cloudwatch@1.19.0 updated 2 packages and audited 1755462 packages in 11.635s 14 packages are looking for funding run `npm fund` for details found 0 vulnerabilities
Snapshot tests の実践
インフラソースコードの準備
デッドレターキューを構成するソースコード
import cloudwatch = require('@aws-cdk/aws-cloudwatch'); import sqs = require('@aws-cdk/aws-sqs'); import { Construct. Duration } from '@aws-cdk/core'; export class DeadLetterQueue extends sqs.Queue { public readonly messagesInQueueAlarm: cloudwatch.IAlarm; constructor(scope: Construct, id: string) { super(scope,id); // Add the alarm this.messagesInQueueAlarm = new cloudwatch.Alarm(this, 'Alarm', { alarmDescription: 'There are messages in the Dead Letter Queue', evaluationPeriods: 1, threshold: 1, metric: this.metricApproximateNumberOfMessagesVisible(), }); } }
テストコードの準備
「Jest」と AWS CDK アサーションライブラリをインストールします.
$ npm install --save-dev jest @types/jest @aws-cdk/assert npm WARN deprecated left-pad@1.3.0: use String.prototype.padStart() npm WARN jest-handson@0.1.0 No repository field. npm WARN jest-handson@0.1.0 No license field. + jest@24.9.0 + @types/jest@24.0.24 + @aws-cdk/assert@1.19.0 updated 3 packages and audited 1755462 packages in 35.82s 1 package is looking for funding run `npm fund` for details found 0 vulnerabilities
package.json
に「Jest」に関する定義を記載します.
{ "name": "jest-handson", "version": "0.1.0", "main": "lib/index.js", "types": "lib/index.d.ts", "scripts": { "build": "tsc", "watch": "tsc -w", "test": "jest" <= 定義が必要 }, "devDependencies": { "@aws-cdk/assert": "^1.19.0", "@types/jest": "^24.0.24",<= 定義が必要 "@types/node": "10.17.5", "jest": "^24.9.0",<= 定義が必要 "ts-jest": "^24.1.0", "typescript": "~3.7.2" }, "jest": { "moduleFileExtensions": ["js"]<= 定義が必要 }, "peerDependencies": { "@aws-cdk/core": "^1.19.0" }, "dependencies": { "@aws-cdk/aws-cloudwatch": "^1.19.0", "@aws-cdk/aws-sns": "^1.19.0", "@aws-cdk/aws-sns-subscriptions": "^1.19.0", "@aws-cdk/aws-sqs": "^1.19.0", "@aws-cdk/core": "^1.19.0" } }
テストコードのコンパイルと実行
ライブラリとpackage.json
の定義が完了したら,テスト用のコードを書いていきます.キューの保持期間が2週間であることを確認するコードを書くのですが,今回は「Snapshot tests」を書きます.
import { SynthUtils } from '@aws-cdk/assert'; import { Stack } from '@aws-cdk/core'; import dlq = require('../lib/dead-letter-queue'); test('dlq creates an alarm', () => { const stack = new Stack(); new dlq.DeadLetterQueue(stack, 'DLQ'); expect(SynthUtils.toCloudFormation(stack)).toMatchSnapshot(); })
テストコードを書き終えたらテストコードをコンパイルして,ユニットテストを実行します.
$ npm run build > jest-handson@0.1.0 build /XXX/XXX/awscdk-handson/jest-handson > tsc lib/dead-letter-queue.ts:3:19 - error TS1005: ',' expected. 3 import { Construct. Duration } from '@aws-cdk/core'; ~ Found 1 error. npm ERR! code ELIFECYCLE npm ERR! errno 2 npm ERR! jest-handson@0.1.0 build: `tsc` npm ERR! Exit status 2 npm ERR! npm ERR! Failed at the jest-handson@0.1.0 build script. npm ERR! This is probably not a problem with npm. There is likely additional logging output above. npm ERR! A complete log of this run can be found in: npm ERR! /XXX/XXX/.npm/_logs/2019-12-21T07_25_41_525Z-debug.log $ npm run build > jest-handson@0.1.0 build /XXX/XXX/awscdk-handson/jest-handson > tsc $ npm test > jest-handson@0.1.0 test /Users/tada/awscdk-handson/jest-handson > jest PASS test/dead-letter-queue.test.ts ✓ dlq creates an alarm (132ms) Test Suites: 1 passed, 1 total Tests: 1 passed, 1 total Snapshots: 1 passed, 1 total Time: 5.396s, estimated 12s Ran all test suites.
ユニットテストが終わると,test/__snapshots__
というディレクトリができており, テストファイル名.test.ts.snap
というCloudFormation テンプレートコピーが生成されます.
// Jest Snapshot v1, https://goo.gl/fbAQLP exports[`dlq creates an alarm 1`] = ` Object { "Resources": Object { "DLQ581697C4": Object { "Type": "AWS::SQS::Queue", }, "DLQAlarm008FBE3A": Object { "Properties": Object { "AlarmDescription": "There are messages in the Dead Letter Queue", "ComparisonOperator": "GreaterThanOrEqualToThreshold", "Dimensions": Array [ Object { "Name": "QueueName", "Value": Object { "Fn::GetAtt": Array [ "DLQ581697C4", "QueueName", ], }, }, ], "EvaluationPeriods": 1, "MetricName": "ApproximateNumberOfMessagesVisible", "Namespace": "AWS/SQS", "Period": 300, "Statistic": "Maximum", "Threshold": 1, }, "Type": "AWS::CloudWatch::Alarm", }, }, } `;
インフラソースコードを変更した場合の「Snapshot tests」の実践
インフラのソースコードを変更した時はどのように再度テストを行うのでしょうか.その方法もみておきます.
CloudWatch Alarm の 間隔をデフォルト5分から1分に変更
インフラのソースコードの CloudWatch Alarm の 間隔をデフォルト5分から1分に変更して再度「Snapshot tests」を行います.
import cloudwatch = require('@aws-cdk/aws-cloudwatch'); import sqs = require('@aws-cdk/aws-sqs'); import { Construct, Duration } from '@aws-cdk/core'; export class DeadLetterQueue extends sqs.Queue { public readonly messagesInQueueAlarm: cloudwatch.IAlarm; constructor(scope: Construct, id: string) { super(scope,id); // Add the alarm this.messagesInQueueAlarm = new cloudwatch.Alarm(this, 'Alarm', { alarmDescription: 'There are messages in the Dead Letter Queue', evaluationPeriods: 1, threshold: 1, metric: this.metricApproximateNumberOfMessagesVisible(), period: Duration.minutes(1), <= 追加 }); } }
再度「Snapshot tests」の実践
テストを実行したところ Period 属性が 300 -> 60 になっていることを通知しています.
$ npm run build && npm test > jest-handson@0.1.0 build /Users/tada/awscdk-handson/jest-handson > tsc > jest-handson@0.1.0 test /Users/tada/awscdk-handson/jest-handson > jest FAIL test/dead-letter-queue.test.ts ✕ dlq creates an alarm (69ms) ● dlq creates an alarm expect(received).toMatchSnapshot() Snapshot name: `dlq creates an alarm 1` - Snapshot + Received @@ -19,11 +19,11 @@ }, ], "EvaluationPeriods": 1, "MetricName": "ApproximateNumberOfMessagesVisible", "Namespace": "AWS/SQS", - "Period": 300, + "Period": 60, "Statistic": "Maximum", "Threshold": 1, }, "Type": "AWS::CloudWatch::Alarm", }, 7 | const stack = new Stack(); 8 | new dlq.DeadLetterQueue(stack, 'DLQ'); > 9 | expect(SynthUtils.toCloudFormation(stack)).toMatchSnapshot(); | ^ 10 | }); at Object.<anonymous> (test/dead-letter-queue.test.ts:9:48) › 1 snapshot failed. Snapshot Summary › 1 snapshot failed from 1 test suite. Inspect your code changes or run `npm test -- -u` to update them. Test Suites: 1 failed, 1 total Tests: 1 failed, 1 total Snapshots: 1 failed, 1 total Time: 3.074s Ran all test suites. npm ERR! Test failed. See above for more details.
この変更が意図的であれば,npm test -- -u
で「Snapshot tests」をコミットします.1 snapshot updated.
とあるため,「Snapshot tests」が更新され,新しいアラームの期間がセットされます.
$ npm test -- -u > jest-handson@0.1.0 test /XXX/XXX/awscdk-handson/jest-handson > jest "-u" PASS test/dead-letter-queue.test.ts ✓ dlq creates an alarm (72ms) › 1 snapshot updated. Snapshot Summary › 1 snapshot updated from 1 test suite. Test Suites: 1 passed, 1 total Tests: 1 passed, 1 total Snapshots: 1 updated, 1 total Time: 3.059s Ran all test suites.
テスト後の CloudFormation テンプレートコピーがどう変化しているかも確認します.こちらも Period 属性が 300 -> 60 に反映されているので意図した変更が反映されています.
// Jest Snapshot v1, https://goo.gl/fbAQLP exports[`dlq creates an alarm 1`] = ` Object { "Resources": Object { "DLQ581697C4": Object { "Type": "AWS::SQS::Queue", }, "DLQAlarm008FBE3A": Object { "Properties": Object { "AlarmDescription": "There are messages in the Dead Letter Queue", "ComparisonOperator": "GreaterThanOrEqualToThreshold", "Dimensions": Array [ Object { "Name": "QueueName", "Value": Object { "Fn::GetAtt": Array [ "DLQ581697C4", "QueueName", ], }, }, ], "EvaluationPeriods": 1, "MetricName": "ApproximateNumberOfMessagesVisible", "Namespace": "AWS/SQS", "Period": 60, <= 変更 "Statistic": "Maximum", "Threshold": 1, }, "Type": "AWS::CloudWatch::Alarm", }, }, } `;
まとめ
AWS CDK がサポートしている「Jest」の概要と「Snapshot tests」 の実践を行いました.他の2つのテスト手法も使い分けの説明ができるようにブログにしていきます.
関連記事
「AWS CDK」の他の記事もぜひ!