grpcurlを使ってGitHub Actions上でgRPCなAPIのテストを実行する


# はじめに

[grpcurl](https://github.com/fullstorydev/grpcurl)というgRPCサーバをコマンドラインで叩けるツールを利用し、それをGitHub Actions上で動かしてひとまず正常動作確認まで試せたのでここに記録を残します。

# 環境

go version go1.13.1 darwin/amd64
grpcurl version: v1.4.0

# Main Contents

## プロジェクト内のGo製ツールの管理はtools.goファイルを生やす

[grpcurl](https://github.com/fullstorydev/grpcurl)はGo製のツールなので`go install`でコマンドを落とすことができます。

Go製のツールのバージョンもGo Moduleの`go.mod`にて管理するための方法を探しました。

すると公式の[Go Modulesに関するこちらのリンク](https://github.com/golang/go/wiki/Modules#how-can-i-track-tool-dependencies-for-a-module)にて言及があり、`tools.go`(ファイル名は何でも良い)上でツールをimportすることが現状(2020年1月時点)のベストプラクティスとわかりました。

公式での具体例が[こちら](https://github.com/go-modules-by-example/index/blob/master/010_tools/README.md)、GitHubのIssueでの議論が[こちら](https://github.com/golang/go/issues/25922)になります。

例として、`tools.go`は以下のようになります。

```tools.go
// +build tools

package tools

import (
	_ "github.com/fullstorydev/grpcurl/cmd/grpcurl"
	_ "github.com/golang/mock/mockgen"
	_ "github.com/golang/protobuf/protoc-gen-go"
	_ "github.com/google/wire/cmd/wire"
)
```

ポイントは`+build tools`を付けることでプロダクションコードには`tools.go`でimport記述されているライブラリコード分をコンパイル対象にさせないことです。

そして今回、[こちらの記事](https://marcofranssen.nl/manage-go-tools-via-go-modules/)を参考に`Makefile`上で上記ツールをインストールするコマンドを用意しました。

```makefile:Makefileの一部
.PHONY: setup
setup: install-tools
	@true

.PHONY: install-tools
install-tools:
	$(GOMOD) download
	@cat tools/tools.go | grep _ | awk -F'"' '{print $$2}' | xargs -tI % go install % 
```

`make install-tools`とすることでツールコマンドがインストールされ、そのバージョンが`go.mod`で管理されるようになります。

## grpcurlをローカルで試す

[pgrpcurlのREADME](https://github.com/fullstorydev/grpcurl)の例を見ながら検証しました。

デフォルトではgRPCのデバッグをするために利用するreflectionの機能が有効になっていることが想定されていますが、今回は`proto`ファイルを参照してクライアントコールする方法にしています。

```terminal
$ grpcurl -v -d @ -plaintext -proto pb/service.proto localhost:8081 pb.service.Method < test.json
```

上記では

* `-d @`で`test.json`としてgRPCクライアント側でテスト用に渡すJSONファイルを指定
* `-proto`オプションでprotoファイルを参照するよう指定

しています。gRPCサーバを`8081`ポートで動作させていれば以下のような結果が得られます。

```
Resolved method descriptor:
// A server-to-client streaming RPC.
//
// Comment
rpc Method ( Args) returns ( stream .pb.ReturnType);

Request metadata to send:
(empty)

Response headers received:
content-type: application/grpc

Response contents:
{
  "userId": "ABCDE",
  ....
}
```

## GitHub ActionsにてGo製ツールを使うためにGOPATHとPATH設定をする

GitHub Actionsの公式ランナーにてGOPATH設定しPATHを通すようにするには、現状(2020年1月時点)スマートな方法がまだないようです。

[ここのIssueでの議論](https://github.com/actions/setup-go/issues/14#issuecomment-563273583)を参考に、以下のような設定をした上で`grpcurl`コマンドでテストをしています。

```yaml:GitHubActionsのworkflowsファイル一部
name: Test
on: push
jobs:
  integration:
    name: Integration test
    runs-on: ubuntu-latest
    steps:

    - name: Setup Go
      uses: actions/setup-go@v1
      with:
        go-version: 1.13.1

    - name: Setup GOPATH
      # temporary step to run grpcurl command in GOPATH
      # copied from https://github.com/actions/setup-go/issues/14#issuecomment-563273583
      run: |
        echo "::set-env name=GOPATH::$(go env GOPATH)"
        echo "::add-path::$(go env GOPATH)/bin"
      shell: bash

    - name: Checkout code
      uses: actions/checkout@v2

    - name: Install test tools
      run: make setup

    # gRPCサーバを立てる作業

    - name: Run gRPC API test
      run: grpcurl -v -d @ -plaintext -proto pb/service.proto localhost:8081 pb.service.Method < test.json
```

gRPCサーバに異常があれば正常終了しないのでひとまず正常系のテストができるようになりました。

gRPCにはHTTPステータスコードとの互換を想定したコード定義が以下リンクのように公式で定められているので、今後これに対応するようにサーバ側を更新しテストを充実させていく予定です。
https://github.com/googleapis/googleapis/blob/master/google/rpc/code.proto

# 参考

* https://syfm.hatenablog.com/entry/2019/08/10/170730
* https://github.com/golang/go/wiki/Modules
* https://qiita.com/yukina-ge/items/a84693f01f3f0edba482