iPhoneでGrafanaのグラフを参照できるアプリGrafanizer作ってます。 詳しくはこちらへ

GAE/Goでのテスト

GolangでのGoogleAppEngineは、金額的にも、運用的にも、速度的にも、手軽にプログラムを作れて便利なのですが、テストについてはちょっとつらいわけです(唐突な前振り)。

普通であればAppEngineSDKに用意されている goappgoapp test とすることになるのですが、これはテスト用のインスタンスやコンテキストを取得する度に新しくPython製のDevServerが立ち上がって来るのでかなーり時間がかかる。

そこで、これまでは一度起動したDevServerを使いまわすべく favclip/testerator を使ってました。

これはこれで良かったのですが、テストが増えてくると反応がなくなる状態が出てきてやっぱりだんだん辛くなってくるわけです。ここにあるとおり対処方法はある のですが、それでも結局DevServerの起動時間は待たなくてはいけないので、素のGolangプログラムを書いている感じでのテストにはなりませんでした。

そんなときに luci/gae なるライブラリを発見して使ってみたらテストがかなり楽になったので、その使い方を紹介します。

  1. go testgoapp test 用にテストを分離
  2. DataStore用にContextを切り替え
  3. テスト時特有の設定
  4. goonとの違いとデータ変換 ← 次のページ
  5. 自動MemCache ← 次のページ

簡単な紹介については ↓のページを参考にすると日本語でわかりやすいと思います。このページがなかったら今でも辛い人生を送ってました(ちょっと言い過ぎ)。

AppEngine/Goのテストを100倍高速化するライブラリluciの紹介

【 ページ内広告 】

1. go test と goapp test にテストを分離

まずは、テストコードを二つに分けてテストコードの先頭に ビルドタグ をつけます。

  • luci/gae を使ってGolang純正のテストツールで対応できるテストには // +build !appengine
  • appengine/aetest を使ってgoappでテストする必要があるテストには // +build appengine

できるだけ appengine/aetest 用のテストは少なくし、普段は go test のみを実行すれば良いようにすると快適になれます。

例えば、 luci/gae がサポートしていないサービス(具体的にはSearchAPIやDataStorage)等は appengine/aetest でテストする必要があるのですが、そういう部分はなるべくうすーく作りテストする回数を下げてやると良いかと思います。またはテストしないという選択肢もアリかもしれません。

2. DataStore用にContextを切り替え

次に luci/gae 用に分けたテスト対象コードにContextの切り替え機能を追加します。

実装については色々なやり方があると思いますが、素直に作るとこんな感じでしょうか。


package main // main.go

import "github.com/luci/gae/impl/prod"

http.HandleFunc("/", indexHandle)

func indexHandle(w http.ResponseWriter, r *http.Request) {
  ctx := prod.Use(appengine.NewContext(r), r)
  data, err := getData(ctx)
  fmt.Fprintf(w, “Hello, %s”, data)}
}

func getData(ctx) (string, error) {
  ...
  return string, nil
}
// +build !appengine
package main // main_test.go

import "github.com/luci/gae/impl/memory"

func TestIndexHandler(t *testing.T) {
  ctx := memory.Use(context.Background())
  data, err := getData(ctx)
  ...
}

Web経由でアクセスされたときはAppEngineのコンテキストを使ってテストのときはデフォルトのコンテキストを使うようにするだけです。

(ところで、もう少しでGAEにやってくるGo1.8のContext対応は大丈夫なんだろうか?)

3. テスト時特有の設定

テスト時特有の設定としては、

  • ログイン処理
  • データの永続性

があります。

ログイン処理

ログイン処理についてはuserパッケージが用意されているので、簡単にログイン状態を作ることが出来ます。

具体的には、コンテキストを作った後にログイン処理をさせるだけですね。簡単です。

ctx := memory.Use(context.Background())
user := user.GetTestable(ctx)
user.Login("user@example.com", "", false)

データ永続化とインデックス作成

memory.Use で作ったデータはctxの再作成タイミングでクリアされてしまうようです。なので、テストコードで直接データをPUTして、http.NewRequest で取得したデータと比較させるようなテストケースではうまく行きませんでした。

また、 index.yaml があっても自動で作られません。Indexが無いというエラーが発生します。

これらを回避するためには、以下のコードをを加えてやる必要があります。

testable := datastore.GetTestable(ctx)
if testable != nil {
	testable.Consistent(true)
	testable.AutoIndex(true)
}

見て分かる通りテスト環境のときだけ有効にさせています。


久しぶりにBlog書き込んだら熱くなってきて長くなったので、次に続く...