7
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

NTTテクノクロスAdvent Calendar 2024

Day 1

aws-cdk-utul - AWS CDK単体テスト実装を高速化するライブラリをリリースしました

Last updated at Posted at 2024-11-30

この記事は、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)されるかをテストできます。

assertionsモジュールによる単体テストの実装例
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-utulの使用例

  • aws-cdk-lib/assertionsモジュールのTemplateクラスの全てのメソッドを適切な型定義有りの状態で利用できます。
    TypedTemplateの実装メソッド

  • メソッド(例えば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クラスや任意のオブジェクト型も引き続き併用できます。
    Matchクラスも引き続き使用可能


ExtraMatch

※こちらはほぼおまけのような機能を提供するモジュールになります。

ExtraMatchクラスはAWS CDKのMatchクラスに対する幾つかのシンタックスシュガーを提供します。

ExtraMatchの使用イメージ
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でインストール
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についての記事です。お楽しみに!

7
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
7
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?