使用 Testerator 工具来加快 GAE/Go 的单元测试速度

testerator是一个为了加速Google App Engine for Go(以下简称gae/go)的单元测试而诞生的库。

由于gae/go服务器的启动速度比Java、Python和PHP快,因此最近在App Engine开发者中很受欢迎。但是不得不说,单元测试的速度实在是太慢了,丝毫没有玩笑可言。

为了缓解这个问题,testerator 应运而生。
要理解 testerator 的作用,首先需要理解为什么 gae/go 的 UnitTest 很慢。

原因在于gae/go的单元测试很慢,这是因为测试环境是借用gae/python环境。
例如,按照官方的示例,使用aetest.NewContext()会启动gae/python中的gae模拟进程。
这个进程的启动大约需要3秒钟。如果单元测试只需要一次启动就可以完成,那就没问题了,但为了清理环境,每次单元测试都会重新创建进程。
因此,如果有100个单元测试,就需要花费3秒*100次,共300秒。

每次执行单元测试都能够像磨咖啡豆一样煮咖啡,这种水平。

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 进程启动过程。

为了加快 Unittest 的速度,testerator 会启动一次并重复使用 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 进行单元测试,首先需要启动一个 gae/python 进程。
如果每个单元测试都只是简单地交替调用 testerator.SpinUp() 和 testerator.SpinDown(),那最终每次都会重新创建进程,这样做没有效果。
一个可靠的方法是使用 TestMain(*testing.M),在单元测试之前调用 testerator.SpinUp(),在单元测试之后调用 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进程…。

在执行多少次单元测试后重新启动进程是由名为ResetThreshold的值进行管理的,默认为15(testarator.go#L52)。

备注:以上翻译为简体中文。

如果想要进行更改,可以在 testerator.DefaultSetup.ResetThreshold 进行设置。
如果发现单元测试没有响应了,可以试着稍微减少一下,或许会有所改善。

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。