これは v2 AWS CDK デベロッパーガイドです。旧版の CDK v1 は 2022 年 6 月 1 日にメンテナンスを開始し、2023 年 6 月 1 日にサポートを終了しました。
翻訳は機械翻訳により提供されています。提供された翻訳内容と英語版の間で齟齬、不一致または矛盾がある場合、英語版が優先します。
を使用すると AWS CDK、インフラストラクチャを記述する他のコードと同様にテストできます。クラウドおよびローカルでテストできます。このトピックでは、クラウドでテストする方法について説明します。ローカルテストのガイダンスについては、「」を参照してくださいを使用して AWS CDK アプリケーションをローカルでテストおよび構築する AWS SAMCLI。 AWS CDK アプリケーションをテストするための標準的なアプローチでは、 AWS CDKのアサーションモジュールとTypeScript 用の Jest
AWS CDK アプリケーション用に記述できるテストには 2 つのカテゴリがあります。
-
きめ細かなアサーションは、「このリソースにはこの値を持つこのプロパティがあります」など、生成された AWS CloudFormation テンプレートの特定の側面をテストします。これらのテストはリグレッションを検出できます。テスト駆動型開発を使用して新機能を開発するときにも役立ちます。(最初にテストを記述したら、正しい実装を記述して合格させることができます) きめ細かなアサーションは、最も頻繁に使用されるテストです。
-
スナップショットテストでは、以前に保存したベースライン AWS CloudFormation テンプレートに対して合成されたテンプレートをテストします。リファクタリングされたコードが元のコードとまったく同じように動作することを確認できるため、スナップショットテストでは自由にリファクタリングできます。変更が意図的なものであった場合は、将来のテストのために新しいベースラインを受け入れることができます。ただし、CDK のアップグレードは合成されたテンプレートが変更される原因になる可能性もなるため、スナップショットのみに頼って実装が正しいことを確認できません。
注記
このトピックで例として使用される TypeScript、Python、Java アプリの完全なバージョンは、GitHub で入手できます
入門
これらのテストの記述方法を説明するために、 AWS Step Functions ステートマシンと AWS Lambda 関数を含むスタックを作成します。Lambda 関数は Amazon SNS トピックにサブスクライブされ、単にメッセージをステートマシンに転送します。
まず、CDK Toolkit を使用して空の CDK アプリケーションプロジェクトを作成し、必要なライブラリをインストールします。使用するコンストラクトはすべてメインの CDK パッケージに含まれ、CDK Toolkit で作成されたプロジェクトのデフォルトの依存関係です。ただし、テストフレームワークをインストールする必要があります。
$
mkdir state-machine && cd state-machine cdk init --language=typescript npm install --save-dev jest @types/jest
テスト用のディレクトリを作成します。
$
mkdir test
プロジェクトの package.json
を編集して NPM に Jest の実行方法を伝え、収集するファイルの種類を Jest に伝えます。必要な変更は次のとおりです。
-
scripts
セクションに新しいtest
キーの追加 -
devDependencies
セクションに Jest とそのタイプの追加 -
moduleFileExtensions
宣言を含む新しいjest
最上位キーの追加
これらの変更は次の概要に示されます。package.json
で示されている場所に新しいテキストを配置します。「...」プレースホルダーは、変更すべきではないファイルの既存の部分を示します。
{
...
"scripts": {
...
"test": "jest"
},
"devDependencies": {
...
"@types/jest": "^24.0.18",
"jest": "^24.9.0"
},
"jest": {
"moduleFileExtensions": ["js"]
}
}
スタックの例
このトピックでテストされるスタックは次のとおりです。前述のように、Lambda 関数および Step Functions ステートマシンが含まれており、1 つ以上の Amazon SNS トピックを受け入れます。Lambda 関数は Amazon SNS トピックにサブスクライブされ、ステートマシンに転送されます。
アプリをテスト可能にするために特別な操作をする必要はありません。実際、この CDK スタックは、このガイドの他のスタック例とは重要な点で異なりません。
lib/state-machine-stack.ts
に次のコードを貼り付けます。
import * as cdk from "aws-cdk-lib";
import * as sns from "aws-cdk-lib/aws-sns";
import * as sns_subscriptions from "aws-cdk-lib/aws-sns-subscriptions";
import * as lambda from "aws-cdk-lib/aws-lambda";
import * as sfn from "aws-cdk-lib/aws-stepfunctions";
import { Construct } from "constructs";
export interface StateMachineStackProps extends cdk.StackProps {
readonly topics: sns.Topic[];
}
export class StateMachineStack extends cdk.Stack {
constructor(scope: Construct, id: string, props: StateMachineStackProps) {
super(scope, id, props);
// In the future this state machine will do some work...
const stateMachine = new sfn.StateMachine(this, "StateMachine", {
definition: new sfn.Pass(this, "StartState"),
});
// This Lambda function starts the state machine.
const func = new lambda.Function(this, "LambdaFunction", {
runtime: lambda.Runtime.NODEJS_18_X,
handler: "handler",
code: lambda.Code.fromAsset("./start-state-machine"),
environment: {
STATE_MACHINE_ARN: stateMachine.stateMachineArn,
},
});
stateMachine.grantStartExecution(func);
const subscription = new sns_subscriptions.LambdaSubscription(func);
for (const topic of props.topics) {
topic.addSubscription(subscription);
}
}
}
スタックを実際にインスタンス化しないように、アプリのメインエントリポイントを変更します。誤ってデプロイすることを回避する必要があります。テストは、テスト用のアプリおよびスタックのインスタンスが作成されます。テスト駆動型開発と組み合わせると便利な戦術です。デプロイを有効にする前に、スタックがすべてのテストに合格していることを確認してください。
Eclipse bin/state-machine.ts
:
#!/usr/bin/env node
import * as cdk from "aws-cdk-lib";
const app = new cdk.App();
// Stacks are intentionally not created here -- this application isn't meant to
// be deployed.
Lambda 関数
スタック例には、ステートマシンを起動する Lambda 関数が含まれています。CDK が Lambda 関数リソースの作成の一環としてバンドルしてデプロイできるように、この関数のソースコードを指定する必要があります。
-
アプリのメインディレクトリにフォルダ
start-state-machine
を作成します。 -
このフォルダには、少なくとも 1 つのファイルを作成します。例えば、次のコードを
start-state-machines/index.js
に保存できます。exports.handler = async function (event, context) { return 'hello world'; };
ただし、実際にスタックをデプロイしないため、どんなファイルでも機能します。
テストを実行する
参考までに、 AWS CDK アプリでテストを実行するために使用するコマンドを次に示します。同じテストフレームワークを使用して任意のプロジェクトでテストを実行するために使用するコマンドと同じです。ビルドステップが必要な言語の場合、テストでコンパイルされることを確認するために含めてください。
$
tsc && npm test
きめ細かなアサーション
きめ細かなアサーションを使用してスタックをテストする最初のステップは、生成された AWS CloudFormation テンプレートに対してアサーションを記述するため、スタックを合成することです。
StateMachineStackStack
では、Amazon SNS トピックがステートマシンに転送されるために渡す必要があります。そのため、テストにはトピックを含む別のスタックを作成します。
通常、CDK アプリを記述するとき、スタックのコンストラクタで Amazon SNS トピックをサブクラス Stack
およびインスタンス化できます。テストでは、Stack
を直接インスタンス化したら、このスタックを Topic
のスコープとして渡してスタックにアタッチします。これは機能的に同等であり、それほど冗長ではありません。テストでのみ使用されるスタックを、デプロイするスタックとは「見かけが異なるもの」にするのうえで役立ちます。
import { Capture, Match, Template } from "aws-cdk-lib/assertions";
import * as cdk from "aws-cdk-lib";
import * as sns from "aws-cdk-lib/aws-sns";
import { StateMachineStack } from "../lib/state-machine-stack";
describe("StateMachineStack", () => {
test("synthesizes the way we expect", () => {
const app = new cdk.App();
// Since the StateMachineStack consumes resources from a separate stack
// (cross-stack references), we create a stack for our SNS topics to live
// in here. These topics can then be passed to the StateMachineStack later,
// creating a cross-stack reference.
const topicsStack = new cdk.Stack(app, "TopicsStack");
// Create the topic the stack we're testing will reference.
const topics = [new sns.Topic(topicsStack, "Topic1", {})];
// Create the StateMachineStack.
const stateMachineStack = new StateMachineStack(app, "StateMachineStack", {
topics: topics, // Cross-stack reference
});
// Prepare the stack for assertions.
const template = Template.fromStack(stateMachineStack);
}
Lambda 関数および Amazon SNS サブスクリプションが作成されたことをアサートできます。
// Assert it creates the function with the correct properties...
template.hasResourceProperties("AWS::Lambda::Function", {
Handler: "handler",
Runtime: "nodejs14.x",
});
// Creates the subscription...
template.resourceCountIs("AWS::SNS::Subscription", 1);
Lambda 関数テストは、関数リソースの 2 つの特定プロパティに特定の値があることをアサートします。デフォルトでは、hasResourceProperties
メソッドは合成された CloudFormation テンプレートで、指定されたリソースのプロパティに対して部分一致を実行します。このテストでは、指定されたプロパティが存在して指定された値を持つ必要がありますが、リソースにはテストされていない他のプロパティを持つこともできます。
Amazon SNS アサーションは、合成されたテンプレートにサブスクリプションが含まれているが、サブスクリプション自体については何も含まれていないことをアサートします。このアサーションは、主にリソース数についてアサートする方法を説明するために含めました。Template
クラスには、CloudFormation テンプレートの Resources
、Outputs
、Mapping
セクションに対してアサーションを記述するより具体的な方法が用意されています。
マッチャー
hasResourceProperties
のデフォルトの部分一致動作は、Match
クラスのマッチャーを使用して変更できます。
マッチャーの範囲は寛容 (Match.anyValue
) から厳格 (Match.objectEquals
) まであります。リソースプロパティのさまざまな部分にさまざまなマッチングメソッドを適用するためにネストできます。例えば、Match.objectEquals
および Match.anyValue
を併用すると、ステートマシンの IAM ロールをより完全にテストできますが、変更される可能性のあるプロパティに特定の値は必要ありません。
// Fully assert on the state machine's IAM role with matchers.
template.hasResourceProperties(
"AWS::IAM::Role",
Match.objectEquals({
AssumeRolePolicyDocument: {
Version: "2012-10-17",
Statement: [
{
Action: "sts:AssumeRole",
Effect: "Allow",
Principal: {
Service: {
"Fn::Join": [
"",
["states.", Match.anyValue(), ".amazonaws.com"],
],
},
},
},
],
},
})
);
多くの CloudFormation リソースには、文字列として表されるシリアル化された JSON オブジェクトが含まれています。Match.serializedJson()
マッチャーは、この JSON 内のプロパティを一致させるために使用できます。
例えば、Step Functions ステートマシンは JSON ベースの Amazon States Language の文字列を使用して定義されます。Match.serializedJson()
を使用し、最初の状態が唯一のステップであることを確認します。ここでもネストされたマッチャーを使用して、オブジェクトのさまざまな部分にさまざまなマッチングの種類を適用します。
// Assert on the state machine's definition with the Match.serializedJson()
// matcher.
template.hasResourceProperties("AWS::StepFunctions::StateMachine", {
DefinitionString: Match.serializedJson(
// Match.objectEquals() is used implicitly, but we use it explicitly
// here for extra clarity.
Match.objectEquals({
StartAt: "StartState",
States: {
StartState: {
Type: "Pass",
End: true,
// Make sure this state doesn't provide a next state -- we can't
// provide both Next and set End to true.
Next: Match.absent(),
},
},
})
),
});
キャプチャ
事前に正確な値を知る必要がなく、プロパティをテストして特定の形式に従うか、別のプロパティと同じ値を持つことを確認すると便利な場合がよくあります。assertions
モジュールは、Capture
クラスでこの機能を提供します。
hasResourceProperties
の値の代わりに Capture
インスタンスを指定することにより、その値は Capture
オブジェクトに保持されます。実際のキャプチャ値は、テストの対象となる asNumber()
、asString()
、asObject
など、オブジェクトの as
メソッドを使用して取得できます。マッチャーと一緒に Capture
を使用し、シリアル化された JSON プロパティなど、リソースのプロパティ内でキャプチャされる値の正確な場所を指定します。
次の例では、ステートマシンの開始状態が Start
で始まる名前を持っていることを確認するテストを実行します。この状態がマシンの状態のリスト内に存在することをテストします。
// Capture some data from the state machine's definition.
const startAtCapture = new Capture();
const statesCapture = new Capture();
template.hasResourceProperties("AWS::StepFunctions::StateMachine", {
DefinitionString: Match.serializedJson(
Match.objectLike({
StartAt: startAtCapture,
States: statesCapture,
})
),
});
// Assert that the start state starts with "Start".
expect(startAtCapture.asString()).toEqual(expect.stringMatching(/^Start/));
// Assert that the start state actually exists in the states object of the
// state machine definition.
expect(statesCapture.asObject()).toHaveProperty(startAtCapture.asString());
スナップショットテスト
スナップショットテスト では、合成された CloudFormation テンプレート全体を、以前に保存されたベースライン (多くの場合、「マスター」と呼ばれます) テンプレートと比較します。きめ細かなアサーションとは異なり、スナップショットテストはリグレッションのキャッチには役に立ちません。スナップショットテストがテンプレート全体に適用され、コード変更以外のことが合成結果に小さな (またはそれほど小さくない) 差を引き起こす可能性があるためです。これらの変更はデプロイにも影響しない可能性がありますが、スナップショットテストは失敗します。
例えば、CDK コンストラクトを更新して新しいベストプラクティスを組み込むと、合成されたリソースやその編成方法が変更される可能性があります。または、追加のメタデータをレポートするバージョンに CDK Toolkit を更新できます。コンテキスト値を変更すると、合成されたテンプレートにも影響します。
ただし、合成されたテンプレートに影響を与える可能性のある他のすべての要因を一定に保つ限り、スナップショットテストはリファクタリングに非常に役立ちます。行った変更がテンプレートを誤って変更したかどうかについて、すぐにわかります。変更が意図的な場合、新しいテンプレートをベースラインとして受け入れるのみです。
例えば、この DeadLetterQueue
コンストラクトがある場合
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(),
});
}
}
次のようにテストできます。
import { Match, Template } from "aws-cdk-lib/assertions";
import * as cdk from "aws-cdk-lib";
import { DeadLetterQueue } from "../lib/dead-letter-queue";
describe("DeadLetterQueue", () => {
test("matches the snapshot", () => {
const stack = new cdk.Stack();
new DeadLetterQueue(stack, "DeadLetterQueue");
const template = Template.fromStack(stack);
expect(template.toJSON()).toMatchSnapshot();
});
});
テストのヒント
テストはテストされるコードと同じ期間存続し、同じ頻度で読み取られて変更されることに注意してください。したがって、記述する方法を検討するために少し時間を割くことをお勧めします。
セットアップ行や一般的なアサーションをコピーして貼り付けないでください。代わりに、このロジックをフィクスチャまたはヘルパー関数にリファクタリングします。各テストが実際にテストする内容を反映した良い名前を使用してください。
1 回のテストでやりすぎないでください。テストでは、1 つの動作のみをテストすることが理想です。誤ってその動作を破った場合、1 つのテストのみが失敗してテストの名前が失敗した内容を知らせます。ただし、これは目指す理想には過ぎません。複数の動作をテストするテストをやむを得ず (または誤って) 記述する場合があります。既に説明した理由により、スナップショットテストには特にこの問題が発生しやすいため、控えめに使用してください。