🧪

GolangのAWS CDKでスナップショットテスト

2022/03/01に公開

はじめに

AWS CDKの利用時にスナップショットテストを導入していますか?
AWS CDKではスナップショットテストが簡単に導入でき、スナップショットテストを導入するとバージョンアップによるスタックへの思わぬ影響を事前に察知することができます。

そんなAWS CDKのスナップショットテストを今回はGolangで実践してみます。
まずv1でAWS CDKプロジェクトを作成し、スナップショットテストを導入した後にv2にバージョンアップさせてスナップショットテストが効いているかを確認していきます。

環境

名称 バージョン
go 1.17.7
node 16.14.0
aws-cdk 1.147.0 ⇒ 2.15.0

このdevcontainer内で実施します。

.devcontainerを確認する
.devcontainer.json
// For format details, see https://aka.ms/devcontainer.json. For config options, see the README at:
// https://github.com/microsoft/vscode-dev-containers/tree/v0.217.4/containers/go
{
	"name": "Go",
	"build": {
		"dockerfile": "Dockerfile",
		"args": {
			// Update the VARIANT arg to pick a version of Go: 1, 1.16, 1.17
			// Append -bullseye or -buster to pin to an OS version.
			// Use -bullseye variants on local arm64/Apple Silicon.
			"VARIANT": "1-bullseye",
			// Options
			"NODE_VERSION": "lts/*"
		}
	},
	"runArgs": [ "--cap-add=SYS_PTRACE", "--security-opt", "seccomp=unconfined" ],

	// Set *default* container specific settings.json values on container create.
	"settings": {
		"go.toolsManagement.checkForUpdates": "local",
		"go.useLanguageServer": true,
		"go.gopath": "/go",
		"go.goroot": "/usr/local/go"
	},

	// Add the IDs of extensions you want installed when the container is created.
	"extensions": [
		"golang.Go"
	],

	// Use 'forwardPorts' to make a list of ports inside the container available locally.
	// "forwardPorts": [],

	// Use 'postCreateCommand' to run commands after the container is created.
	// "postCreateCommand": "go version",

	// Comment out to connect as root instead. More info: https://aka.ms/vscode-remote/containers/non-root.
	"remoteUser": "vscode",
	"features": {
		"azure-cli": "latest"
	}
}
.devcontainer/Dockerfile
# See here for image contents: https://github.com/microsoft/vscode-dev-containers/tree/v0.217.4/containers/go/.devcontainer/base.Dockerfile

# [Choice] Go version (use -bullseye variants on local arm64/Apple Silicon): 1, 1.16, 1.17, 1-bullseye, 1.16-bullseye, 1.17-bullseye, 1-buster, 1.16-buster, 1.17-buster
ARG VARIANT="1.17-bullseye"
FROM mcr.microsoft.com/vscode/devcontainers/go:0-${VARIANT}

# [Choice] Node.js version: none, lts/*, 16, 14, 12, 10
ARG NODE_VERSION="none"
RUN if [ "${NODE_VERSION}" != "none" ]; then su vscode -c "umask 0002 && . /usr/local/share/nvm/nvm.sh && nvm install ${NODE_VERSION} 2>&1"; fi

# [Optional] Uncomment this section to install additional OS packages.
# RUN apt-get update && export DEBIAN_FRONTEND=noninteractive \
#     && apt-get -y install --no-install-recommends <your-package-list-here>

# [Optional] Uncomment the next lines to use go get to install anything else you need
# USER vscode
# RUN go get -x <your-dependency-or-tool>

# [Optional] Uncomment this line to install global node packages.
# RUN su vscode -c "source /usr/local/share/nvm/nvm.sh && npm install -g <your-package-here>" 2>&1

AWS CDKプロジェクトを作成

まずはAWS CDK v1でプロジェクトを作成するため、aws-cdk v1をインストールします。

terminal
$ yarn global add aws-cdk@1
yarn global v1.22.17
[1/4] Resolving packages...
[2/4] Fetching packages...
[3/4] Linking dependencies...
[4/4] Building fresh packages...

success Installed "aws-cdk@1.147.0" with binaries:
      - cdk
Done in 2.02s.
terminal
$ cdk --version
1.147.0 (build f9b35cf)

languageにgoを指定してCDKアプリを初期化します。

terminal
$ cdk init app --language go

すぐに初期状態のアプリが作成されます。
初期状態だとSQSを作成する参考用コードがコメントアウトされています。今回はこれを使おうと思うのでコメントアウトを解除します。

example.go
package main

import (
	"github.com/aws/aws-cdk-go/awscdk"
	"github.com/aws/aws-cdk-go/awscdk/awssqs"
	"github.com/aws/jsii-runtime-go"

	"github.com/aws/constructs-go/constructs/v3"
)

type ExampleStackProps struct {
	awscdk.StackProps
}

func NewExampleStack(scope constructs.Construct, id string, props *ExampleStackProps) awscdk.Stack {
	var sprops awscdk.StackProps
	if props != nil {
		sprops = props.StackProps
	}
	stack := awscdk.NewStack(scope, &id, &sprops)

	// The code that defines your stack goes here

	// example resource
	awssqs.NewQueue(stack, jsii.String("ExampleQueue"), &awssqs.QueueProps{
		VisibilityTimeout: awscdk.Duration_Seconds(jsii.Number(300)),
	})

	return stack
}

func main() {
	app := awscdk.NewApp(nil)

	NewExampleStack(app, "ExampleStack", &ExampleStackProps{
		awscdk.StackProps{
			Env: env(),
		},
	})

	app.Synth(nil)
}

// env determines the AWS environment (account+region) in which our stack is to
// be deployed. For more information see: https://docs.aws.amazon.com/cdk/latest/guide/environments.html
func env() *awscdk.Environment {
	// 省略
}

AWS CDKのassertionsパッケージは各言語の一般的なテストインターフェースを利用できるところが素晴らしいです。これを使用するためテストコードも同様にコメントアウトを解除します。

example_test.go
package main

import (
	"testing"

	"github.com/aws/aws-cdk-go/awscdk"
	"github.com/aws/aws-cdk-go/awscdk/assertions"
	"github.com/aws/jsii-runtime-go"
	"github.com/bradleyjkemp/cupaloy/v2"
)

// example tests. To run these tests, uncomment this file along with the
// example resource in example.go
func TestExampleStack(t *testing.T) {
	// GIVEN
	app := awscdk.NewApp(nil)

	// WHEN
	stack := NewExampleStack(app, "MyStack", nil)

	// THEN
	template := assertions.Template_FromStack(stack)

	template.HasResourceProperties(jsii.String("AWS::SQS::Queue"), map[string]interface{}{
		"VisibilityTimeout": 300,
	})
}

現時点でテストが成功することも確認しておきましょう。

terminal
$ go test ./...
ok      example 3.263s

スナップショットテストを導入する

golangのtestingパッケージはスナップショットテストの仕組みがありません。独自に仕組みを作成することも可能ですが、ここはcupaloyを利用しますのでインストールします。

terminal
$ go get github.com/bradleyjkemp/cupaloy/v2
go: downloading github.com/bradleyjkemp/cupaloy/v2 v2.7.0
go: downloading github.com/bradleyjkemp/cupaloy v2.3.0+incompatible
go: downloading github.com/davecgh/go-spew v1.1.1
go: downloading github.com/pmezard/go-difflib v1.0.0
go get: added github.com/bradleyjkemp/cupaloy/v2 v2.7.0

既存のテストファイルにスナップショットテストを追記します。

example_test.go
func TestExampleStackSnapshot(t *testing.T) {
	// GIVEN
	app := awscdk.NewApp(nil)

	// WHEN
	stack := NewExampleStack(app, "MyStack", nil)

	// THEN
	template := assertions.Template_FromStack(stack)

	cupaloy.SnapshotT(t, template.ToJSON())
}

テストを実行してみます。

terminal
$ go test ./...
--- FAIL: TestExampleStackSnapshot (0.01s)
    example_test.go:39: snapshot created for test TestExampleStackSnapshot, with contents:
        (*map[string]interface {})((len=1) {
          (string) (len=9) "Resources": (map[string]interface {}) (len=1) {
            (string) (len=20) "ExampleQueue4CEF454C": (map[string]interface {}) (len=4) {
              (string) (len=14) "DeletionPolicy": (string) (len=6) "Delete",
              (string) (len=10) "Properties": (map[string]interface {}) (len=1) {
                (string) (len=17) "VisibilityTimeout": (float64) 300
              },
              (string) (len=4) "Type": (string) (len=15) "AWS::SQS::Queue",
              (string) (len=19) "UpdateReplacePolicy": (string) (len=6) "Delete"
            }
          }
        })
        
FAIL
FAIL    example 3.240s
FAIL

初回のテスト実行は必ず失敗します。代わりにこの時.snapshotsというフォルダにテスト関数の名前でスナップショットの内容が保存されます。

スナップショットが取れたところでもう一度テストを実行してみます。

terminal
$ go test ./...
ok      example 3.184s

今度はスナップショットがあるため成功しました。

AWS CDKのバージョンを上げる

テストが失敗することを確認するためにAWS CDKのバージョンを上げます。

terminal
$ npm install -g aws-cdk
terminal
$ cdk --version
2.15.0 (build 151055e)

goパッケージのAWS CDKバージョンも上げます。

terminal
$ go get github.com/aws/aws-cdk-go/awscdk/v2
go: downloading github.com/aws/aws-cdk-go v0.0.0-20220301071257-001e6dc3514a
go: downloading github.com/aws/aws-cdk-go/awscdk/v2 v2.15.0
go: downloading github.com/aws/constructs-go/constructs/v10 v10.0.9
go get: added github.com/aws/aws-cdk-go/awscdk/v2 v2.15.0
go get: added github.com/aws/constructs-go/constructs/v10 v10.0.9

パッケージの参照も更新します。

example.go
import (
-	"github.com/aws/aws-cdk-go/awscdk"
-	"github.com/aws/aws-cdk-go/awscdk/awssqs"
+	"github.com/aws/aws-cdk-go/awscdk/v2"
+	"github.com/aws/aws-cdk-go/awscdk/v2/awssqs"
	"github.com/aws/jsii-runtime-go"

-	"github.com/aws/constructs-go/constructs/v3"
+	"github.com/aws/constructs-go/constructs/v10"
)
example_test.go
import (
	"testing"

-	"github.com/aws/aws-cdk-go/awscdk"
-	"github.com/aws/aws-cdk-go/awscdk/assertions"
+	"github.com/aws/aws-cdk-go/awscdk/v2"
+	"github.com/aws/aws-cdk-go/awscdk/v2/assertions"
	"github.com/aws/jsii-runtime-go"
	"github.com/bradleyjkemp/cupaloy/v2"
)

再度テスト

テストを実施してみます。

terminal
$ go test ./...
--- FAIL: TestExampleStackSnapshot (0.01s)
    example_test.go:39: snapshot not equal:
        --- Previous
        +++ Current
        @@ -1,2 +1,9 @@

-	(*map[string]interface {})((len=1) {
+	(*map[string]interface {})((len=3) {
+	  (string) (len=10) "Parameters": (map[string]interface {}) (len=1) {
+	    (string) (len=16) "BootstrapVersion": (map[string]interface {}) (len=3) {
+	      (string) (len=7) "Default": (string) (len=32) "/cdk-bootstrap/hnb659fds/version",
+	      (string) (len=11) "Description": (string) (len=120) "Version of the CDK Bootstrap resources in this environment, automatically retrieved from SSM Parameter Store. [cdk:skip]",
+	      (string) (len=4) "Type": (string) (len=34) "AWS::SSM::Parameter::Value<String>"
+	    }
+	  },
 	   (string) (len=9) "Resources": (map[string]interface {}) (len=1) {
	@@ -10,2 +17,29 @@
 	       }
+	  },
+	  (string) (len=5) "Rules": (map[string]interface {}) (len=1) {
+	    (string) (len=21) "CheckBootstrapVersion": (map[string]interface {}) (len=1) {
+	      (string) (len=10) "Assertions": ([]interface {}) (len=1) {
+	        (map[string]interface {}) (len=2) {
+	          (string) (len=6) "Assert": (map[string]interface {}) (len=1) {
+	            (string) (len=7) "Fn::Not": ([]interface {}) (len=1) {
+	              (map[string]interface {}) (len=1) {
+	                (string) (len=12) "Fn::Contains": ([]interface {}) (len=2) {
+	                  ([]interface {}) (len=5) {
+	                    (string) (len=1) "1",
+	                    (string) (len=1) "2",
+	                    (string) (len=1) "3",
+	                    (string) (len=1) "4",
+	                    (string) (len=1) "5"
+	                  },
+	                  (map[string]interface {}) (len=1) {
+	                    (string) (len=3) "Ref": (string) (len=16) "BootstrapVersion"
+	                  }
+	                }
+	              }
+	            }
+	          },
+	          (string) (len=17) "AssertDescription": (string) (len=104) "CDK bootstrap stack version 6 required. Please run 'cdk bootstrap' with a recent version of the CDK CLI."
+	        }
+	      }
+	    }
    }
        
FAIL
FAIL    example 2.704s
FAIL

予想通り失敗しました。スタックに含められるCDKの情報が増えたようですね。
意図している内容のためこれを正としてスナップショットを更新しましょう。

terminal
$ UPDATE_SNAPSHOTS=true go test ./...

この時は先ほどと同じようにテストに失敗しますが、スナップショットが更新されます。

もう一度実行してみましょう。

terminal
$ go test ./...
ok      example 2.744s

うまくいきました。

さいごに

golangでも簡単にAWS CDKのスナップショットテストを導入できました。AWS CDKを利用する際は安心してバージョンアップをするために必ず入れてほしいテストです。
なお、あくまでスナップショットテストは変更検知させるための仕組みであって、生成されたテンプレートが期待する構成になっているかどうかを検証するテストではありません。それにはデフォルトで生成されていたようなテンプレートを検証する単体テストを導入しましょう。

Discussion