GAE/GoとGojiの組み合わせでテストを書く

これまで、MartiniとかBeegoとかでGoogle App Engine/Goのアプリケーションを作ってきましたが、Goでのテストを書けていませんでした。CasperJSのe2eテストでごまかしていましたが、今回Gojiでアプリケーションを作るにあたってちゃんとテストまわりを整備しました。

# Goji
[Go Web Frameworks 比較](http://qiita.com/najeira/items/bdc988c4e93b3b5ccf2f)で紹介されていますが、かなり薄めの実装です。Goだとおそらくそういうのがスタンダードですね。実質的に、ルーティングの部分を楽にかけるぐらいですね。以下のようにREST APIを生やす時に便利です。

```go

package main

import (
	"net/http"

	"github.com/zenazn/goji"
)

func init() {
	http.Handle("/", goji.DefaultMux)

	goji.Get("/", indexHandler)
	goji.Get("/api/v1/spots", spotHandler)
	goji.Get("/api/v1/spots/:spotCode", spotGetHandler)
	goji.Get("/edit/", indexHandler)
	goji.Get("/edit/v1/spots", spotHandler)
	goji.Get("/edit/v1/spots/:spotCode", spotGetHandler)
	goji.Post("/edit/v1/spots", spotCreateHandler)
	goji.Patch("/edit/v1/spots/:spotCode", spotUpdateHandler)
}

```

# GAE/Goのテスト
以下、テストの一部を書いてます。テストの全体は[githubで公開](https://github.com/yosukesuzuki/t2tapp/blob/master/main_test.go)してるのでそちらを参照してください。

```go

func TestCreateSpot(t *testing.T) {
	opt := aetest.Options{AppID: "t2jp-2015", StronglyConsistentDatastore: true}
	inst, err := aetest.NewInstance(&opt)
	defer inst.Close()
	input, err := json.Marshal(Spot{SpotName: "foo", Body: "bar"})
	req, err := inst.NewRequest("POST", "/edit/v1/spots", bytes.NewBuffer(input))
	if err != nil {
		t.Fatalf("Failed to create req: %v", err)
	}
	loginUser := user.User{Email: "hoge@gmail.com", Admin: false, ID: "111111"}
	aetest.Login(&loginUser, req)
	ctx := appengine.NewContext(req)
	res := httptest.NewRecorder()
	c := web.C{}
	spotCreateHandler(c, res, req)
	if res.Code != http.StatusCreated {
		t.Fatalf("Fail to request spots create, status code: %v", res.Code)
	}
	spots := []Spot{}
	_, err = datastore.NewQuery("Spot").Order("-UpdatedAt").GetAll(ctx, &spots)
	for i := 0; i < len(spots); i++ {
		t.Logf("SpotCode:%v", spots[i].SpotCode)
		t.Logf("SpotName:%v", spots[i].SpotName)
	}
	if spots[0].SpotName != "foo" {
		t.Fatalf("not expected value! :%v", spots[0].SpotName)
	}

}
```

この内容について順に解説していきます。

## StronglyConsistentDatastore
下は、テスト環境の設定です。StronglyConsistentDatastoreというのを有効すると、データストアへの反映がすぐにされるようになります。デフォルトだと実際のデータストアと同じようにデータの反映にややタイムラグがある仕様となっています。REST APIで書き込んだ値をまたすぐに取得して値をチェックするようなテストの際には、こうしておかないとデータが反映されずにテストが失敗してしまいます。

```go

opt := aetest.Options{AppID: "t2jp-2015", StronglyConsistentDatastore: true}

```

## InstanceはCloseする
開発時に _goapp test_ を繰り返してて、気がついたら、かなりの数のpythonプロセスが上がってました。MacのCPU使用率異常に高いなあと思ってある時、プロセス確認したら、以下のような感じでした。

```bash
ps aux | grep python

suzukiyosuke    10586  13.0  0.4  2541932  66300 s000  S     3:20PM   2:25.36 /usr/local/Cellar/python/2.7.10_2/Frameworks/Python.framework/Versions/2.7/Resources/Python.app/Contents/MacOS/Python /Users/suzukiyosuke/go_appengine/dev_appserver.py --port=0 --api_port=0 --admin_port=0 --skip_sdk_update_check=true --clear_datastore=true --clear_search_indexes=true --datastore_path /var/folders/bm/ftlrwxys521d062ww9f5p1qr0000gn/T/appengine-aetest782315070/datastore --datastore_consistency_policy=consistent /var/folders/bm/ftlrwxys521d062ww9f5p1qr0000gn/T/appengine-aetest782315070
suzukiyosuke    10596  12.4  0.4  2541932  66264 s000  S     3:20PM   2:26.28 /usr/local/Cellar/python/2.7.10_2/Frameworks/Python.framework/Versions/2.7/Resources/Python.app/Contents/MacOS/Python /Users/suzukiyosuke/go_appengine/dev_appserver.py --port=0 --api_port=0 --admin_port=0 --skip_sdk_update_check=true --clear_datastore=true --clear_search_indexes=true --datastore_path /var/folders/bm/ftlrwxys521d062ww9f5p1qr0000gn/T/appengine-aetest691799584/datastore --datastore_consistency_policy=consistent /var/folders/bm/ftlrwxys521d062ww9f5p1qr0000gn/T/appengine-aetest691799584
suzukiyosuke    12095  12.3  0.4  2541932  66156 s003  S     3:52PM   0:01.56 /usr/local/Cellar/python/2.7.10_2/Frameworks/Python.framework/Versions/2.7/Resources/Python.app/Contents/MacOS/Python /Users/suzukiyosuke/go_appengine/dev_appserver.py --port=0 --api_port=0 --admin_port=0 --skip_sdk_update_check=true --clear_datastore=true --clear_search_indexes=true --datastore_path /var/folders/bm/ftlrwxys521d062ww9f5p1qr0000gn/T/appengine-aetest330937532/datastore --datastore_consistency_policy=consistent /var/folders/bm/ftlrwxys521d062ww9f5p1qr0000gn/T/appengine-aetest330937532
suzukiyosuke    10587  11.0  0.4  2541932  65988 s000  S     3:20PM   2:26.25 /usr/local/Cellar/python/2.7.10_2/Frameworks/Python.framework/Versions/2.7/Resources/Python.app/Contents/MacOS/Python /Users/suzukiyosuke/go_appengine/dev_appserver.py --port=0 --api_port=0 --admin_port=0 --skip_sdk_update_check=true --clear_datastore=true --clear_search_indexes=true --datastore_path /var/folders/bm/ftlrwxys521d062ww9f5p1qr0000gn/T/appengine-aetest669790853/datastore --datastore_consistency_policy=consistent /var/folders/bm/ftlrwxys521d062ww9f5p1qr0000gn/T/appengine-aetest669790853
suzukiyosuke    12094   3.0  0.4  2541932  65672 s003  S     3:52PM   0:01.58 /usr/local/Cellar/python/2.7.10_2/Frameworks/Python.framework/Versions/2.7/Resources/Python.app/Contents/MacOS/Python /Users/suzukiyosuke/go_appengine/dev_appserver.py --port=0 --api_port=0 --admin_port=0 --skip_sdk_update_check=true --clear_datastore=true --clear_search_indexes=true --datastore_path /var/folders/bm/ftlrwxys521d062ww9f5p1qr0000gn/T/appengine-aetest105125009/datastore --datastore_consistency_policy=consistent /var/folders/bm/ftlrwxys521d062ww9f5p1qr0000gn/T/appengine-aetest105125009
suzukiyosuke    12104   0.8  0.4  2540908  65164 s003  R     3:52PM   0:01.41 /usr/local/Cellar/python/2.7.10_2/Frameworks/Python.framework/Versions/2.7/Resources/Python.app/Contents/MacOS/Python /Users/suzukiyosuke/go_appengine/dev_appserver.py --port=0 --api_port=0 --admin_port=0 --skip_sdk_update_check=true --clear_datastore=true --clear_search_indexes=true --datastore_path /var/folders/bm/ftlrwxys521d062ww9f5p1qr0000gn/T/appengine-aetest152841963/datastore --datastore_consistency_policy=consistent /var/folders/bm/ftlrwxys521d062ww9f5p1qr0000gn/T/appengine-aetest152841963
```

ちゃんとCloseしないいけなかったのですね。以下のようにdefer でCloseしておけば、ちゃんとtest後にプロセスが消えているようでした。

```go

inst, err := aetest.NewInstance(&opt)
defer inst.Close()

```

## Loginユーザーとしてテストする
GAEの便利なところとして、Googleアカウントでのログイン制御が簡単にできるところです。それのテストができます。

```go

loginUser := user.User{Email: "hoge@gmail.com", Admin: false, ID: "111111"}
aetest.Login(&loginUser, req)

```

## データをPOSTする
普通によくあるテストですが、テストのinstanceを作成したら、それを使ってPOSTリクエストを作って、REST APIのテストをします。

```go

input, err := json.Marshal(Spot{SpotName: "foo", Body: "bar"})
req, err := inst.NewRequest("POST", "/edit/v1/spots", bytes.NewBuffer(input))
if err != nil {
    t.Fatalf("Failed to create req: %v", err)
}

```


## データストアの書き込みを確認
REST APIでデータを書き込みをしたら、それが実際に書き込みされているか、確認してみます。

```go

spots := []Spot{}
_, err = datastore.NewQuery("Spot").Order("-UpdatedAt").GetAll(ctx, &spots)
for i := 0; i < len(spots); i++ {
    t.Logf("SpotCode:%v", spots[i].SpotCode)
    t.Logf("SpotName:%v", spots[i].SpotName)
}
if spots[0].SpotName != "foo" {
    t.Fatalf("not expected value! :%v", spots[0].SpotName)
}

```

これで一通りテストに使いやすそうな機能は紹介しました。

# CIの設定
テストは手元でも実行しますが、やはりCIサービスで自動実行されると便利ですよね。[最新のGAE SDKを取得するスクリプト](https://github.com/yosukesuzuki/t2tapp/blob/master/getlatestsdk.py)を作っていますのでよろしければそちらも利用してください。

CircleCIの設定は以下のとおりです。今回はWebdriver.IOでのe2eテストの設定も入れています。また masterブランチとdeployment/productionへのマージがあった場合は、デプロイも走るようにしています。

```yaml

machine:
  timezone:
    Asia/Tokyo

dependencies:
  pre:
    - python getlatestsdk.py
    - unzip -q -d $HOME google_appengine.zip
    - npm install -g webdriverio
    - npm install -g webdriver-manager
    - curl -O http://selenium-release.storage.googleapis.com/2.47/selenium-server-standalone-2.47.0.jar
  override:
      - echo $HOME

test:
  pre:
    - java -jar selenium-server-standalone-2.47.0.jar:
        background: true
    - sleep 5
    - webdriver-manager start:
        background: true
    - sleep 5
    - $HOME/go_appengine/goapp serve:
        background: true
    - sleep 5
  override:
    - $HOME/go_appengine/goapp test
    - wdio wdio.conf.js

deployment:
  development:
    branch: master
    commands:
      - $HOME/go_appengine/appcfg.py --oauth2_refresh_token=$APPENGINE_TOKEN update . --version=dev
  production:
    branch: deployment/production
    commands:
      - $HOME/go_appengine/appcfg.py --oauth2_refresh_token=$APPENGINE_TOKEN update . --version=production

```

以上、具体的なコードをもとにテストの仕方を紹介しました。コード群全体は以下のレポジトリで公開していますので、御覧ください。

https://github.com/yosukesuzuki/t2tapp