えいのうにっき

a-knowの日記です

GAE/Go(datastore)のテスト実行でテストケースごとに devserver が起動するのを回避する

最近趣味で、GAE/Go の Standard Environment で動くような Web アプリっぽいものを書いている。

今回はなんかテストも書いてみようという気持ちになったので書いてみているのだけど、ほどなくタイトルの問題に突き当たった。

datastore が絡むテストの書き方

datastore が絡むテストの書き方については、基本的に以下の記事に書いてあるとおり(だと思ってる)んだけど、

www.nowsprinting.com

今回の僕のケースだと 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 インスタンスを使い回す

上記のような問題に対して、いいライブラリがあった。

github.com

教えてくれた @serinuntius さん、ありがとうございます!

これを使えば、起動した devserver のインスタンスを持ち回ることができるようになる。つまり、テスト起動時に一度だけインスタンスを起動し、全部のテストケースの実行が終わったらインスタンスをシャットダウンできればよい。

普通にテストを書いた場合、各テストケースは func TestXxx(t *testing.T) みたいな感じで書いてると思うんだけど、それとは別に以下のような TestMain(*testing.M)main_test.go などで用意するこで、「テスト起動時に一度だけインスタンスを起動し、全部のテストケースの実行が終わったらインスタンスをシャットダウンする」が実現できる。

gist.github.com

これで go test すると、まずこの TestMain が呼ばれるようになるので、その中で m.Run() を呼び出すことで各テストケースを実行することができる。 m.Run() の前後に、テスト全体で必要な事前/事後処理を入れてやればいい、というわけ。これについては以下の記事が参考になった。

swet.dena.com

いままで aetest.NewInstance(&opt) とかって書いてた各テストケースの方も直さなくちゃいけなくて、それは以下のようなコードで置き換えればよさそう。

instance, _, err := testerator.SpinUp()
if err != nil {
    t.Fatal(err.Error())
}
defer testerator.SpinDown()

各テストケースで testerator.SpinDown() 呼んでもええんか?と思ったんだけどなぜか大丈夫っぽい(むしろ呼ばないと devserver のプロセスが生きたままどんどん残っていく)。理由までは追ってない......すんません。

参考情報

ここで書いたことほぼそのまま以下の Qiita 記事にまとまってたのだけど、今回独力ではこのページに辿り着けなかったこともあり、援護射撃(?)的な意味でこのエントリを書きました。

qiita.com

テスト周り、最初は億劫なんだけど書き始めると楽しいので、今後も頑張っていきたい......!