メインコンテンツに移動
コミュニティ通信

AWS CDK における単体テストの使い所を学ぶ

2024-11-01 | Author : 後藤 健太 (AWS DevTools Hero)

はじめに

皆さん、こんにちは。AWS DevTools Hero の後藤と申します。普段、AWS Cloud Development Kit (AWS CDK) へのコントリビュート活動を行っており、Top Contributor、並びに Community Reviewer に選定いただいています。

AWS CDK は、使い慣れたプログラミング言語でクラウドリソースを定義できるオープンソースのフレームワークです。プログラミング言語で書けるということは、一般的なソフトウェア開発で行われるようなテストコードを書くことができるということです。

今回はそんな IaC ツールである AWS CDK におけるテストのうち、単体テストの使い所、つまり「どんな場面でどのように単体テストを使えば良いのか」について紹介します。


X ポスト » | Facebook シェア » | はてブ »

builders.flash メールメンバー登録

builders.flash メールメンバー登録で、毎月の最新アップデート情報とともに、AWS を無料でお試しいただけるクレジットコードを受け取ることができます。 
今すぐ登録 »

1. 単体テストとは

一般的なソフトウェア開発における単体テスト (Unit Tests) とは、関数やクラスなど、アプリケーションの最小の構成要素である「ユニット」に対して行われるテストのことです。

単体テスト以外にも、単体テストよりも広い範囲を対象とする統合テスト (Integration Tests) などのテストもあります。

それらと比べると、単体テストはユニット単位の小さい粒度を対象としたテストであるため、バグの検出時に原因を特定しやすく、またテストの実行が速いというメリットがあります。

2. AWS CDK における単体テストの種類

AWS CDK における単体テストの種類には、主に以下の 3 つがあります。

  • スナップショットテスト
  • Fine-grained assertions テスト
  • バリデーションテスト

前者 2 つに関しては、AWS CDK の Developer Guide の Test AWS CDK applications というページに詳しい説明が掲載されているので、ぜひご覧ください。また、AWS Black Belt Online Seminar にて公開されている AWS CDK における開発とテスト (Advanced #1) からもこれらのテストについて学ぶことができます。

また、AWS CDK における統合テストは、実際に CDK コードで定義したリソースをデプロイして動作確認するのが一般的で非常に有用なテストなのですが、本記事では統合テストに関しては触れません。AWS CDK における統合テストに関しては、AWS の公式ブログで AWS CDK アプリケーションのためのインテグレーションテストの作成と実行 という記事が公開されているのでぜひご覧ください。

ではここから、AWS CDK におけるそれぞれの単体テストの書き方や、具体的にどんな場面でどのように使えば良いのかといった使い所についてご紹介していきます。

3. スナップショットテスト

3-1. スナップショットテスト

AWS CDK におけるスナップショットテストとは、CDK コードから合成される AWS CloudFormation テンプレートを出力し、以前のテスト実行時に生成したテンプレートの内容と比較してテンプレートの差分を検出するテストのことです。

例えば、MyStack という Stack クラスに対するスナップショットテストは、以下のような書き方で行うことができます。

※テストファイルは、cdk initコマンドで CDK プロジェクト作成時にプロジェクトのルートディレクトリの直下に test というディレクトリが作られているため、その中に my-stack.test.ts というようなファイルを作成して書くことが一般的です。

Screenshot of source code demonstrating how to write a unit test for an AWS Cloud Development Kit (CDK) stack using Jest and the CDK assertions library. The code imports modules from 'aws-cdk-lib', sets up a test for snapshot validation of a stack called MyStack, and shows usage of Template.fromStack and expect(...).toMatchSnapshot().

テストファイルの実行

このテストファイルを、cdk init を実行した際に定義されている npm run test コマンドで実行すると、test ディレクトリの直下に __snapshots__ というディレクトリが作成され、その中に my-stack.test.ts.snap というスナップショットファイルが保存されます。スナップショットファイルは、CloudFormation テンプレートの JSON 形式で保存されています。そして、その以前のテスト実行時に保存されたスナップショットと、今回の CDK コードによって生成されたテンプレートを比較し、差分がある場合はテストが失敗します。

その差分が想定通りのものである場合、npx jest --updateSnapshotというコマンドでテストを実行することで、スナップショットファイルを今回生成されたものに更新することができます。

3-2. スナップショットテストの使い所

私の個人的な考えとしてですが、スナップショットテストは AWS CDK を使用した開発において、開発初期などの一部を除きほとんど必須であると考えています。

特に、以下の場面で効果を発揮します。

  1. AWS CDK のバージョンアップデート

  2. CDK コードのリファクタリング

  3. バージョン管理システムでの差分管理

3-2-1. AWS CDK のバージョンアップデート

スナップショットテストを使っておいた方が良い大きな理由として挙げられるのが、AWS CDK ライブラリのバージョンを上げた際でも、スナップショットテストが成功してさえいれば CloudFormation テンプレートに差分は生じていない、つまりデプロイ済みのリソースへの影響が無いことを保証出来るからです。

というのも、AWS CDK ライブラリはバージョンが上がるごとに様々な変更が加えられ、新たな機能が追加されたり、バグが修正されたりすることによって CloudFormation テンプレートの出力が変わることがまれにあります。

しかし、AWS CDK を用いて開発を行っているプロジェクトにおいて、生成される CloudFormation テンプレートの内容が AWS CDK のバージョンを上げた際に変わってしまうと、それによってすでにデプロイしているリソースに思わぬ変更が発生してしまうことがあります。それを事前に検知して防ぐことができるのが、このスナップショットテストなのです。

基本的には OSS (オープンソースソフトウェア) である AWS CDK ライブラリ自体の開発側で、生成される CloudFormation テンプレートに極力変更がないように開発が行われているのですが、どうしても変わってしまうことはあるためユーザー側でスナップショットテストを行うことが重要になってきます。(とはいえ、AWS CDK の OSS 開発においては破壊的変更を防ぐための仕組みがあるため安心です。)

3-2-2. CDK コードのリファクタリング

CDK コードをリファクタリングする際にも、スナップショットテストは非常に有用です。

基本的にリファクタリングでは、コードの変更前と変更後で挙動 (CDK においては CloudFormation テンプレート) が変わらないようにすることが求められます。

スナップショットテストを活用することで、リファクタリングによって生成される CloudFormation テンプレートが、リファクタリング前と同じであることを確認することができます。

3-2-3. バージョン管理システムでの差分管理

Git などでスナップショットファイルを含むコードのバージョン管理をしていると、開発・運用中にリソース定義の変更を行った際に、より強力な効果を発揮します。

それは、CDK コードの粒度だけでなく、CloudFormation テンプレートの粒度での更新差分が可視化・記録されるからです。

CDK コード上ではわかりづらいような思わぬ変更も確認できるため、リソース定義の変更に対するリスクを最小限に抑えることができます。


4. Fine-grained assertions テスト

4-1.Fine-grained assertions テストとは

AWS CDK における Fine-grained assertions テストとは、生成された CloudFormation テンプレートの一部を取り出して、その部分に対してチェックを行うテストのことです。これにより、どのようなリソースが生成されるのかといった細かい構成要素に対するテストをすることができます。

例えば、「AWS::SNS::Subscription のリソースが 2 つ生成されているか」や、「AWS::Lambda::Function Runtime nodejs20.x が設定されているか」といったような細かい粒度のテストを行うことができます。

Screenshot of TypeScript code showing AWS Cloud Development Kit (CDK) unit tests for fine-grained assertions, including tests for SNS subscription count and Lambda runtime configuration with nodejs20.x.

テストコードの例

例えば、「AWS::SNS::Subscription のリソースが 2 つ生成されているか」や、「AWS::Lambda::Function の Runtime に nodejs20.x が設定されているか」といったような細かい粒度のテストを行うことができます。
Screenshot of TypeScript code demonstrating unit tests for AWS Cloud Development Kit (CDK) using fine-grained assertions. The sample tests check for the creation of two SNS subscriptions and verify a Lambda function's runtime is nodejs20.x.

4-2. Fine-grained assertions テストの使い所

この Fine-grained assertions テストですが、実は具体的にどういう時にどんなテストを書けば良いのか、といったセオリーはまだあまり確立されていないように感じます。

というのも、リソースごと、プロパティごとといった細かい粒度で様々なチェックをすることができ、多岐にわたるテストケースが考えられるためです。また全てのリソースに対してテストを書いていくとさらに膨大な量になり、テストの旨みよりも冗長さやメンテナンスの大変さが上回ってしまうケースもあります。

そのため、あくまで私個人の判断基準となりますが、以下のような場面で使うのが良いと考えています。

1. ループ処理
2. 条件分岐
3. プロパティの override
4. 特に保証したい定義
5. props を使った値の指定

前提として、AWS CDK はプログラミング言語で書けるが故に「手続き的」にリソース定義のコード記述を行うこともできますが、「宣言的」にリソース定義の記述を行うことが可能です。

※ここでいう「宣言的」とは、「○○ というリソースを作成する」というように、リソースの存在を宣言することを指します。一方で「手続き的」とは、「○○ というリソースを作成するために、まずはこういう処理を行い、次にこういう処理を行う」といったように、リソースの作成手順を手続き的に記述することを指します。

インフラ定義としては、やはり定義を見るだけでどんなリソースが生成されるのかがわかりやすい「宣言的」な方が好ましいケースが多いかと思われます。AWS CDK はあくまでインフラ定義のためのツールであるため、基本的には「宣言的」に書くことが多いでしょう。

そして、「リソース A を作る」というような宣言的な記述において、「リソース A が作られる」というのは自明です。自明なものを確認する旨みに対して、Fine-grained assertions テストではリソース定義側のコードとほぼ同じようなコードが出来上がることで二重定義のような煩わしさを感じ、テストのメンテナンスコストが上がってしまうこともあります。

そのような理由から、私の個人的な判断基準としては、全てのリソースに対して細かく Fine-grained assertions テストを書くのではなく、上記のような場面で使うのが良いと考えています。

4-2-1. ループ処理

上記では「宣言的」にリソース定義の記述を行うお話をしましたが、ループ処理を使ってリソースを生成する場合、それは「手続き的」なコード記述になります。

つまり、これによってどのようなリソースが生成されるかは自明とは言えなくなってしまいます。

そのためループ処理を使ってリソースを生成する場合には、そのループ処理が正しく動作しているかを確認するための Fine-grained assertions テストを書くことが重要になります。

ループ処理を使ったリソース定義

具体的には、以下のような CDK コードがあるとします。

typescript
import * as cdk from 'aws-cdk-lib';
import { Construct } from 'constructs';
import { Topic } from 'aws-cdk-lib/aws-sns';

export interface MyStackProps extends cdk.StackProps {
  appNames: string[];
}

export class MyStack extends cdk.Stack {
  constructor(scope: Construct, id: string, props: MyStackProps) {
    super(scope, id, props);

    // 重複する要素がある場合を考慮して一意な組み合わせにする
    const appNames = new Set(props.appNames);

    for (const appName of appNames) {
      new Topic(this, `${appName}Topic`, {
        displayName: `${appName}Topic`,
      });
    }
  }
}

Fine-grained assertions

このようなループ処理を使ったリソース定義に対して、以下のような Fine-grained assertions テストを書くことができます。
Screenshot of code demonstrating a unit test for AWS Cloud Development Kit (CDK) using fine-grained assertions to check creation and properties of SNS Topics in a stack with imported modules from 'aws-cdk-lib', 'aws-cdk-lib/assertions', and a custom stack.

4-2-2. 条件分岐

if 文のような条件分岐を使って環境ごとにリソースを生成するかどうかを変えるような場合も、その条件分岐が正しく動作しているかの確認は重要です。 以下のような CDK コードがあるとします。

typescript
import * as cdk from 'aws-cdk-lib';
import { CfnWebACL } from 'aws-cdk-lib/aws-waf';
import { Construct } from 'constructs';

export interface MyStackProps extends cdk.StackProps {
  isProd: boolean;
}

export class MyStack extends cdk.Stack {
  constructor(scope: Construct, id: string, props: MyStackProps) {
    super(scope, id, props);

    if (props.isProd) {
      new CfnWebACL(this, 'WebAcl', {
        // ...
      });
    }
  }
}

テストの例

isProd が true の場合に CfnWebACL が作られることを確認するには、以下のようなテストを書くことができます。
Screenshot of a JavaScript code example for AWS Cloud Development Kit (CDK) unit testing, demonstrating fine-grained assertions using the 'assertions' library to verify that a Web ACL is created in a production stack with the expected resource count.

環境ごとにプロパティを指定するかどうかを変えるような場合

今度は、環境ごとにプロパティを指定するかどうかを変えるような場合です。

typescript
import * as cdk from 'aws-cdk-lib';
import { Distribution } from 'aws-cdk-lib/aws-cloudfront';
import { Construct } from 'constructs';

export interface MyStackProps extends cdk.StackProps {
  isProd: boolean;
}

export class MyStack extends cdk.Stack {
  constructor(scope: Construct, id: string, props: MyStackProps) {
    super(scope, id, props);

    // ...

    new Distribution(this, 'Distribution', {
      // ...
      webAclId: props.isProd ? webAclId : undefined,
    });
  }
}

Match.absent

例えば、isProd が false の場合に webAclId が「紐付かない」ことを確認するテストも書くことができます。特徴としては、Match.absent メソッドを使うことで、該当のプロパティに「値が指定されていない」ことを確認することができます。
Screenshot of a TypeScript unit test for AWS Cloud Development Kit (CDK) using the 'assertions' module, demonstrating how to check that the WebACLId property is not set for a CloudFront Distribution. The code includes AWS CDK imports, stack creation, and a test verifying the absence of WebACLId with a comment in Japanese.

4-2-3. プロパティの override

AWS CDK では、CDK で提供される L2 Construct を使ってリソースを定義することが一般的です。

しかし、L2 Construct には対応していないプロパティを設定したいケースなどで、エスケープハッチ を用いて L1 Construct にキャストしてから、addPropertyOverride などのメソッドでプロパティを override (上書き) することがあります。

この場合プロパティの指定に Construct の型が使えず、CloudFormation テンプレートの構造に合わせて自前でプロパティの記述をする必要があり、特に階層構造のようなプロパティの場合に記述ミスが発生しやすいです。

エスケープハッチ

コード

typescript
import * as cdk from 'aws-cdk-lib';
import { Construct } from 'constructs';
import { Bucket, CfnBucket } from 'aws-cdk-lib/aws-s3';

export class MyStack extends cdk.Stack {
  constructor(scope: Construct, id: string) {
    super(scope, id);

    const bucket = new Bucket(this, 'Bucket');

    // Bucket をエスケープハッチしてプロパティを override する
    const cfnSrcBucket = bucket.node.defaultChild as CfnBucket;
    cfnSrcBucket.addPropertyOverride('NotificationConfiguration.EventBridgeConfiguration.EventBridgeEnabled', true);
  }
}

テストの例

このように override を用いてリソース定義を上書きする場合に、それが意図した通りに反映されているかを確認するためのテストを書くことができます。
Screenshot showing a sample TypeScript unit test using AWS CDK's assertion library, demonstrating fine-grained assertions for EventBridge configuration in an S3 bucket. Includes Japanese comment highlighting property overwriting with escape hatch.

4-2-4. 特に保証したい定義

次は、特に保証したい定義に対してテストを書くケースです。

これは最初にご説明した「宣言的」なコード記述に対して行うテストになります。

先ほどは、「宣言的」、つまりリソース定義が自明なものには Fine-grained assertions テストを書かないかのような説明をしましたが、特に保証したい定義に対してテストを書くことも重要です。

例えば、「ある要件を実現するためにこのプロパティは設定しておきたい」などのような、他のプロパティと比べて重要な定義に対して、「意思表示」 のような形でテストを書いておくことができます。もし今後、別の開発者がその設定を変更してしまった際に該当するテストが失敗し、その違反したテストを参照することで元の設計者の意図を理解でき、意思の伝搬に繋がることもあります。

ライフサイクルで期限設定をする

コード

typescript
import * as cdk from 'aws-cdk-lib';
import { Construct } from 'constructs';
import { Bucket } from 'aws-cdk-lib/aws-s3';

export class MyStack extends cdk.Stack {
  constructor(scope: Construct, id: string) {
    super(scope, id);

    new Bucket(this, 'Bucket', {
      lifecycleRules: [{ expiration: cdk.Duration.days(100) }],
    });
  }
}

テストコードの例

この lifecycleRules における expiration という設定を保証したい場合、以下のようなテストを書くことができます。
Screenshot of a TypeScript unit test for AWS CDK using fine-grained assertions to check S3 Bucket lifecycle configuration. The code imports App, assertions, and Template from AWS CDK libraries, creates a stack, and tests that the bucket's LifecycleConfiguration has an ExpirationInDays of 100 and Status set to 'Enabled'.

Match.anyValue

この場合、開発中の要件変更などでプロパティの値を変更した際に、テスト側の値も合わせて変更する必要があります。もしプロパティを指定できているかだけを確認できれば良い場合は、Match.anyValue メソッドを用いることで具体値の指定はせずとも確認することができ、テストのメンテナンスコストを下げることができます。

typescript
import { Match } from 'aws-cdk-lib/assertions';

// ...

template.hasResourceProperties('AWS::S3::Bucket', {
  LifecycleConfiguration: {
    Rules: [
      {
        ExpirationInDays: Match.anyValue(),
        Status: 'Enabled',
      },
    ],
  },
});

addDependency

また、例えば addDependency のような CDK によって提供されるメソッドを使って定義を加える際にも、意図した通りに反映されていることを保証したいケースもあるでしょう。

typescript
import * as cdk from 'aws-cdk-lib';
import { Construct } from 'constructs';
import { LogGroup, ResourcePolicy } from 'aws-cdk-lib/aws-logs';
import { PolicyStatement, ServicePrincipal } from 'aws-cdk-lib/aws-iam';
import { HostedZone } from 'aws-cdk-lib/aws-route53';
import { Bucket, CfnBucket } from 'aws-cdk-lib/aws-s3';

export interface MyStackProps extends cdk.StackProps {
  domainName: string;
}

export class MyStack extends cdk.Stack {
  constructor(scope: Construct, id: string, props: MyStackProps) {
    super(scope, id, props);

    const logGroup = new LogGroup(this, 'QueryLogGroup');
    const hostedZone = new HostedZone(this, 'HostedZone', {
      zoneName: props.domainName,
      queryLogsLogGroupArn: logGroup.logGroupArn,
    });
    const resourcePolicy = new ResourcePolicy(this, 'QueryLogResourcePolicy', {
      policyStatements: [
        new PolicyStatement({
          principals: [new ServicePrincipal('route53.amazonaws.com')],
          actions: ['logs:CreateLogStream', 'logs:PutLogEvents'],
          resources: [logGroup.logGroupArn],
        }),
      ],
    });

    // HostedZone が QueryLogResourcePolicy に依存するように
    hostedZone.node.addDependency(resourcePolicy);
  }
}

テストの例

この例の場合では以下のようなテストを書くことで、想定通りのリソース間に依存が追加されているかどうかを確認できます。
Screenshot of a unit test for AWS CDK (Cloud Development Kit) using fine-grained assertions. The code imports required modules, sets up a template, and tests if an AWS Route53 HostedZone resource depends on QueryLogResourcePolicy using addDependency. The test includes both English and Japanese comments explaining the dependency check.

4-2-5. props を使った値の指定

Stack や Construct に渡す props の値を使ってリソースのプロパティを指定する場面でも、Fine-grained assertions テストは有用です。具体的には、props の値が正しくリソースに反映されているかを確認するテストを書きます。これは、リソースのプロパティに具体値を直接記述する「ベタ書き」の定義ではない場合に起こりうる、値の渡し忘れの防止になります。
Screenshot of sample unit test code for AWS Cloud Development Kit (CDK) demonstrating fine-grained assertions using messageRetentionPeriodInDays and checking resource properties for AWS SNS Topic with specific ArchivePolicy. Contains TypeScript code and Japanese comments.

実際の Stack に渡す用に定義した props をそのまま使う場合

また実際にデプロイされる CDK コードによるリソース定義をテストするために、テスト用 props ではなく実際の Stack に渡す用に定義した props をそのまま使いたいケースも多いかと思います。この場合、その props が持つプロパティを使って確認をすることで、具体値の指定はせずとも props から渡された値を指定できていることを保証でき、テストのメンテナンスコストを削減できます。
A sample code snippet showing how to write fine-grained unit tests using assertions in AWS CDK, with Japanese comments explaining the use of stack properties in testing resource definitions.

5. バリデーションテスト

5-1. バリデーションテストとは

3 つ目にご説明するバリデーションテストとは、その名の通りバリデーションに関するテストになります。

バリデーションとは、条件分岐などを通して値の妥当性を検証する処理のことです。AWS CDK においても、Stack や Construct への入力である props のプロパティに対してバリデーション処理を実装することがあります。

AWS CDK における具体的なバリデーションの方法に関しては、筆者が以前 builders.flash で執筆した AWS CDK におけるバリデーションの使い分け方を学ぶ という記事をご覧ください。

入力値を検証するバリデーションテストの例

例えばバリデーションテストには、あるプロパティに対する入力値が特定の範囲内に収まっているかを検証するコードを書くことができます。

typescript
import * as cdk from 'aws-cdk-lib';
import { Construct } from 'constructs';
import { Bucket } from 'aws-cdk-lib/aws-s3';

export interface MyStackProps extends cdk.StackProps {
  lifecycleDays: number;
}

export class MyStack extends cdk.Stack {
  constructor(scope: Construct, id: string, props: MyStackProps) {
    super(scope, id, props);

    if (!cdk.Token.isUnresolved(props.lifecycleDays) && props.lifecycleDays > 400) {
      throw new Error('ライフサイクル日数は400日以下にしてください');
    }

    new Bucket(this, 'Bucket', {
      lifecycleRules: [
        {
          expiration: cdk.Duration.days(props.lifecycleDays),
        },
      ],
    });
  }
}

cdk.Token.isUnresolved

cdk.Token.isUnresolved メソッドは、値が Token でないかどうかを確認するメソッドになります。こちらの解説なども上記の「AWS CDK におけるバリデーションの使い分け方を学ぶ」記事に記載しているため、ぜひご覧ください。

テストの例

このような CDK コードに対して、以下のようなバリデーションテストを書くことができます。許容しない入力値が渡された際に、エラーが発生することを確認するテストになります。
Screenshot of AWS CDK unit test code demonstrating validation logic for lifecycle days. The test ensures that the lifecycleDays property in MyStack is 400 or fewer days, and throws an error in Japanese if exceeded.

5-2. バリデーションテストの使い所

Stack や Construct が受け取るプロパティに対して何らかのバリデーション処理を実装している場合、そのバリデーション処理が正しく動作しているかは非常に重要な確認事項であるため、バリデーションテストはぜひ書いておきたいテストです。各バリデーションごとにテストケースを書けると良いでしょう。

逆に言えば、特にバリデーション処理を何も実装していない場合は不要となります。

6. CDK の単体テストで覚えておくと良いこと

6-1. 個数チェックと自動生成リソース

Fine-grained assertions テストでは、assertions モジュール の Template クラスが持つ resourceCountIs メソッドなどで、特定のリソースタイプの個数を確認するテストを書くことができます。

typescript
const template = Template.fromStack(stack); template.resourceCountIs('AWS::Logs::LogGroup', 5);

L2 Construct

一方で、CDK でよく使う L2 Construct ですが、ベストプラクティスに沿ったり、より高い開発者体験を提供するために、Construct 内部に自動でいくつかのリソースが生成されることがあります。そこで、例えば上記のテストのように、自分ではその種類のリソースを 5 つ定義したつもりでも、実際には 6 つのリソースが生成されていた、といったようなケースがあります。

また、そのようなケースも加味してテストで指定する個数を 6 つとしたとしても、後からその値を見た際に内訳がよくわからず混乱や認知負荷につながることもあるため注意しましょう。よほど自動生成リソースも含めた個数を確認したいわけではない場合、リソースの自動生成はスナップショットテストの更新差分からでも確認可能なため、このような個数チェックを捨てるといった選択肢に目を向けるのも良いかもしれません。それでもテストを残したい場合、意図がわかるようにきちんとコメントを書くことも良いでしょう。

resourcePropertiesCountIs メソッド

もしくは、resourcePropertiesCountIs メソッドを使用して、特定のプロパティや値を持つリソースに絞った個数を確認するテストもぜひご検討ください。

typescript
const template = Template.fromStack(stack);
template.resourcePropertiesCountIs(
  'AWS::Logs::LogGroup',
  {
    // '/aws/lambda/my-app/'という命名規則を持つロググループに限定
    LogGroupName: Match.stringLikeRegexp('/aws/lambda/my-app/'),
  },
  5,
);

6-2. Construct ごとのテスト

本記事では基本的に、実際に定義した Stack クラスに対する単体テストを例としてご紹介しました。

しかし CDK コードを書く上で、カスタム Construct を作成して、それらを組み合わせて Stack を定義することも多いかと思います。

そのような場合、カスタム Construct ごとに単体テストを書くことで、その Construct に閉じた範囲でのテストを行うことができ、他の Construct に影響を受けずに動作を確認することができます。これにより、Construct 単体での信頼性や再利用性を担保することができます。

また、テストファイルを Construct ごとに分けることで、一つ一つのテストファイルが責務ごとに凝集されてよりシンプルになり、理解容易性が増すかもしれません。

テストの例

具体的には、空のスタックを定義し、テスト対象のカスタム Construct のみを追加してテストを行うことができます。
Screenshot of unit test code for AWS CDK in TypeScript, including Japanese comments and a test using MyConstruct and SNS Topic resource assertions.

注意すべき点

しかし、Construct の数が多くなるにつれ、全ての Construct ごとにテストを書こうとするとテストの数が非常に多くなってしまうことがあります。また、Construct ごとのテストを書いたとしても、実際にデプロイされる環境の構成となる Stack に対するテストは必須で書いておきたいでしょう。その場合、Stack のテストと Construct のテストで重複しないようにうまくテストの範囲や責務を分けないと、テストのメンテナンスが大変になる可能性があるため注意が必要です。

再利用性を特に担保したい Construct のみに Construct 単位のテストを書くといった使い分けも良いでしょう。もしくは、特に Construct を再利用するケースがない場合は、Construct ごとの単体テストを書かないという選択肢も問題ないと思います。

7. オススメの最小構成

本記事でご紹介した全てのテストをまとめて導入することは中々大変かと思われます。

そのため最小構成でお手軽に CDK での単体テストを導入したい場合、まずはスナップショットテストから導入してみるのが良いでしょう。簡単に導入ができ、かつデプロイされる CloudFormation テンプレートでの思わぬ変更を検知できることは非常に大きなメリットです。

8. まとめ

AWS CDK における単体テストとして、以下の 3 つの単体テストの書き方、およびそれらの使い所についてご紹介しました。

  • スナップショットテスト
  • Fine-grained assertions テスト
  • バリデーションテスト


単体テストは AWS CDK においても信頼性を向上するために非常に重要な要素です。ぜひこれらのテストを活用して、より信頼性の高い AWS CDK 開発に取り組んでみてもらえると幸いです。

筆者プロフィール

後藤 健太 (AWS DevTools Hero / @365_step_tech)

AWS CDK のコントリビュート活動を行っており、Top Contributor や Community Reviewer に選定。2024 年 2 月に発足されたコミュニティ駆動の CDK コンストラクトライブラリである Open Constructs Library では、メンテナーを担っている。
また、cls3 や delstack といった自作 AWS ツールの OSS 開発も行なっている。2024 年 3 月、AWS DevTools Hero に選出。

Portrait of an individual with dark hair wearing a black shirt, set against a light blue background.