最近趣味で、GAE/Go の Standard Environment で動くような Web アプリっぽいものを書いている。
今回はなんかテストも書いてみようという気持ちになったので書いてみているのだけど、ほどなくタイトルの問題に突き当たった。
datastore が絡むテストの書き方
datastore が絡むテストの書き方については、基本的に以下の記事に書いてあるとおり(だと思ってる)んだけど、
今回の僕のケースだと http.Request
を使うテストが書きたかったので、以下のような書き方になる。
opt := aetest.Options{StronglyConsistentDatastore: true} instance, err := aetest.NewInstance(&opt) if err != nil { t.Fatalf("Failed to create aetest instance: %v", err) } defer instance.Close()
こうしたコードを書くと、このコードごとに裏で appengine の devserver が localhost で起動するのでこれだけで数秒かかる。そしてこれはたいてい全てのテストケースで必要になるので、愚直にこれを書いていくと、全テストケースの実行にめちゃくちゃ時間がかかってしまう。つらい。
favclip/testerator を使って devserver インスタンスを使い回す
上記のような問題に対して、いいライブラリがあった。
教えてくれた @serinuntius さん、ありがとうございます!
https://t.co/0lVJqWC9j5
— serinuntius@no plan inc CTO (@_serinuntius) 2018年9月9日
これじゃないですか?
これを使えば、起動した devserver のインスタンスを持ち回ることができるようになる。つまり、テスト起動時に一度だけインスタンスを起動し、全部のテストケースの実行が終わったらインスタンスをシャットダウンできればよい。
普通にテストを書いた場合、各テストケースは func TestXxx(t *testing.T)
みたいな感じで書いてると思うんだけど、それとは別に以下のような TestMain(*testing.M)
を main_test.go
などで用意するこで、「テスト起動時に一度だけインスタンスを起動し、全部のテストケースの実行が終わったらインスタンスをシャットダウンする」が実現できる。
これで go test
すると、まずこの TestMain
が呼ばれるようになるので、その中で m.Run()
を呼び出すことで各テストケースを実行することができる。 m.Run()
の前後に、テスト全体で必要な事前/事後処理を入れてやればいい、というわけ。これについては以下の記事が参考になった。
いままで aetest.NewInstance(&opt)
とかって書いてた各テストケースの方も直さなくちゃいけなくて、それは以下のようなコードで置き換えればよさそう。
instance, _, err := testerator.SpinUp() if err != nil { t.Fatal(err.Error()) } defer testerator.SpinDown()
各テストケースで testerator.SpinDown()
呼んでもええんか?と思ったんだけどなぜか大丈夫っぽい(むしろ呼ばないと devserver のプロセスが生きたままどんどん残っていく)。理由までは追ってない......すんません。
参考情報
ここで書いたことほぼそのまま以下の Qiita 記事にまとまってたのだけど、今回独力ではこのページに辿り着けなかったこともあり、援護射撃(?)的な意味でこのエントリを書きました。
テスト周り、最初は億劫なんだけど書き始めると楽しいので、今後も頑張っていきたい......!
テスト、書き始めるまでがちょっと億劫だったりするかもだけど書き始めたらむしろ楽しいんだよね
— a-know | Daisuke Inoue (@a_know) 2018年9月2日