はじめに
Golangのテスト方法については、第一話でも少し触れたが、実際に動かす際には、DynamoDB等のAWSサービスの本物に接続するか、モックが必要になったりして、あの内容だけでは実用部分に欠けていた。
今回は、DynamoDB local をモックとしてローカル環境で動かし、実際にそこに接続するための実装を整理する。
ベースとなるソースコードは、上記の第一話のソースコードにする。
DynamoDB local を起動する
毎度手動で起動してから go test
を実行するのは面倒なので、make test
の中でサクっと起動できるようにしてしまいたい。
以下のように docker-compose の力を借りて起動をしよう。
version: '3'
services:
dynamodb-local:
image: amazon/dynamodb-local
ports:
- "8000:8000"
main_test.go の変更点
dynamoDB に接続する箇所について以下のように変更する。
svc := dynamodb.New(sess, &aws.Config{Endpoint: aws.String("http://localhost:8000")})
どうせテストコードはテスト環境でしか動かさないので、固定してしまえば良いだろう。必要があれば、環境変数やプロパティから渡すようにしておこう。
また、以前の記事ではデータ敷き込み時にテーブルが既に作られていることを前提に書いていたが、今回は DynamoDB local は毎回起動時に真っ新になるので、以下のように setup() 関数内でテーブルを作っておく。
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 を停止させるなら不要な操作ではある。
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
されているときだけ、ローカルに接続し、そうでない場合はデフォルトのプロファイルでアクセスする。
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 のマクロを以下のようにする。
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 で起動するなら、特権モードをつけておく必要がある。
コンソールでは環境編集画面の以下のチェックボックスだ。
Terraform で作っている場合は、
resource "aws_codebuild_project" "test" {
(中略)
environment {
(中略)
privileged_mode = "true"
}
(後略)
}
だ。
CodeBuild で docker-compose するには、install
フェーズに以下を入れる。
install:
runtime-versions:
golang: 1.x
docker: 19
なぜか AWSの公式ドキュメントには記載がないが、githubのコンテナ定義(この例では ubuntu:4.0) の runtimes.yml には
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 に乗せたのだから、レポートを組み込むこともできるので有効活用してみよう。この場合、まだ 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 でコマンドを入れるのであれば、
install:
(中略)
commands:
- go get github.com/jstemmer/go-junit-report
- go get github.com/t-yuki/gocover-cobertura
(後略)
な感じで入れておく。
これで、ローカルでも CodeBuild でも回帰テストが回せるようになったぞ!
docker-compose が使えるので、minio や ElasticMQ や LocalStack も動かせるのでオススメ!