Golangはじめて物語(第6話: DynamoDB local＋testingでお手軽に回帰テストする)

# はじめに
Golangのテスト方法については、[第一話でも少し触れた](https://qiita.com/neruneruo/items/1ef52f908e4497103a59#%E3%83%86%E3%82%B9%E3%83%88%E3%83%97%E3%83%AD%E3%82%B0%E3%83%A9%E3%83%A0)が、実際に動かす際には、DynamoDB等のAWSサービスの本物に接続するか、モックが必要になったりして、あの内容だけでは実用部分に欠けていた。

今回は、DynamoDB local をモックとしてローカル環境で動かし、実際にそこに接続するための実装を整理する。

ベースとなるソースコードは、上記の第一話のソースコードにする。

# DynamoDB local を起動する
毎度手動で起動してから ```go test``` を実行するのは面倒なので、```make test``` の中でサクっと起動できるようにしてしまいたい。
以下のように docker-compose の力を借りて起動をしよう。

```YAML:docker-compose.yml
version: '3'
services:
  dynamodb-local:
    image: amazon/dynamodb-local
    ports:
      - "8000:8000"
```

# main_test.go の変更点
dynamoDB に接続する箇所について以下のように変更する。

```golang:main_test.go
	svc := dynamodb.New(sess, &aws.Config{Endpoint: aws.String("http://localhost:8000")})
```

どうせテストコードはテスト環境でしか動かさないので、固定してしまえば良いだろう。必要があれば、環境変数やプロパティから渡すようにしておこう。

また、以前の記事ではデータ敷き込み時にテーブルが既に作られていることを前提に書いていたが、今回は DynamoDB local は毎回起動時に真っ新になるので、以下のように setup() 関数内でテーブルを作っておく。

```golang:main_test.go
	input := &dynamodb.CreateTableInput{
		AttributeDefinitions: []*dynamodb.AttributeDefinition{
			{
				AttributeName: aws.String("id"),
				AttributeType: aws.String("S"),
			},
		},
		KeySchema: []*dynamodb.KeySchemaElement{
			{
				AttributeName: aws.String("id"),
				KeyType:       aws.String("HASH"),
			},
		},
		ProvisionedThroughput: &dynamodb.ProvisionedThroughput{
			ReadCapacityUnits:  aws.Int64(1),
			WriteCapacityUnits: aws.Int64(1),
		},
		TableName: aws.String("[テーブル名]"),
	}

	_, err := svc.CreateTable(input)
	if err != nil {
		return fmt.Errorf("setup() error: %w", err)
	}
```

気になるなら、以下のように teardown() 関数でテーブル削除をしておこう。
毎回 DynamoDB local を停止させるなら不要な操作ではある。

```golang:main_test.go
	input := &dynamodb.DeleteTableInput{
		TableName: aws.String("[テーブル名]"),
	}

	_, err := svc.DeleteTable(input)
	if err != nil {
		return fmt.Errorf("teardown() error: %w", err)
	}
```

# プロダクトコードの変更点
プロダクトコード側は、以下のようにして dynamoDB への接続を制御する。
起動時に環境変数が ```export MODE_UT=TRUE``` されているときだけ、ローカルに接続し、そうでない場合はデフォルトのプロファイルでアクセスする。

```golang:main_test.go
	svc := func(mode_ut string) *dynamodb.DynamoDB {
		if(mode_ut != "TRUE"){
			return dynamodb.New(sess)
		} else {
			return dynamodb.New(sess, &aws.Config{Endpoint: aws.String("http://localhost:8000")})
		}
	}(os.Getenv("MODE_UT"))
```

別に svc を切り出して普通に if で分岐しても良いのだけど、なんか var にあれこれ書くの格好悪くてな……。

# Makefileの変更点
makefile では test のマクロを以下のようにする。

```makefile:Makefile
test:
	docker-compose up -d; \
	export MODE_UT=TRUE; \
	go clean -testcache; \
	go test -v -coverprofile=./c.out ./...; \
	go tool cover -func=c.out; \
	docker-compose down; \
	rm ./c.out
.PHONY: test
```

```test clean -testcache``` はお好みで入れても入れなくても。
まあ、```test -v``` も好みで良い(入れないとOKのときは何もログが出なくなる)。
```-coverprocile``` することで、カバレッジ用の情報をダンプし、```go tool cover``` でその情報をもとに実行結果を出してくれる。

↓こんな感じだ。

```
ok      モジュール名     0.965s  coverage: 55.6% of statements
モジュール名/main.go:19: init            0.0%
モジュール名/main.go:22: main            0.0%
モジュール名/main.go:26: handler         57.7%
total:                  (statements)    55.6%
```

この方法は Docker があれば使えるので、ローカル環境でもできるだろうし、AWS の EC2 上で起動してローカル環境からアクセスするようにしても良いだろう。CodeBuild でもできるような気がするが、未検証。docker-compose をインストールするか、docker run で直接走らせるかの工夫は必要になるだろう。

# CodeBuild でも CI してみよう
さて、ローカルでの回帰テストができるようになったら、CI に組み込みたくなるのが世の常というもの。
Buildspec で DynamoDB Local を Docker で起動するなら、特権モードをつけておく必要がある。
コンソールでは環境編集画面の以下のチェックボックスだ。

![キャプチャ.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/111314/006d0607-9d2f-870b-fa40-fa680f8eb96f.png)

Terraform で作っている場合は、

```HCL
resource "aws_codebuild_project" "test" {
  (中略)
  environment {
    (中略)
    privileged_mode = "true"
  }
  (後略)
}
```

だ。

CodeBuild で docker-compose するには、```install``` フェーズに以下を入れる。

```YAML
  install:
    runtime-versions:
      golang: 1.x
      docker: 19
```

なぜか [AWSの公式ドキュメント](https://docs.aws.amazon.com/ja_jp/codebuild/latest/userguide/build-env-ref-available.html)には記載がないが、[githubのコンテナ定義(この例では ubuntu:4.0)](https://github.com/aws/aws-codebuild-docker-images/tree/master/ubuntu/standard/4.0) の runtimes.yml には

```YAML
  docker:
    versions:
      18:
        commands:
          - echo "Using Docker 19"
      19:
        commands:
          - echo "Using Docker 19"
```

の記述があるので使えるのである。2021/2/28 現在、ubuntu:5.0 の最新版イメージにはなぜか docker の定義がないため、将来的には使えなくなる可能性があることを留意しておく(AL2 の最新版イメージには入っている)。

CodeBuild 上でも make から docker-compose を起動することはできるので、ちゃんと makefile を書いておけば、Buildspec で頑張ることはほとんどないはずだ。

あとは、せっかく CodeBuild に乗せたのだから、[レポートを組み込む](https://qiita.com/neruneruo/items/c10c7983a586f2819bbe)こともできるので有効活用してみよう。この場合、まだ CodeBuild のレポート機能が Golang の標準的な出力に対応していないため、

```
	$(GOPATH)/bin/go-junit-report < ../report/testresult.log > ../report/report.xml; \
```

や

```
	$(GOPATH)/bin/gocover-cobertura < ../report/cover.out > ../report/coverage.xml; \
```

を、Buildspec なり makefile に入れておく必要がある(詳細はそれぞれググってもらえば情報はいろいろと集められる)。
Buildspec でコマンドを入れるのであれば、

```YAML
  install:
    (中略)
    commands:
      - go get github.com/jstemmer/go-junit-report
      - go get github.com/t-yuki/gocover-cobertura
    (後略)
```

な感じで入れておく。

これで、ローカルでも CodeBuild でも回帰テストが回せるようになったぞ！
docker-compose が使えるので、minio や ElasticMQ や LocalStack も動かせるのでオススメ！