13
12

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

Google Cloud PlatformAdvent Calendar 2015

Day 22

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

Posted at

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

Goji

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


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で公開してるのでそちらを参照してください。


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で書き込んだ値をまたすぐに取得して値をチェックするようなテストの際には、こうしておかないとデータが反映されずにテストが失敗してしまいます。


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

InstanceはCloseする

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

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後にプロセスが消えているようでした。


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

Loginユーザーとしてテストする

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


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

データをPOSTする

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


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でデータを書き込みをしたら、それが実際に書き込みされているか、確認してみます。


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を取得するスクリプトを作っていますのでよろしければそちらも利用してください。

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


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

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

13
12
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
13
12

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?