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

この記事は最終更新日から3年以上が経過しています。

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

ユーザー登録して、Qiitaをもっと便利に使ってみませんか。
  1. あなたにマッチした記事をお届けします
    ユーザーやタグをフォローすることで、あなたが興味を持つ技術分野の情報をまとめてキャッチアップできます
  2. 便利な情報をあとで効率的に読み返せます
    気に入った記事を「ストック」することで、あとからすぐに検索できます
コメント
この記事にコメントはありません。
あなたもコメントしてみませんか :)
すでにアカウントを持っている方は
ユーザーは見つかりませんでした