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

[testerator](https://github.com/favclip/testerator) はGoogle App Engine for Go(以下gae/go)のUnit Testを高速化するために生まれたライブラリです。

gae/goはサーバのSpinUp速度がJava, Python, PHPに比べて高速なので、最近App Engine Developerたちの間に人気ですが、いかんせんUnitTestの遅さだけは洒落にならないレベルで遅いです。

それを緩和するために生まれたのが、 [testerator](https://github.com/favclip/testerator) です。
[testerator](https://github.com/favclip/testerator) がやってくれることを理解するためには、まずgae/goのUnitTestがなぜ遅いのかを理解する必要があります。

gae/goのUnitTestが遅い原因は、テスト環境がgae/pythonの環境に間借りしているからです。
例えば、[公式のサンプル](https://cloud.google.com/appengine/docs/go/tools/localunittesting/#Go_Introducing_the_aetest_package) にある通り、 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](https://github.com/favclip/testerator) はgae/pythonのプロセスの立ち上がりを抑制してくれる

[testerator](https://github.com/favclip/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](https://github.com/favclip/testerator) 利用時の注意点

[testerator](https://github.com/favclip/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](https://github.com/favclip/testerator) はUnitTestの高速化に貢献してくれました。
しかし、たくさんのUnitTestがある時は、爆速にはなりません。
なぜならば、gae/pythonのプロセスをずっと使いまわすると、途中で応答が無くなってしまうので、定期的にgae/pythonのプロセスを再起動しているからです・・・。

何回UnitTestを実行した後にプロセスを再起動するかは ResetThreshold という値が管理されており、デフォルトは15です [testarator.go#L52](https://github.com/favclip/testerator/blob/master/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](https://github.com/favclip/testerator/blob/master/README.md) に書いてあったほうがいいんじゃない！？"って思ったことでしょう。
僕もそう思います。
...がんばって、英語書いてpull req送っておきますね。