Goのテストでseedingする

Gopher道場 [Advent Calendar](https://qiita.com/advent-calendar/2018/gopherdojo) 16日目のエントリです。
昨日は @kaznishi さんによる[[豆知識]Bounds Check Elimination](https://blog.kaznishi.com/post/181215_bounds_check_elimination/)でした。

Database周りの実装はテストでモックせずに、ちゃんとDBを使ってseedingしてテストしたくなります。
[rubenv/sql-migrate](https://github.com/rubenv/sql-migrate)を使って、migrationとseedingをいい感じに共存させる方法をご紹介します。

ちなみにdatastoreの場合は[timakin/dsmock](https://github.com/timakin/dsmock)が便利です。

ディレクトリ構造
---

まずmigrationとseedingをディレクトリ別々で作り、SQLを書いていきます。
他の言語のフレームワークを触ってるとよく出会う構成ですね。

```shell-session
./sql
├── migrations
│   └── 1.sql
└── testdata
    └── 1_test.sql
```

migrationをしたいときは`migrations`だけを、migrationとseedingを両方やるときは両方のディレクトリをがっちゃんこすればいい、という考え方です。

実装
---

Go製のmigrationツールはいくつかありますが、[rubenv/sql-migrate](https://github.com/rubenv/sql-migrate)を選んだ理由はmigrationファイルを柔軟にわたすことができるからです。

こちらがmigrationを実行する関数です。

```go
// Execute a set of migrations
//
// Returns the number of applied migrations.
func Exec(db *sql.DB, dialect string, m MigrationSource, dir MigrationDirection) (int, error) {
	return ExecMax(db, dialect, m, dir, 0)
}
```

`MigrationSource`はInterfaceとなっていて、`FindMigrations()`が実装されていればよい。

```go
type MigrationSource interface {
	// Finds the migrations.
	//
	// The resulting slice of migrations should be sorted by Id.
	FindMigrations() ([]*Migration, error)
}
```

ちなみに[rubenv/sql-migrate](https://github.com/rubenv/sql-migrate)が標準で用意している`FileMigrationSource`、`HttpFileSystemMigrationSource`、`MemoryMigrationSource`などのstructたちは`FindMigrations()`の振る舞いを持っています。

https://godoc.org/github.com/rubenv/sql-migrate#pkg-index

ということは、複数の`FindMigrations()`をがっちゃんこする`FindMigrations()`を実装すれば、複数ディレクトリを指定できるはず・・・！

```go
type migrationSources struct {
	sources []migrate.MigrationSource
}

func (mss *migrationSources) FindMigrations() ([]*migrate.Migration, error) {
	var migrations []*migrate.Migration

	for _, s := range mss.sources {
		ms, err := s.FindMigrations()
		if err != nil {
			return nil, err
		}
		migrations = append(migrations, ms...)
	}

	return migrations, nil
}
```

`migrationSources`に`./sql/migrations`と`./sql/testdata`が合わさってmigrationが走ります。

```go
	mc := &migrationSources{
		sources: []migrate.MigrationSource{
			&migrate.FileMigrationSource{
				Dir: "./sql/migrations",
			},
			&migrate.FileMigrationSource{
				Dir: "./sql/testdata",
			},
		},
	}

	_, err = migrate.Exec(db, "postgres", mc, migrate.Up)
```

これをdatabaseを使うテストに挟んであげると、ケースごとにまっさらな状態にテストできます。
参照系のテストであれば、ケースごとではなくていいと思います。

```go
	for name, c := range cases {
		t.Run(name, func(t *testing.T) {
			migrateUp(t)
			defer migrateDown(t)

			// ...
		})
	}
```

デメリット
---

DBのmigrationテーブルを見てみると、本来入るべきではないseedingのファイルが入ってしまいます。
が、テスト時だけなら、毎回、migrationをゼロから実行するので問題はなさそうです。

```
SELECT * FROM gorp_migrations;
     id     |          applied_at
------------+-------------------------------
 1.sql      | 2018-12-12 14:00:05.608612+00
 1_test.sql | 2018-12-12 14:00:05.610527+00
```

まとめ
---

[rubenv/sql-migrate](https://github.com/rubenv/sql-migrate)はmigrationライブラリながらも工夫をすれば、テスト時のseedingにも使える。ただし、seedingのファイル名もmigrationテーブルに記録されてしまうので、アプリケーションを動かすときのseedingには向かない。

サンプルコードはこちら
https://github.com/sawadashota/sql-test-sample

明日は @mizkei さんのエントリになります。お楽しみに！