Go GolangでDBアクセスがあるユニットテストのやり方を考える - Qiita SQLが実行される箇所をmockする(実際にDBに接続してSQLの結果を得る必要がない場合) sqlmock を使う 実際のDBとテストデータを用意してSQLも実際に実行する テスト用DBの用意 https://github.com/DATA-DOG/go-txdb でテストケースごとに独立したトランザクション内でテストデータを用意・Rollbackを行う https://zenn.dev/rinchsan/articles/83cf6f3b5d70c4d9b5d4 mysql だとnested transactionが使えないからコミットしている場合、Savepointにかえるとかしないと、rollbackできなさそう…? 毎回deleteする、でもよくない? https://github.com/go-testfixtures/testfixtures でfixtureを用意する https://github.com/testcontainers/testcontainers-go や https://github.com/ory/dockertest を利用する https://qiita.com/yasuflatland-lf/items/4f18b55c2a6492d0c462 Makefileでdocker-compose up してもいいけど、テストコードで書いておくとSkipも可 CIではスキップしたい func skipCI(t *testing.T) { if os.Getenv("CI") != "" { t.Skip("Skipping testing in CI environment") } } func TestNewFeature(t *testing.T) { skipCI(t) } あるいはshortではスキップにする go test -short if testing.Short() { t.Skip("skipping testing in short mode") } dockertestとfixtureを使ったテスト dockertest を使ってコンテナを起動し、testfixtures でテストデータを流し込む package db_test import ( "database/sql" "fmt" "os" "testing" "time" "github.com/go-testfixtures/testfixtures/v3" "github.com/ory/dockertest/v3" ) var ( pool *dockertest.Pool resource *dockertest.Resource db *sql.DB fixtures *testfixtures.Loader ) func prepareTestDatabase() error { var err error pool, err = dockertest.NewPool("") // コンテナの起動に時間がかかるため pool.MaxWait = 2 * time.Minute if err != nil { return err } basepath, _ := os.Getwd() user := "user" // DB名に test が入っていないと、fixtureのload時にエラーになる (https://github.com/go-testfixtures/testfixtures#security-check) dbName := "test_db" // Dockerコンテナ起動時の細かいオプションを指定する runOptions := &dockertest.RunOptions{ Repository: "mysql", Tag: "5.7", Env: []string{ fmt.Sprintf("MYSQL_DATABASE=%s", dbName), fmt.Sprintf("MYSQL_USER=%s", user), fmt.Sprintf("MYSQL_PASSWORD=%s", user), "MYSQL_ROOT_PASSWORD=secret", "TZ=Asia/Tokyo", }, Mounts: []string{ basepath + "/etc/01_create_table.sql:/docker-entrypoint-initdb.d/01_create_table.sql", }, Cmd: []string{ "mysqld", "--character-set-server=utf8", "--collation-server=utf8_unicode_ci", }, } // imageをpullして起動する resource, err = pool.RunWithOptions(runOptions) if err != nil { return err } // コンテナが起動されたことを確認するためのコマンドを定義 if err := pool.Retry(func() error { time.Sleep(10 * time.Second) err := connectDb(resource.GetPort("3306/tcp")) if err != nil { return err } return db.Ping() }); err != nil { return err } return nil } func connectDb(port string) error { dsn := fmt.Sprintf("%s:%s@tcp(localhost:%s)/%s?parseTime=true&loc=Asia2FTokyo", user, user, port, dbName, ) dbURL := func(port nat.Port) string { return dsn } execError := compose. WaitForService("db", wait.ForSQL(nat.Port(port), "mysql", dbURL)). WithCommand([]string{"up", "-d"}). Invoke() err := execError.Error if err != nil { return fmt.Errorf("Could not run compose file: %v - %v", composeFilePaths, err) } db, err = sql.Open("mysql", dsn) if err != nil { return err } return db.Ping() } func TestSample(t *testing.T) { // テストデータ準備 start if testing.Short() { t.Skip("skip integration test") } var err error err = prepareContainer(t) if err != nil { t.Fatal(err) } defer func() { if err := compose.Down().Error; err != nil { t.Fatalf("Could not purge resource: %v", err) } }() // テストデータ準備 end // ... test }