この記事は、NTTテクノクロス Advent Calendar 2024 シリーズ1の1日目の記事になります。
皆さんこんにちは。NTTテクノクロスの堀江です。普段はAWSやAzure上でのシステム設計、構築や実装、調査検証系の案件を幅広く担当しています。
はじめに
AWS CDKの単体テスト(きめ細かなアサーションテスト)の実装を高速化・効率化するためのライブラリ、aws-cdk-utul - AWS CDK Unit Test Utility Libraryをこの度リリースしました。
なにやら抽象的な名前のライブラリですが、簡潔に述べると、このライブラリを使用することで、IDEの型ヒントのサポートをフルに受けながらAWS CDKの単体テストを実装できるようになります。
本記事では、ライブラリの開発背景と実際の使い方をお話していきます。
開発の背景
AWS CDKのコンストラクトやスタックに対してきめ細かなアサーションテスト(以降、これを単体テストとして扱います)を行うための標準的なモジュールとして、aws-cdk-lib/assertionsモジュールが存在します。このモジュールを使用することで、自分たちが実装したコンストラクトやスタックから期待通りのCloudFormationテンプレートが合成(synthesize)されるかをテストできます。
import { aws_ec2 as ec2, Stack } from "aws-cdk-lib";
import { Match, Template } from "aws-cdk-lib/assertions";
const stack = new Stack();
new ec2.Vpc(stack, "VPC", {
ipAddresses: ec2.IpAddresses.cidr("10.0.0.0/16"),
subnetConfiguration: [
{ subnetType: ec2.SubnetType.PUBLIC, cidrMask: 24, name: "public" },
],
maxAzs: 2,
});
const template = Template.fromStack(stack);
test("サブネットマスクが/24のサブネットが2つ作成されることを確認する", () => {
template.resourcePropertiesCountIs(
"AWS::EC2::Subnet",
{
CidrBlock: Match.stringLikeRegexp("/24$"),
},
2
);
});
しかし、(実際に使用経験がある方なら共感いただけると思いますが、)このモジュール単体では、単体テストを効率的に実装していくのは結構大変です。その大きな原因の一つが、テスト対象となる各種AWS CloudFormationリソースタイプ(上記の例であればAWS::EC2::Subnet
)に関する型情報が乏しく、テストコード実装時にIDEの型ヒント機能や言語そのものの型チェック機能の恩恵を殆ど得られないためです。
例えばTypeScriptのassertionsの場合、引数や戻り値として扱える各種リソースタイプの型情報は上記の通り、非常に型情報に乏しいです。
import { aws_ec2 as ec2, Stack } from "aws-cdk-lib";
import { Template } from "aws-cdk-lib/assertions";
const stack = new Stack();
new ec2.Vpc(stack, "VPC", {
ipAddresses: ec2.IpAddresses.cidr("10.0.0.0/16"),
subnetConfiguration: [
{ subnetType: ec2.SubnetType.PUBLIC, cidrMask: 24, name: "public" },
],
maxAzs: 2,
});
const template = Template.fromStack(stack);
test("サブネットマスクが/24のサブネットが2つ作成されることを確認する", () => {
template.resourcePropertiesCountIs(
"AWS::EC2::Subent", // リソースタイプも文字列でハードコードが必要なので、偶にスペルミスする
{
// CidrBlock: Match.stringLikeRegexp("/24$"),
// プロパティ名や値の型を間違えてもコンパイルは通ってしまう。
Cidrblock: 24,
},
2
);
});
上記のように、単体テストコードの実装をうっかり間違えて単体テストが失敗した場合、テストコードの実装が間違っているのか、それとも本当にコンストラクトやスタックの実装が間違っているのか、調査するのに一手間要します。
そこで開発したのが、以下のような機能を有するaws-cdk-utulです。
機能
TypedTemplate
TypedTemplate
クラスは、aws-cdk-lib/assertionsモジュールのTemplate
クラスの使い勝手を維持しながら、(ほぼ)全てのAWS CloudFormationリソースタイプに関する適切な型情報を提供するため、IDEの型ヒント機能をフル活用して高速且つ効率的にAWS CDK単体テストを実装できます。
これによって、前述したようなテストコード実装時の凡ミスや、些細な内容で公式ドキュメントをググる必要も大幅に減らせます。
-
aws-cdk-lib/assertionsモジュールの
Template
クラスの全てのメソッドを適切な型定義有りの状態で利用できます。
-
メソッド(例えば
findResoruces
)からの戻り値は、後続の処理でより利用し易いような構造に変更しています戻り値の使用例import { TypedTemplate } from "@horietakehiro/aws-cdk-utul/lib/assertions"; import { AWS_EC2_SUBNET } from "@horietakehiro/aws-cdk-utul/lib/types/cfn-resource-types"; import { aws_ec2 as ec2, Stack } from "aws-cdk-lib"; const stack = new Stack(); new ec2.Vpc(stack, "VPC", { ipAddresses: ec2.IpAddresses.cidr("10.0.0.0/16"), subnetConfiguration: [ { subnetType: ec2.SubnetType.PUBLIC, cidrMask: 24, name: "public" }, ], maxAzs: 2, }); const template = TypedTemplate.fromStack(stack); test("サブネットマスクが/24のサブネットが2つ作成されることを確認する", () => { // 従来の戻り値は{"リソースの論理ID" : {リソースの定義}}だが、 // TypedTemplateの戻り値は、{id: リソースの論理ID, def: {リソースの定義}}のリストとしている const subnets = template.findResources(AWS_EC2_SUBNET()); expect(subnets.length).toBe(2); subnets.forEach(({ id, def }) => { expect(id.includes("public")).toBe(true); expect(def.Properties?.CidrBlock?.endsWith("/24")).toBe(true); }); });
-
aws-cdk-lib/assertionsモジュールの
Matcher
クラスや任意のオブジェクト型も引き続き併用できます。
ExtraMatch
※こちらはほぼおまけのような機能を提供するモジュールになります。
ExtraMatch
クラスはAWS CDKのMatch
クラスに対する幾つかのシンタックスシュガーを提供します。
import {
ExtraMatch,
TypedTemplate,
} from "@horietakehiro/aws-cdk-utul/lib/assertions";
import {
AWS_EC2_SUBNET,
AWS_EC2_VPC,
} from "@horietakehiro/aws-cdk-utul/lib/types/cfn-resource-types";
import { CfnOutput, aws_ec2 as ec2, Stack } from "aws-cdk-lib";
const stack = new Stack();
const vpc = new ec2.Vpc(stack, "VPC", {
ipAddresses: ec2.IpAddresses.cidr("10.0.0.0/16"),
subnetConfiguration: [
{ subnetType: ec2.SubnetType.PUBLIC, cidrMask: 24, name: "public" },
],
maxAzs: 2,
});
new CfnOutput(stack, "VPCCidrBlock", {value: vpc.vpcCidrBlock})
const template = TypedTemplate.fromStack(stack);
test("サブネットマスクが/24のサブネットが2つ作成されることを確認する", () => {
const [{ id }] = template.findResources(AWS_EC2_VPC());
template.hasResource(
AWS_EC2_SUBNET({
Properties: {
// VpcId: {Ref: id}と同義
VpcId: ExtraMatch.ref(id),
},
})
);
// Value: {Fn::GetAtt: [id, "CidrBlock"]}と同義
template.hasOutput("VPCCidrBlock", { Value: ExtraMatch.getAtt(id, "CidrBlock") });
});
インストール方法
npm install @horietakehiro/aws-cdk-utul
補足
- このライブラリで参照しているAWS CloudFormationリソースタイプのスキーマ情報は、us-east-1(バージニアリージョン)のものを使用しています
- AWS CDK v2以上と互換性があります
おわりに
aws-cdk-utulを使用することで、高速且つ効率的にAWS CDKの単体テストを実装できます。また今後も、単体テスト実装効率化の為の便利機能を(思いつき次第)本ライブラリに色々追加していきたいと思っています。
興味のある方は是非ご利用ください。
明日のNTTテクノクロス Advent Calendar 2024は、 @subutakahiro さんによる、GitHub Copilot for Xcodeについての記事です。お楽しみに!