txdbでデータベースを使ったテストをしやすくしよう![Go]

テストを書いている中で、特にデータベース依存のコードのテストをコードってどうやって書いてますか?

インメモリデータベースやトランザクションを使ってやるなどの方法がありますが、今回はtxdbというライブラリを使って、簡単にデータベース依存のコードを書く方法を紹介したいと思います。

# インメモリデータベースを使ったテストの記事
インメモリデータベースを使ってテストができる`go-sqlmock`の記事はこちらです。

https://qiita.com/maooz4426/items/b973490dd005e3d8e9de

# txdbとは

データベース接続と同時にトランザクションを張ることができ、接続を切るとロールバックしてくれるGoのライブラリです。

テストの際にモックデータを入れてそのデータをクリーンアップしたい時とかに便利なライブラリです。

https://github.com/DATA-DOG/go-txdb

# 使い方

## 環境
今回はORMライブラリとしてgormを採用しています。

:::note
### ORMとは
Object Relation Mappingの略
リレーショナルデータベースの操作をオブジェクト指向の考え方で行えるようにするという考え方です。
:::

なのでgorm使用した場合のスクリプトを書いていますが、他のORMライブラリでもそんなに変わらないと思います。


## セットアップ

txdbを使うために`go.mod`のあるディレクトリで下のコマンドを入力します。

```zsh
go get github.com/DATA-DOG/go-txdb
```

## 接続スクリプトを書く
txdbを使ってデータベースに接続することで、接続と同時にトランザクションを張ってくれるようになります。
接続スクリプトは下のようになっています。

```fake_test.go
type TestDB struct {
	DB *gorm.DB
}

var dsn string

func init() {
	user, password, host, port, dbName := "root", "root", "127.0.0.1", "3306", "report_generator_test"

	if os.Getenv("DB_USER") != "" {
		user = os.Getenv("DB_USER")
	}
	if os.Getenv("DB_PASSWORD") != "" {
		password = os.Getenv("DB_PASSWORD")
	}
	if os.Getenv("DB_HOST") != "" {
		host = os.Getenv("DB_HOST")
	}
	if os.Getenv("DB_PORT") != "" {
		port = os.Getenv("DB_PORT")
	}

	dsn = fmt.Sprintf("%s:%s@tcp(%s:%s)/%s?charset=utf8mb4&parseTime=true",
		user,
		password,
		host,
		port,
		dbName,
	)

	txdb.Register("txdb", "mysql", dsn)
}

func NewTestDB() (*TestDB, error) {

	var db *gorm.DB
	var err error
	dialector := mysql.New(mysql.Config{
		DriverName: "txdb",
		DSN:        strconv.FormatInt(time.Now().UnixNano(), 10),
	},
	)
	db, err = gorm.Open(dialector, &gorm.Config{})
	if err != nil {
		return nil, err
	}

	return &TestDB{DB: db}, nil

}
```

まず、init関数内でRegisterメソッドでtxdbのドライバーを登録します。

init関数はパッケージの初期化時に実行される関数です。

Registerメソッドでは、第一引数にtxdb用のドライバー名を設定します。第二引数にドライバー名を指定して、第3引数にdsn(DataSourceName)を指定します。

```go
txdb.Register(name, drv, dsn)
```

次に、データベース操作をするためのインスタンスを受け取るためのインスタンスメソッドを実装しています。
```go
func NewTestDB() (*TestDB, error) {
```

gormをデータベースに接続させるためにdialectorに接続情報を記述します。

先ほどtxdbように登録したデータベースドライバーを`txdb`と登録したので、`DriverName`にはRegisterメソッドで登録した名前を記述します。
`DSN`は、データベース接続に対する識別子として機能します。
時間をDSNとして登録することで効率的に識別子を生成することができます。

```go
dialector := mysql.New(mysql.Config{
	DriverName: "txdb",
	DSN:        strconv.FormatInt(time.Now().UnixNano(), 10),
},
```

dialectorを指定して、gormで操作するためのdbインスタンスを生成します。
```go
db, err = gorm.Open(dialector, &gorm.Config{})
```

こうすることでtxdbを使いつつ、gormでのデータベース操作ができるようになります。

## 切断スクリプトを抽象化する

txdbでは切断することでロールバックしてくれるので、挿入したデータをない状態に戻してくれます。

単純にgormでラッピングしているsqlパッケージのdbインスタンスを取り出し、Closeメソッドを使うことで切断できますが、データを消去するために使うにしては大分わかりづらいので、データ削除用のメソッドとして実装しようと思います。

```go
func (t *TestDB) TruncateTables() error {
	db, err := t.DB.DB() 
	if err != nil {
		return err
	}
	return db.Close()
}
```

sqlパッケージにあるDB型のインスタンスを取り出すために、DBメソッドを使用しています。
```go
db, err := t.DB.DB() 
```

そのインスタンスを使用してdb.Closeを実行してロールバックを行っています。
```go
return db.Close()
```

## テストを書いてみる

使い方の例として、簡略的なコードを書いてみます。

```go
func TestCreate(t *testing.T) {
	db, err := mysql.NewTestDB()
	assert.NoError(t, err)
	defer db.TruncateTables()

    r := repository.New(db.DB)

    // メソッドの呼び出し
	res, err := r.Create(
		ctx,
		input,
	)
}
```

先ほど実装したコードを使用してテストを行います。

まず、dbインスタンスをまず受け取ります。
```go
db, err := mysql.NewTestDB()
assert.NoError(t, err)
```

そしてテストケースが終了した後にロールバック(クリーンアップ)するように、deferで該当メソッドを指定します。
```go
defer db.TruncateTables()
```

あとはTestDB構造体の中にgormのインスタンスがあるので下のように取り出して、repository等データベース操作が実装されているパッケージのコンストラクタにDI(依存性注入)していくことで、テストをしていくことができます。

```go
r := repository.New(db.DB)
```

かなり簡略的に上のは書いていますが、DIするdbインスタンスを用意する際にtxDB向けにセットアップしたgormのインスタンスを生成してDIします。


# 最後に

txDBを使うことで大分データベース依存のテストケースが書きやすくなります。

ぜひ使ってみてください!

# Go関連の記事

https://qiita.com/maooz4426/items/e513a87a5282466c47b7

https://qiita.com/maooz4426/items/fa1aa9a1a8c8c3a10701

https://qiita.com/maooz4426/items/4e70baae712c9e19300e

https://qiita.com/maooz4426/items/b01f0502a2c1cdc914c9

https://qiita.com/maooz4426/items/49aef802e3a178ed5bd6

https://qiita.com/maooz4426/items/b973490dd005e3d8e9de

https://qiita.com/maooz4426/items/f6740347eec6df320a1e

https://qiita.com/maooz4426/items/44a71129ce2f0a463817

https://qiita.com/maooz4426/items/1ab385f3aceeb660e500

https://qiita.com/maooz4426/items/422a8d6428c2504efe23

https://qiita.com/maooz4426/items/592028f297ca76830c6b

https://qiita.com/maooz4426/items/63071aba6a69c9a55ef4

https://qiita.com/maooz4426/items/e1af6b5994e0c6c067d7

https://qiita.com/maooz4426/items/575b36ded6eadfda36b0

https://qiita.com/maooz4426/items/e1933175e492883db26d

https://qiita.com/maooz4426/items/a61bbc6c32a349ce71ee

https://qiita.com/maooz4426/items/91fa49341bc2e9d8eddd

https://qiita.com/maooz4426/items/2f9fe5e8d6b6a44b3d57

https://qiita.com/maooz4426/items/17cbad84213a222df694

https://qiita.com/maooz4426/items/698b0f8ee4c43b465d8c