Edited at

testeratorを使ってgae/goのunit testを高速化する

More than 1 year has passed since last update.

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送っておきますね。