🔒
dockertest のススメ
追記: 2024-07-20
Testcontainers を使いましょう。
概要
dockertest は go でテストを書く際に docker 経由で指定したコンテナを起動してくれてテストが終わったらコンテナを削除してくれる便利ライブラリです。
モチベーション
時雨堂では TimescaleDB という PostgreSQL に TSDB 拡張を追加した少し変わった RDBMS を利用しています。
TimescaleDB 専用の関数があったりするため、モックなどを使わずにテストを書くのが現実的です。
dockertest
基本的には dockertest の README に書いてある内容を整理した程度です。
実際に使っているコードを公開用に修正したものを共有します。
var q *db.Queries
func TestMain(m *testing.M) {
pool, err := dockertest.NewPool("")
// DB の初期化に失敗したときに早く気付きたいの短めに設定
pool.MaxWait = 10 * time.Second
if err != nil {
log.Fatalf("Could not connect to docker: %s", err)
}
pwd, _ := os.Getwd()
runOptions := &dockertest.RunOptions{
Repository: "timescale/timescaledb",
// latest だと本番とマッチしなくなる場合があるのでバージョン指定
Tag: "2.8.0-pg14",
// ポート番号は固定せずに 0 で listen する
Env: []string{
"POSTGRES_USER=postgres",
"POSTGRES_PASSWORD=password",
"POSTGRES_DB=defaultdb",
"listen_addresses='*'",
},
// ここでデータベースの初期化ファイルを渡す
Mounts: []string{
pwd + "/db/schema.sql:/docker-entrypoint-initdb.d/schema.sql",
},
}
resource, err := pool.RunWithOptions(runOptions,
func(config *docker.HostConfig) {
// 処理が終了したらインスタンスを削除する
config.AutoRemove = true
config.RestartPolicy = docker.RestartPolicy{
Name: "no",
}
},
)
if err != nil {
log.Fatalf("Could not start resource: %s", err)
}
hostAndPort := resource.GetHostPort("5432/tcp")
databaseUrl := fmt.Sprintf("postgres://postgres:password@%s/defaultdb?sslmode=disable", hostAndPort)
// docker が起動するまで少し時間がかかるのでリトライする
if err := pool.Retry(func() error {
config, err := pgxpool.ParseConfig(databaseUrl)
if err != nil {
return err
}
// pgx の pool 機能を利用してる
p, err := pgxpool.ConnectConfig(context.Background(), config)
if err != nil {
return err
}
// 一応 Ping 飛ばして動作確認をする
if err := p.Ping(context.Background()); err != nil {
return err
}
// sqlc の query を生成
q = db.New(p)
return nil
}); err != nil {
log.Fatalf("Could not connect to database: %s", err)
}
code := m.Run()
if err := pool.Purge(resource); err != nil {
log.Fatalf("Could not purge resource: %s", err)
}
os.Exit(code)
}
func TestGetSpam(t *testing.T) {
c := context.Background()
spamID, err := uuid.NewRandom()
assert.NoError(t, err)
spamName := "spam!spam!spam!"
spam, err := q.CreateSpam(c, db.CreateSpam{
ID: spamID,
Name: spamName,
})
assert.NoError(t, err)
assert.Equal(t, spamID, spam.ID)
assert.Equal(t, spamName, spam.Name)
}
本当に気軽に使えるので良さそうと思った方は是非使ってみてください。
Discussion