1. はじめに

読者の皆様、AWS基盤の構築には、どういったツールをお使いでしょうか。

AWS で利用できる構築ツールには、AWS CloudFormation 、Terraform、AWS CDK がありますが、ここでは、AWS CDK についてフォーカスします。

AWS CDK とは 、普段開発者が利用しているプログラミング言語で AWS 構築ができるツールです。

AWS CDK は、普段利用しているプログラミング言語で開発できるメリットがある一方で、意図しないリソースが作成や更新される可能性を孕んでいます。

こういった意図しないリソースの作成や更新をデプロイ前に発見することは、安心して本番環境に変更を反映するには必要不可欠な要素です。

本記事では、こういった意図しない変更をデプロイ前に発見する方法の一つでであるスナップショットテストについて説明していきます。

2. スナップショットテストとは?

AWS CDK は、TypeScript等のプログラミング言語で記載しますが、最終的には、ビルドすることで、AWS CloudFormation テンプレートを生成されます。

スナップショットテストは、前回ビルドされた AWS CloudFormation テンプレートと新しく生成された AWS CloudFormation テンプレートを比較し、差分を検出するテストです。

新旧の AWS CloudFormation テンプレートに差分があれば、テスト失敗となります。

2.1. ユースケース

2.1.1. リファクタリング

コードリファクタリングとは?

コードリファクタリングは、機能を変更せずに読みやすさと理解を向上させるためにコード構造を改善するプロセスです。アプリケーションプログラミングでは、開発者は、アプリケーションが適切に機能>するように、相互に、および外部システムとやり取りするコードブロックを記述する必要があります。コードリファクタリングにより、これらのコードブロックは簡潔かつ正確になり、開発者の意図が明確に伝わります。これにより、コードベースの保守が容易になり、ソフトウェアのパフォーマンスが向上します。

参考: コードリファクタリングとは

リファクタリングとは、AWS のドキュメントにある通り、機能を変更せずにコードの構造を変更することをいいます。

AWS CDK でリファクタリング時に AWS リソースの構成が変更されていないか確認する用途でスナップショットテストが役立ちます。

2.1.2. レビュー時の差分確認

スナップショットテストは、開発者が自身のコードの修正によって意図しない AWS リソースが作成されていことの確認にも役立ちますが、コードレビューにも役立ちます。

スナップショットテストを実行すると、プロジェクトフォルダの test/__snapshots__/{プロジェクト名}.test/ts/snap が作成されます。

ここには、前回スナップショットテストで生成した AWS CloudFormation テンプレートが書き込まれています。

このファイルを GitHub等で差分比較すると、前回のスナップショットの比較することができるので、レビュワーも生成されるリソースに問題がないのか確認することができます。

3. スナップショットテストの実施

3.1. テストコードの作成

test/プロジェクト名.test.ts にテストコードを記述します。

import * as cdk from 'aws-cdk-lib';
import { Template } from 'aws-cdk-lib/assertions';
import * as test from '../lib/test-stack';


test('Snapshot test', () => {
    const app = new cdk.App();
    const stack = new LinuxLearning.LinuxLearningStack(app, 'MyTestStack');
    const template = Template.fromStack(stack);
    expect(template.toJSON()).toMatchSnapshot();
});

コードの説明

このテストコードでは、AWS CDK のスタックから CloudFormation テンプレートを生成し、その内容が以前保存したスナップショットと一致するかを検証しています。

const app = new cdk.App();

CDK アプリケーションを作成します。すべてのスタックは App に属するため、最初にインスタンスを生成します。

const stack = new TestStack(app, 'MyTestStack');

テスト対象となる CDK スタックを生成します。

const template = Template.fromStack(stack);

CDK スタックから CloudFormation テンプレートを取得します。

expect(template.toJSON()).toMatchSnapshot();

CloudFormation テンプレートを JSON 形式に変換し、保存済みのスナップショットと比較します。

初回実行時は比較対象が存在しないため、新しいスナップショットファイルが作成されます。2回目以降は、現在生成されたテンプレートとスナップショットを比較し、差分があればテストが失敗します。

3.2. テストの実行

ターミナルで npm test コマンドを実行すると、スナップショットテストが実行されます。

3.2.1. テスト成功

リファクタリング等実施後、以下のようにスナップショットテストが成功した場合、リファクタリングによってAWS リソースが変更されていないことの証明ができます。

$ npm test

> linux_learning@0.1.0 test
> jest

ts-jest[config] (WARN) message TS151002: Using hybrid module kind (Node16/18/Next) is only supported in "isolatedModules: true". Please set "isolatedModules: true" in your tsconfig.json. To disable this message, you can set "diagnostics.ignoreCodes" to include 151002 in your ts-jest config. See more at https://kulshekhar.github.io/ts-jest/docs/getting-started/options/diagnostics
 PASS  test/linux_learning.test.ts (10.195 s)
  ✓ Snapshot test (486 ms)

1 snapshot written.
Snapshot Summary
1 snapshot written from 1 test suite.

Test Suites: 1 passed, 1 total
Tests:       1 passed, 1 total
Snapshots:   1 written, 1 total
Time:        10.472 s
Ran all test suites.

3.2.2. テスト失敗

スナップショットテストに失敗すると、以下のように差分がエラーとして出力されます。

リファクタリング等実施後、以下のようにスナップショットテストが失敗した場合、リファクタリングによってAWS リソースに意図しない変更があったことを意味します。

一方で、意図してAWS リソースの変更を行った場合もここに差分として出力されます。

この場合、意図した変更だけが差分として出力されているか確認してください。

$ npm test

> linux_learning@0.1.0 test
> jest

 FAIL  test/linux_learning.test.ts (5.615 s)
  ✕ Snapshot test (411 ms)

  ● Snapshot test

    expect(received).toMatchSnapshot()

    Snapshot name: `Snapshot test 1`

    - Snapshot  - 0
    + Received  + 7

    @@ -196,10 +196,17 @@
                  "Description": "Allow HTTP access",
                  "FromPort": 80,
                  "IpProtocol": "tcp",
                  "ToPort": 80,
                },
    +           {
    +             "CidrIp": "0.0.0.0/0",
    +             "Description": "Allow HTTPS access",
    +             "FromPort": 443,
    +             "IpProtocol": "tcp",
    +             "ToPort": 443,
    +           },
              ],
              "VpcId": {
                "Ref": "LinuxLearningVPC060B1DB9",
              },
            },

       8 |     const stack = new LinuxLearning.LinuxLearningStack(app, 'MyTestStack');
       9 |     const template = Template.fromStack(stack);
    > 10 |     expect(template.toJSON()).toMatchSnapshot();
         |                               ^
      11 | });
      12 |

      at Object.<anonymous> (test/linux_learning.test.ts:10:31)

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:        5.87 s, estimated 11 s
Ran all test suites.

3.2.2. スナップショットの更新

スナップショットテストに失敗したが、差分が意図した修正の場合は、新しいスナップショットに更新します。

更新するには、npm test -- -u を実行します。

test/__snapshots__/{プロジェクト名}.test/ts/snap のスナップショットが更新されます。

$ npm test -- -u

> linux_learning@0.1.0 test
> jest -u

 PASS  test/linux_learning.test.ts (5.516 s)
  ✓ Snapshot test (414 ms)

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:        5.816 s, estimated 11 s
Ran all test suites.

4. レビュワー視点

スナップショットテストを実施するとレビュワー視点からもメリットがあります。

GitHubのプルリクエストでコードの差分時に、CDK のソースコードとスナップショットの差分を確認することでCDKの変更に問題ないのか机上でレビューすることが可能になり、安心してコードの変更を本番環境に反映することができます。

画像1

おわりに

本記事ではスナップショットテストの有用性について述べてきました。AWS CDK で AWS リソースの作成、更新を行う際、意図しない変更が怖くなることがありますが、スナップショットテストを活用すれば、デプロイ前に意図しない変更に気が付きやすくなります。

スナップショット用のテストコード作成には、それほどコストがかからないので、導入してみるのも良いと思います。

ただ、スナップショットテストだけでは、十分でないケースもあるので、より充実したテストが必要な場合、アサーションテストも組み合わせるのが良いでしょう。

参考

AWS CDK とは

AWS CDK アプリケーションのテスト

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

あらゆる言語でのCDKアプリケーションのテスト