Slide 1

Slide 1 text

次世代タクシー配車サービス「MOV 」における テスト事例紹介 DeNA.go #2 オートモーティブ事業本部 toku_bass 1

Slide 2

Slide 2 text

目次 MOV 紹介 前提環境 テストを書く上での方針 並列テストのためのデータ生成 DI できないコードと戦う その他tips 2

Slide 3

Slide 3 text

MOV 紹介 3

Slide 4

Slide 4 text

MOV 紹介 2018/04 神奈川県 地域限定でリリース 2018/06 神奈川県 対象エリア拡大 2018/12 東京都 リリース 2019/07 大阪府、京都府 リリース 4

Slide 5

Slide 5 text

発表の前提 GAE/Go 1st MOV API サーバーで使用 test 実行方法 local でgoapp test CircleCI chatbot E2E 5

Slide 6

Slide 6 text

テストを書く上での方針 注目しているtestcase 以外の暗黙のデータに依存しない テスト全体に関わる xture を使わない 並列(testing.T.Parallel )でテスト実行 6

Slide 7

Slide 7 text

テスト全体に関わる xture を使わない テスト実行前にマスターデータ以外をDB にload しない テストが初期 xture に依存するようになる 7

Slide 8

Slide 8 text

並列でテスト実行 testing.T.Parallel() をしたいがグローバルな状態をもつDB がネック user_id=1 のような固定値を書かないようにする github.com/bxcodec/faker (のv3 )がいい 8

Slide 9

Slide 9 text

並列テストのためのデータ生成 前述の github.com/bxcodec/faker を使用 テスト用のユーザを作りたい場合などに構造体のgotag を利用してダミ ーデータを生成する type User struct { Name string // ランダムな文字列を生成 Email string `faker:"email"` // email 形式で生成 } s := SomeStruct{} faker.FakeData(&s) faker:"email" のようにデフォルトで用意されているタグ以外にもプロダ クト用に自作のtag を作ることができる 9

Slide 10

Slide 10 text

faker のMOV での利用例 テスト用のtag をプロダクトコードに記述したくないという意見がで たため、静的解析で同等の構造体を別名で生成しgotag を付与 primary key がランダム値が偶然被らないように管理(todo) id 採番アルゴリズム採用(go-katsubushi など) 10

Slide 11

Slide 11 text

DI できないコードと戦う すべてDI できればいいが、現実は厳しい。 特にtime.Now() はどこにでも現れる 11

Slide 12

Slide 12 text

time.Now - DI できないコードと戦う time.Now 定義をpackage 変数に保存し、ラッパー関数経由で呼び出 す テスト時にだけpackage 変数を書き換えるsetter をビルド対象に含め る build タグを使用する 12

Slide 13

Slide 13 text

ラッパー package util var nowFunc func() time.Time func init() { // setter 関数はテスト時にしか定義されない nowFunc = time.Now } func Now() time.Time { return nowFunc() } 13

Slide 14

Slide 14 text

setter // +build test package util import ( "sync" "time" ) var m sync.Mutex func SetNowFunc(f func() time.Time) func() { m.Lock() nowFunc = f return func() { m.Unlock() } } 14

Slide 15

Slide 15 text

利用例 func TestXXXX(t *testing.T) { unlock := util.SetNowFunc(func() time.Time { return time.Date(...) // 省略 }) defer unlock() ... } 15

Slide 16

Slide 16 text

Client 実装をファイル単位で偽装 - DI できない コードと戦う build タグを使うことでファイル単位で偽装する大技 基本アイデア link 利用例 var gotMessageText string reset := infra.SetMock_Send(func(message domain.MailMessage) error { gotMessageText = message.TextPart return nil }) defer reset() 16

Slide 17

Slide 17 text

その他tips testerator pstest 17

Slide 18

Slide 18 text

testerator GAE 用のテストサーバーの起動には3 秒ほどかかるため、テストケー スごとに上げ下げしないためのライブラリ SpinUp 関数を呼ぶと、起動済みサーバーの情報か、起動していなけ れば改めて起動して情報を返す ins, ctx, err := testerator.SpinUp() しかし、たまに意図せずテストサーバーが落ちる問題がおきた 18

Slide 19

Slide 19 text

testerator がサーバーを落とす条件 内部カウンター(Setup.counter) が0 になったとき SpinUp で+1, SpinDown で-1 Setup.total % Setup.ResetThreshold == 0 のとき total はSpinUp のときに+1 される RestThreshold はデフォルトで1000 counter が0 にならないように中央管理 テストごとに実装者がSpinUp,SpinDown を記述していると内部カウ ンタが0 になるタイミングが発生した SpinUp するラッパー関数を用意し中央管理 19

Slide 20

Slide 20 text

Cloud PubSub 依存のテスト pstest が便利 Cloud PubSub の公式ライブラリのテスト方法 どうやってテストすればいいのか悩んだ場合はライブラリそのもののテ ストを確認しにいくと良い 20