Goでテスト用Dockerコンテナ(MySQL, Redis)などを動かす

# はじめに
Goで書いたアプリケーションのテスト時に、データベースへの接続や、キャッシュへの接続などが必要になることが多いかと思います。その際に手軽にテスト用データベースやキャッシュなどをを立ち上げられる[testcontainer-go](https://github.com/testcontainers/testcontainers-go)というツールを利用してみて、非常に簡単に利用することができました。どなたかのお役に立てたら幸いです。

動作するコードは以下のリポジトリに置いてあります。
[yasuflatland-lf/go-cloudrun-boilerplate](https://github.com/yasuflatland-lf/go-cloudrun-boilerplate)

# 動作環境
- Mac 11.2.3 (BigSur)
- Go 1.15.8
- Docker Desktop for Mac (3.2.1)
- Git 2.23.0

# 実行方法
1. [yasuflatland-lf/go-cloudrun-boilerplate](https://github.com/yasuflatland-lf/go-cloudrun-boilerplate)を`git clone`する。
1. `go mod tidy`でモジュール群をロード。
1. `go test`を実行

# 解説
テストコンテナを呼び出しているのがこのファンクションの中になります。

```golang
func initMySQLContainer() (string, func()) {
	ctx := context.Background()
	username := "root"
	password := "password"
	seedDataPath, err := os.Getwd()
	mysqlPort, _ := nat.NewPort("tcp", "3306")
	req := testcontainers.ContainerRequest{
		Image:        "mysql:5.7",
		ExposedPorts: []string{"3306/tcp"},
		Env: map[string]string{
			"MYSQL_ROOT_PASSWORD": "password",
		},
		BindMounts: map[string]string{
			seedDataPath + "/test/db/mysql_init": "/docker-entrypoint-initdb.d",
			seedDataPath + "/test/db/my.cnf":     "/etc/mysql/conf.d/my.cnf",
		},
		WaitingFor: wait.ForListeningPort(mysqlPort),
	}
	mysqlC, err := testcontainers.GenericContainer(ctx, testcontainers.GenericContainerRequest{
		ContainerRequest: req,
		Started:          true,
	})
	if err != nil {
		panic(err)
	}

	ip, err := mysqlC.Host(ctx)
	if err != nil {
		panic(err)
	}

	port, err := mysqlC.MappedPort(ctx, "3306")
	if err != nil {
		panic(err)
	}

	// cloudSQL service fetch MySQL connection data from environment valuables.
	// Set here dummy server information for test purpose.
	os.Setenv("DB_NAME", "test")
	os.Setenv("DB_USERNAME", username)
	os.Setenv("DB_PASSWORD", password)
	os.Setenv("DB_IP", ip)
	os.Setenv("DB_PORT", port.Port())

	dataSourceName := fmt.Sprintf("%s:%s@tcp(%s:%d)/test", username, password, ip, port.Int())
	fmt.Println(dataSourceName)
	cTerm := func() {
		defer mysqlC.Terminate(ctx)
	}

	return dataSourceName, cTerm
}
```

[testcontainer-go](https://github.com/testcontainers/testcontainers-go)公式を見てもらうとわかるように、MySQLだけでなくさまざまなコンテナを起動することができます。大体上記コードを見てもらうと、どこにパスワードを設定して、みたいな部分はわかると思いますが、いくつかポイントを説明してみます。

## 初期データのロードとMySQLの初期設定
以下の部分でコンテナのファイルやディレクトリへマウントを行っています。

```golang
BindMounts: map[string]string{
    seedDataPath + "/test/db/mysql_init": "/docker-entrypoint-initdb.d",
    seedDataPath + "/test/db/my.cnf":     "/etc/mysql/conf.d/my.cnf",
},
```

### /test/db/mysql_init
初期データのロード（データベース作成、テーブル作成、レコード挿入）をしています。MySQLのコンテナの説明を読むと、`/docker-entrypoint-initdb.d`の下に配置されたファイルを初期化時に読み取る、ということで、ここに初期化設定ファイルを置いています。

### /test/db/my.cnf
MySQLの設定ファイルをマウントしています。ここにMySQLの設定を書きます。筆者のテスト環境ではutf8mb4の設定が必要(絵文字などを正しく表示するにはこのキャラクタセットが必要）だったので、その設定が書かれています。

## IP、Portデータの取得
`testcontainer-go`のインスタンスを起動すると、コンテナは`3306`で起動しますが、ホスト側にランダムなポートを渡してきます。それを取得して、今回は環境変数に入れて、それをデータベースアクセス側のファンクションで取り出して利用するというようにしています。

```golang
ip, err := mysqlC.Host(ctx)
if err != nil {
    panic(err)
}

port, err := mysqlC.MappedPort(ctx, "3306")
if err != nil {
    panic(err)
}
```


# あとがき
もともとJavaのアプリケーション作成時にtestcontainerを利用していて、Goでも同じものがないかなと探していて見つけました。近年はある言語で使えるライブラリやツールが、他の言語にもポートされている、というようなことが多くあるように感じます。