GoのtestをGKEで分散実行する

# 前置き

Goのプロジェクトも大きくなってくると、go testに時間がかかってくる様になりますよね〜

私の愛してやまないGAE/Goの場合は特に、テストケースの度にpythonのローカルサーバープロセスが起動する為、バカにならない時間がかかります。

そこで金の弾丸に物を言わせて、Google Container Engine(GKE)でcoreたくさんぶん回してテストを並列実行してみたらどうだろうか、と思い立ち、実験してみました。

GKEのセットアップは↓をほぼそのまま参考にさせてもらいました。とても簡単にできました＼(^o^)／
[GKE を使って golang アプリケーションコンテナを稼働させる](http://blog.kaneshin.co/entry/2016/12/15/133943)

※ 今回はGKEから外向きにポートは開かないので、Serviceの設定は不要です

# 概要

用意した資材はこちら。
https://github.com/knightso/honeybee

とりあえず実験目的でかなりテキトーに作った状態なので、今回は詳しいsetup手順は割愛して処理の概要だけ紹介します。
（↑の資材リポジトリに必要なコードは揃ってるので興味のある人はどうぞ）

## 処理の流れ

### クライアント(hb-client)

#### 1. ローカルのgoファイルを解析して、テスト対象のパッケージ＆関数のリストを作成

テスト対象のパッケージとファイル名の抽出は手抜きをして`go list`（実際にはgbを使っているので`gb list`）を利用しました。

```bash
$ go list -f '{{.ImportPath}}:{{.Dir}}:{{range .TestGoFiles}}{{.}},{{end}}{{range .XTestGoFiles}}{{.}},{{end}}' ./...
```

これで、
`importパス:ディレクトリ:hoge_test.go,fuga_test.go,piyo_test.go,...略...` 
みたいなレコードのリストが出力されます（便利ですね！）。

それを標準入力で受け取り、各ファイルのASTを解析し、テスト関数名を抽出しています。

→[コード](https://github.com/knightso/honeybee/blob/b74fa90d3da81f3eb0528930ec3055bc1c9c0062/cmd/hb-client/main.go#L213-L237)

#### 2. テスト関数毎にリクエストを作成し、GAEのTaskQueue(PULL)に送信

1で解析した情報をもとにリクエストパラメータを作成し、[Google Client Library](https://github.com/google/google-api-go-client)を利用してGAEのTaskQueue(PULL)に送信します。

| リクエストパラメータ    |
|----------|
| UUID |
| github commit番号 |
| パッケージ |
| ファイル名 |
| テスト関数名 |

→[コード](https://github.com/knightso/honeybee/blob/b74fa90d3da81f3eb0528930ec3055bc1c9c0062/cmd/hb-client/main.go#L275)

※ 今回メッセージのやりとりにGAEのTaskQueueのみを利用しました。GAEアプリ自体は使用していません。


### コンテナ(hb)

#### 1. Taskqueueからリクエストを一件Lease

クライアントが送信したリクエストをTaskQueueから取り出します。
→[コード](https://github.com/knightso/honeybee/blob/b74fa90d3da81f3eb0528930ec3055bc1c9c0062/cmd/hb/main.go#L208)

Lease関数で取り出した後はDeleteする必要があります。
→[コード](https://github.com/knightso/honeybee/blob/b74fa90d3da81f3eb0528930ec3055bc1c9c0062/cmd/hb/main.go#L229)

#### 2. githubの指定されたcommitをcheckout

ある時点までのレポジトリはdocker build時にイメージ内に、あらかじめcloneしておきます。

コンテナでは、リクエストで指定されたcommit番号で`git checkout`します。
→[コード](https://github.com/knightso/honeybee/blob/b74fa90d3da81f3eb0528930ec3055bc1c9c0062/cmd/hb/main.go#L229)

ただ・・差分のみの取得とはいえ大量のコンテナから集中して`git checkout`するのはどうなんだろう？との懸念から現在は一旦処理をコメントアウトしてます。

#### 3. テスト関数を指定してテスト実行

カレントディレクトリに`test.sh`というスクリプトを用意しておくとキックする仕組みにしました。
→[サンプルコード](https://github.com/knightso/honeybee/blob/b74fa90d3da81f3eb0528930ec3055bc1c9c0062/cmd/hb/test.sh.sample)

サンプルではgbを利用していますが、go testでもいけます。

#### 4. テスト結果をTaskQueue(PULL)に送信

テスト結果を結果用TaskQueueに送信します。
→[コード](https://github.com/knightso/honeybee/blob/b74fa90d3da81f3eb0528930ec3055bc1c9c0062/cmd/hb/main.go#L146-L171)

送信する際に、Tagにクライアントから送られてきたUUIDを指定しています。こうすることでクライアントは自分の送信したUUIDに紐づく結果のみをLeaseすることが出来ます。

#### 5. ログとしてCloud Datastoreにも保存

→[コード](https://github.com/knightso/honeybee/blob/b74fa90d3da81f3eb0528930ec3055bc1c9c0062/cmd/hb/main.go#L135-L137)

### 再度クライアント(hb-client)

#### 1. TaskQueueの結果リストを監視(UUIDでタグを指定してLease)

TagにUUIDを指定して、TaskQueueからLeaseしています。
→[コード](https://github.com/knightso/honeybee/blob/b74fa90d3da81f3eb0528930ec3055bc1c9c0062/cmd/hb-client/main.go#L387)

#### 2. 送信したリクエストの数だけ結果が返ってきたらレポート出力して終了


# 結果

手元の開発中プロジェクトでテストしました。

**まずはローカルで普通にgo test**

| テストケース数    | 実行時間  |
|----------------|-----------|
| 320 | 約1,000秒  |
たった320件で1000秒って時間かかりすぎだろ！ってカンジですが、GAE/Goのaetestだとこんなカンジです。

↓↓

**GKEクラスターで分散実行!**

| core数    | pod数 | 実行時間  |
|----------|------|-----------|
| 320 | 320 | 約80秒  |

**10倍以上速くなった！ ＼(^o^)／**

と一瞬喜んでみましたが、coreが320倍になったのに実行時間はたったの10倍ってちょっとひどいですね(^^;

まあ完全並列実行しても一番長いテストケースの実行時間に集約されてしてしまうので、仕方ない面もありますが・・・費用対効果考えるとかなり微妙ですね。。

・・・という訳で軽く玉砕した感がありますが、せっかくなので記事にさせてくださいm(_ _)m

今回作成したコードは、もうちょっと実用性が上がってツールとしての体裁が整ったら改めて紹介したいな、と思っています。

ではでは、良いお年を〜