18
11

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

AWS CDK と3つのテスト

Last updated at Posted at 2019-12-20

この記事は PLAID Advent Calendar 2019 の21日目の記事です。

はじめに

株式会社プレイド にてエンジニアインターンとしてお世話になっている @it-akumi と申します。
プレイドでは KARTE の開発をさせていただいており、専らJavaScriptと格闘する日々です1

これまでも他社のインターンに参加したことはありましたが、その際はWebサービスそれ自体の開発ではなく、インフラ周りの業務に従事していました。その際に AWS CDK を導入する機会がありましたので、今回はそのテストについて書きます。

AWS CDK とは

Infrastructure as Code という概念が登場して久しいですが、AWS 上に構築するリソースをコード化するために提供されているのが AWS CloudFormation (以下 CFn) です。CFn においては JSON または YAML でリソースの定義ファイル (テンプレート) を書き、これに基づいて様々な AWS リソースの構築がなされます。

さて、AWS CDK (以下 CDK2) は CFn の代替ともいうべきツールです。
これは CFn とは異なり、 TypeScript や Python といった言語でリソースの定義を行います。そのため、それぞれの言語でロジックが書けることはもちろんのこと、IDE による補完の恩恵を受けられたり、テストを書いたりすることもできます。

なお、CFn と CDK の関係については AWS CDK 開発者プレビューに次のように書かれています。

CDK はクラウドインフラストラクチャの「コンパイラ」であると考えてください。(中略) CDK application を実行することによって AWS の “アセンブリ言語” である、CloudFormation テンプレートが生成されます。これでテンプレートを CloudFormation のプロビジョニングエンジンで処理する準備が整います。

つまりCDK によるリソース構築は、まずコードに基づいて CFn で実行可能なテンプレートが作られ、それが実行されて AWS リソースが作られる、という流れになります。

3種類のテスト

公式ドキュメントには、CDK におけるテストとして以下の3つが示されています。

Snapshot tests

コードが生成する CFn テンプレートが、前回生成したものと同一であるかをチェックします。つまり、そのコードによって構築されるリソース及びその設定が既存のものと同一であるかをチェックするということです。

コードのリファクタリングを行う際に利用できるようですが、何かしらの変更を加える場合にはそれが意図的なものであってもテスト自体は失敗するということになります。

Fine-grained assertions

構築するリソースに対して意図した設定がなされているかをテストします。

Snapshot tests は意図的な変更を加えると失敗しますので、リソースの増築や設定変更の際にはこちらのテストが使えるようです。

Validation tests

あるリソースの設定に対するバリデーションのためのテストです。

さて、例として以下のリソースを考えます。
ALB にアタッチされた EC2 インスタンス上で Nginx が動作しており、そこに外部からアクセスできるようになっています3

aws-cdk-example.png

このリソースを構築するためのコードを一部示します。

lib/network-stack.ts
import cdk = require("@aws-cdk/core");
import ec2 = require("@aws-cdk/aws-ec2");

interface NetworkStackProps extends cdk.StackProps {
    cidrMask: number;
}

export class NetworkStack extends cdk.Stack {
    readonly vpc: ec2.Vpc;
    readonly cidr: string = "10.0.0.0/16";

    constructor(scope: cdk.Construct, id: string, props: NetworkStackProps) {
        super(scope, id, props);

        if(!(props.cidrMask >= 16 && props.cidrMask <= 28)){
            throw new Error("Valid values of cidrMask are 16--28");
        }

        this.vpc = new ec2.Vpc(this, "Vpc", {
            cidr: this.cidr,
            subnetConfiguration: [
                {
                    name: "Example-Public",
                    cidrMask: props.cidrMask,
                    subnetType: ec2.SubnetType.PUBLIC
                },
                {
                    name: "Example-Private",
                    cidrMask: props.cidrMask,
                    subnetType: ec2.SubnetType.PRIVATE
                }
            ]
        });

        this.vpc.node.applyAspect(new cdk.Tag("Name", "Example-Vpc"));
        for (let subnet of this.vpc.publicSubnets) {
            subnet.node.applyAspect(new cdk.Tag("Name", `${subnet.node.id.replace(/Subnet[0-9]$/, "")}-${subnet.availabilityZone}`));
        }
        for (let subnet of this.vpc.privateSubnets) {
            subnet.node.applyAspect(new cdk.Tag("Name", `${subnet.node.id.replace(/Subnet[0-9]$/, "")}-${subnet.availabilityZone}`));
        }
    }
}

以後、上記のコードに対するテストを書いていきます。
本記事に記すのは一部ですが、コード及びテスト全体は GitHub にありますのでご確認ください。

使用したパッケージは以下です。

$ npm list --depth=0
example@0.1.0 /home/user/aws-cdk-example
├── @aws-cdk/assert@1.19.0
├── @aws-cdk/core@1.19.0
├── @types/jest@24.0.24
├── jest@24.9.0
(以下省略)

なお、テストの実行方法はいずれも $ npm run build && npm test です4

Snapshot tests

テストは以下のようになりました。

test/aws-cdk-example.test.ts
import assert = require("@aws-cdk/assert");
import cdk = require("@aws-cdk/core");

import { NetworkStack } from "../lib/network-stack";

test("NetworkStack Snapshot Tests", () => {
    const app = new cdk.App();
    const networkStack = new NetworkStack(app, "NetworkStack", { cidrMask: 24 });
    expect(assert.SynthUtils.toCloudFormation(networkStack)).toMatchSnapshot();
});

これを実行すると、test/__snapshots__ というディレクトリにスナップショットが作られます。内部では Object として lib/network-stack.ts から生成される CFn テンプレートが入っており、これが比較対象として用いられることが窺えます。

Fine-grained assertions

テストは以下のようになりました。
CDK によって構築される VPC の2つのプロパティ (CidrBlock と Tags) の設定値をチェックしています。

test/aws-cdk-example.test.ts
import "@aws-cdk/assert/jest";
import cdk = require("@aws-cdk/core");

import { NetworkStack } from "../lib/network-stack";

test("NetworkStack Fine-Grained Assertions", () => {
    const app = new cdk.App();
    const networkStack = new NetworkStack(app, "NetworkStack", { cidrMask: 24 });
    expect(networkStack).toHaveResource("AWS::EC2::VPC", {
        CidrBlock: "10.0.0.0/16",
        Tags: [{ "Key": "Name", "Value": "Example-Vpc" }]
    });
});

テストとして書いたプロパティとその値は CFn のテンプレート形式に準じているようですので、必要に応じてそちらのドキュメントも参照する必要があるかもしれません。

なお、 toHaveResource メソッドは通常のJestには存在しませんので、 @aws-cdk/assert/jest を import しておく必要があります。

Validation tests

テストは以下のようになりました。

test/aws-cdk-example.test.ts
import "@aws-cdk/assert/jest";
import cdk = require("@aws-cdk/core");

import { NetworkStack } from "../lib/network-stack";

test("NetworkStack Validation Tests With Valid CidrMask", () => {
    const app = new cdk.App();
    const networkStack = new NetworkStack(app, "NetworkStack", { cidrMask: 24 });
    expect(networkStack).toHaveResource("AWS::EC2::Subnet", {
        CidrBlock: "10.0.0.0/24"
    });
});

test("NetworkStack Validation Tests With Invalid CidrMask", () => {
    const app = new cdk.App();
    expect(() => {
        new NetworkStack(app, "NetworkStack", { cidrMask: 32 });
    }).toThrowError("Valid values of cidrMask are 16--28")
});

NetworkStack に引数として渡すプロパティに対するバリデーションのテストです。
cidrMask が適切な場合にはそれに応じたサブネットが作られ、不適切な場合には例外を投げることを確かめています。

おわりに

今回書いた CDK は AWS でのみ利用可能なツールですが、プレイドでは AWS, GCP を併用したマルチクラウド構成が取られています5。CDK のマルチクラウド版ともいうべきツールとしては Pulumi が思いあたりますが、こちらは触れたことがありませんので機会があればまたいずれ。

参考リンク

  1. KARTEとその開発に用いられる技術スタックについてはPLAID Engineer Blogの こちらの記事 に詳しいです。

  2. CDK は Cloud Development Kit の頭文字です。Go CDK なるものも存在するようです。

  3. この構成について、詳細は https://it-akumi.hatenablog.com/entry/2019/08/26/204310 をご覧ください。

  4. このコマンドで実行できるよう、 package.jsonscripts を定義しています。

  5. https://codezine.jp/article/detail/10401

18
11
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
18
11

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?