testerator はGoogle App Engine for Go(以下gae/go)のUnit Testを高速化するために生まれたライブラリです。
gae/goはサーバのSpinUp速度がJava, Python, PHPに比べて高速なので、最近App Engine Developerたちの間に人気ですが、いかんせんUnitTestの遅さだけは洒落にならないレベルで遅いです。
それを緩和するために生まれたのが、 testerator です。
testerator がやってくれることを理解するためには、まずgae/goのUnitTestがなぜ遅いのかを理解する必要があります。
gae/goのUnitTestが遅い原因は、テスト環境がgae/pythonの環境に間借りしているからです。
例えば、公式のサンプル にある通り、 aetest.NewContext() を利用すると、裏ではgae/pythonにあるgaeのmockのプロセスが立ち上がります。
このプロセスの立ち上がりには3secほどかかります。これがUnitTest開始時に1度だけで済めばよいのですが、環境をクリアするために、UnitTest毎にプロセスの再作成が行われます。
そのため、UnitTestが100個あると、3sec * 100で300secかかります。
UnitTestを実行する度に、珈琲を豆から挽いて淹れることができるレベルです。
import (
"testing"
"google.golang.org/appengine/aetest"
)
func TestWithContext1(t *testing.T) {
ctx, done, err := aetest.NewContext() // ←ここで3sec
if err != nil {
t.Fatal(err)
}
defer done()
// Run code and tests requiring the context.Context using ctx.
// ...
}
func TestWithContext2(t *testing.T) {
ctx, done, err := aetest.NewContext() // ←ここでも3sec !
if err != nil {
t.Fatal(err)
}
defer done()
// Run code and tests requiring the context.Context using ctx.
// ...
}
testerator はgae/pythonのプロセスの立ち上がりを抑制してくれる
testerator はUnitTestのスピードを上げるために、一度立ち上げてgae/pythonのプロセスを使いまわすようになっています。
import (
"testing"
"github.com/favclip/testerator"
"google.golang.org/appengine/datastore"
)
func TestPut(t *testing.T) {
_, c, err := testerator.SpinUp() // gae/pythonのインスタンスが無ければ起動、あれば使いまわす
if err != nil {
t.Fatal(err.Error())
}
defer testerator.SpinDown() // プロセスをシャットダウンせずに、Datastoreなどの内容をクリアする
...
}
func TestGetEmpty(t *testing.T) {
_, c, err := testerator.SpinUp()
if err != nil {
t.Fatal(err.Error())
}
defer testerator.SpinDown()
...
}
testerator 利用時の注意点
testerator を使ってUnitTestを高速化するためには、最初にgae/pythonのプロセスを1つ立ち上げる必要があります。
UnitTest毎に testerator.SpinUp()
, testerator.SpinDown()
を交互に呼ぶだけだと、結局プロセスを毎回作り直してしまうからです。
無難な方法としては、 TestMain(*testing.M)
を使って、UnitTest前に testerator.SpinUp()
を呼んで、UnitTest後に testerator.SpinDown()
を呼びます。
import (
"fmt"
"os"
"testing"
_ "github.com/favclip/testerator/datastore"
_ "github.com/favclip/testerator/memcache"
_ "github.com/favclip/testerator/search"
"github.com/favclip/testerator"
)
func TestMain(m *testing.M) {
_, _, err := testerator.SpinUp() // 最初の1プロセスを起動!
if err != nil {
fmt.Printf(err.Error())
os.Exit(1)
}
status := m.Run() // UnitTest実行!
err = testerator.SpinDown() // 最初に立ち上げたプロセスを落とす
if err != nil {
fmt.Printf(err.Error())
os.Exit(1)
}
os.Exit(status)
}
それでも完璧には早くならなかったよ・・・
testerator はUnitTestの高速化に貢献してくれました。
しかし、たくさんのUnitTestがある時は、爆速にはなりません。
なぜならば、gae/pythonのプロセスをずっと使いまわすると、途中で応答が無くなってしまうので、定期的にgae/pythonのプロセスを再起動しているからです・・・。
何回UnitTestを実行した後にプロセスを再起動するかは ResetThreshold という値が管理されており、デフォルトは15です testarator.go#L52
変更したい場合は testerator.DefaultSetup.ResetThreshold
に設定を行います。
もし、なんかよくわからんけど、UnitTestの応答が無くなったら、ちょっと減らしてみると良いかもしれません。
func TestMain(m *testing.M) {
_, _, err := testerator.SpinUp()
if err != nil {
fmt.Printf(err.Error())
os.Exit(1)
}
testerator.DefaultSetup.ResetThreshold = 12
status := m.Run()
err = testerator.SpinDown()
if err != nil {
fmt.Printf(err.Error())
os.Exit(1)
}
os.Exit(status)
}
さいごに
ここまで読んだあなたはきっと"これって README.md に書いてあったほうがいいんじゃない!?"って思ったことでしょう。
僕もそう思います。
...がんばって、英語書いてpull req送っておきますね。