
scenarigoを用いたgRPCサーバのE2Eテスト実装事例
はじめに(イントロダクション)
こんにちは、ワンキャリアでバックエンドエンジニアをしている田中(@kakke18_perry)です。業務では、ONE CAREER CLOUDの開発に携わっており、リアーキテクチャの一環で、一部の機能をGo+gRPCでリプレイスしています。
アーキテクチャ選定理由については以下の記事で紹介していますので、興味のある方はこちらもぜひご一読ください。
本記事では、Go+gRPCで実装されたサーバ(以後、gRPCサーバとする)に対するE2Eテストの実装方法を解説します。具体的には、scenarigoを用いたE2Eテストの実装、テストの実行までの手順を説明し、E2Eテストをどこまで書けばいいのかという話もします。
gRPCサーバに対するE2Eテストの選択肢
E2Eテストは、システム全体の動作を検証するテスト手法であり、開発プロセスにおいて重要な役割を果たします。そんなE2Eテストの実現方法についてChatGPT(GPT-4)に聞いてみました。

ChatGPTも言ってる通り、Goのテストフレームワーク(_test.goファイルに実装しgo testによって実行されるもの)とgRPCクライアントを使用する方法がシンプルに実装できそうです。しかし、今回はscenarigoというツールを採択しました。その理由は以下の2つです。
yamlファイルでテストが書ける
筆者がscenarigoを使ったことがある
Goのテストフレームワークで実装するより、yamlファイルで実装した方がテストケースの見通しが良いと判断しました。また、筆者がscenarigoを使ったことがあった(gRPCでの利用は初めて)というのも採択理由として大きかったです。
ちなみに、PostmanでもgRPCを使えるらしいですが(参考)、GUIを使う必要があったりjsonファイルへのエクスポート等の運用面においてデメリットがあると感じているので、今回は選択肢に入れませんでした。
gRPC E2Eテストの実装
実際のE2Eテストの実装とテスト対象のgRPCサーバについて解説します。
サンプルリポジトリはこちらです。
gRPCサーバの実装
gRPCサーバの実装について簡単に解説します。作ってわかる!はじめてのgRPCを参考にしてるので、gRPCサーバの実装方法の詳細についてはこちらの記事を参考にしてもらえると良いと思います。
ディレクトリ構成はREADME.mdにも書いてありますが、以下の通りです。
.
├── cmd
│ └── server
├── e2e
│ ├── gen
│ ├── plugins
│ │ └── grpc
│ │ └── pb
│ └── scenarios
├── internal
│ ├── dao
│ ├── model
│ └── service
├── pb
└── proto
├── echo
│ └── v1
└── user
└── v1
cmd/serverディレクトリにmain.goが配置されています。ここでは、protoファイルから生成されたファイルに実装されているRegisterXxxServiceServerを呼び出し、gRPCサーバにサービスを登録しています。
protoディレクトリにはprotoファイルが配置されており、bufにより生成されたgoファイルがpbディレクトリに配置されています。
internalディレクトリに今回扱うサーバの実装が書かれています。MySQL等のDBに接続するのが面倒だったので、daoパッケージでmockを実装しています。サーバのビジネスロジックについてはserviceパッケージに実装しています。
では、動作を確認してみましょう。一般的なHTTPサーバと異なりcurlが使えないので、代わりにgrpcurlを使います。
make runにより、gRPCサーバを起動する
make runを実行したターミナルとは別ウィンドウで grpcurl -plaintext -d '{"message":"hello"}' localhost:8080 echo.v1.EchoService/Echo を実行する
{"message": "hello"} というレスポンスが返ってくることを確認する
E2Eテストの実装
gRPCサーバが実装できたので、次はE2Eテストの実装に入っていきます。scenarigo/README.mdを参考にしてるので、詳細についてはこちらを参考にしてもらえると良いと思います。
e2eディレクトリにscenarigo関連のコードをまとめています。ポイントとしては、protoファイルから生成されるファイルをe2e/pluginsディレクトリにも配置していることです。理由としては以下の二点です。
シナリオテストを記述するyamlファイルからクライアント生成のgoファイルを呼び出したい
アプリケーション本体との依存を無くしたい
scenarigoのcontributorである@zoncoenさんの資料に以下のようなスライドがあったのでpluginsディレクトリにしています(この方法がわからずに筆者はかなりの時間を使ってしました、、、)。

シナリオテストについてはe2e/scenarigosディレクトリに実装しています。
では、動作を確認してみましょう。
cd e2e
make init(go installによりscenarigoをインストール)
make build(scenarigo buildによりpluginsをビルド)
make test(scenarigo testによりE2Eテストの実行)
以下のような実行結果が得られる
scenarigo run
ok scenarios/echo.yaml 0.011s
ok scenarios/user.yaml 0.008s
E2Eテストはどこまで書けばいい?
今回使用したリポジトリでは、以下のテストケースしかE2Eテストとして実装していません。筆者は、E2Eテストではこのぐらいのテスト、つまり、HTTP Status Codeでいう200と4xx系の合計3~5つを実装すれば良いと考えています。
システムに期待する正常な動作(HTTP Status Codeでいう200)
リクエストが不正な場合(HTTP Status Codeでいう400)
対象とするリソースが存在しない場合(HTTP Status Codeでいう404)
あるべきソフトウェアテストの姿として「テストのピラミッド」が持ち出されると思います。これは、UIテスト(E2Eテスト)よりクイックに実行できるユニットテストの数を多くしようというものです。

今回使用したリポジトリでもユニットテストでUserの準異常系に対するテストをinternal/model/user_test.goに実装しています。しかし、E2Eテストでは、「nameが空」という限定的なパターンのみ実装しています。
最終的なインプットとアウトプットをテストするE2Eテストを多く実装すれば良いと考える方もいると思いますが、そうするとユニットテスト等のCIで実行される自動テストによって担保されている内容までE2Eテスト実装する必要があり、実装する時間やそれを運用する時間を合わせて考えるとコストパフォーマンスが悪いと考えています。
しかし、これまでの話を覆すようですが、結局一番大事なのはそのプロダクト(プロジェクト)のステークホルダー間で合意が取れていることだと思います。「modelパッケージのテストカバレッジは80%以上を保とう」「ユニットテストは書かずにE2Eテストでカバーしよう」などが合意が取れ、言語化(ADR等で開発者全員が閲覧できる状態)されており、運用されている(PRレビューやlinter整備)状態が理想であると考えています。
まとめ
本記事ではgRPCサーバに対するE2Eテストをscenarigoを用いて実装する方法について説明しました。また、E2Eテストをどこまで書けばいいのかについても筆者の考えをまとめました。
ワンキャリアでは、サーバーの実装に主にRuby(Rails)を使用していますが、今後はGoで実装されたプロジェクトも開発予定です。Goに興味がある方や自信がある方、そして「力を貸してあげたい!」という方は、ぜひお気軽にお問い合わせください!ざっくばらんに話しましょう!
参考資料
▼ワンキャリアのエンジニア組織のことを知りたい方はまずこちら
▼カジュアル面談を希望の方はこちら
▼エンジニア求人票