DB接続を含むテストはツライ
- テスト用のデータをテストケースごとに用意しないといけない。
- DBの変更結果が他のテストケースに影響を与えないようにリセットしないといけない。
- DBへの変更が他のテストケースに影響を与えるので並列実行できない。
DATA-DOG/go-txdbを使うと改善できる
-
DATA-DOG/go-txdb で生成することのできるDBコネクションには↓のような特徴があります。
-
sql.DB
と互換性がある。 - すべてのクエリが独立したトランザクション内で実行される。
-
.Close()
を呼ぶとそのトランザクション内で実行されたクエリがすべてRollbackされる。
-
- これをうまく使うと、テストケースごとに独立したトランザクション内でクエリを実行することができ、テスト終了後にDB変更がRollbackされるので、テストケースごとのデータ処理が必要なくなり、他のテストケースへの影響もなくなるのでテストを並列実行することができます。
- 実際のサンプルコードは こちら に置いています。
軽い解説
pkg/dao/dao_test.go
- 今回はDB接続を含むコードを
dao
パッケージに置きます。 -
TestMain
を定義して、テストに必要なデータを go-testfixtures/testfixtures を使って挿入します。-
testfixtures
は平行テストに対応していないことがREADMEにも書いてありますが、今回はTestMain
でしか呼ばれないので問題ありません。
-
- 後のテストで
txdb
を使うためにtxdb.Register
を呼んでおきます。
func TestMain(m *testing.M) {
prepare()
txdb.Register("txdb", "mysql", config.DB.DSN)
code := m.Run()
os.Exit(code)
}
func prepare() {
db, err := sql.Open(config.DB.Driver, config.DB.DSN)
if err != nil {
panic(err)
}
defer db.Close()
fixtures, err := testfixtures.New(
testfixtures.Database(db),
testfixtures.Dialect("mysql"),
testfixtures.Directory("/go/src/github.com/rinchsan/txdb-todo/testdata/fixtures"),
)
if err != nil {
panic(err)
}
if err := fixtures.Load(); err != nil {
panic(err)
}
}
pkg/dao/user_test.go
- ユーザ追加のテストを例に取ります。
- テスト対象のコードやDBのスキーマなどの詳細はGitHubのほうを見てください。
-
sql.Open
に渡すDriverに"txdb"
を指定して取得したコネクションを使ってテストをしていきます。-
sql.Open
の第2引数に渡す文字列ごとに独立したトランザクションを作成してくれます。
-
-
defer
で呼んでいるdb.Close()
によってテストケースごとにDBの変更がRollbackされています。 -
t.Parallel()
を利用してテストを平行に走らせることも可能です。 - ちなみに今回の例では環境変数を利用することで、ローカル開発用に使っているデータベースとは別のものを使ってテストを行うようにしていますので、詳しくは
Makefile
のtest
を見てみてください。
func TestUserImpl_Add(t *testing.T) {
t.Parallel()
cases := map[string]struct {
user *entity.User
noErr bool
}{
"new user": {
user: &entity.User{Username: "rinchsan"},
noErr: true,
},
"duplicate username": {
user: &entity.User{Username: "John"},
noErr: true,
},
"empty username": {
user: &entity.User{Username: ""},
noErr: true,
},
}
for name, c := range cases {
c := c
t.Run(name, func(t *testing.T) {
t.Parallel()
db, err := sql.Open("txdb", uuid.New().String())
assert.NoError(t, err)
defer db.Close()
impl := dao.NewUser(db)
err = impl.Add(context.Background(), c.user)
if c.noErr {
assert.NoError(t, err)
} else {
assert.Error(t, err)
}
})
}
}
感想
ビジネスロジックのテストをgomockとかを使って書くと、今回のtxdbと合わせて結構いい感じにプロジェクト全体のテストが書けそうです。