短期間での新規プロダクト開発における「コスパの良い」Goのテスト戦略

この記事は GMOインターネットグループ Advent Calendar 2024 15日目の記事です。

はじめに

GMOペパボでは、マルチプレイ用ゲームサーバーを簡単にセットアップできる「ロリポップ! for Gamers」を展開しています。今年の2月に生まれたこのプロダクトですが、なんと開発開始から13営業日でユーザーへの提供を開始するというスピード感で誕生した歴史を持ちます。開発当初から関わっていたエンジニアの目線から、この短期間での初期リリースを支えたバックエンドの「テスト戦略」について、うまく行ったことや、今後の課題についてお話ししていきたいと思います。

ロリポップ! for Gamersとは?

テスト戦略についてお話しする前に、テスト対象である弊サービスについてお話しさせてください。

ロリポップ! for Gamersとは、2024年2月29日に無料モニターへの提供を開始し、4月15日に正式リリースされた、「サーバーの専門知識がなくても簡単にゲームのマルチプレイ専用サーバーが立てられるサービス」です。マインクラフトをはじめ、パルワールド、FiveM(GTA V)、ARKなどといった人気ゲームのサーバーを簡単に立てて遊ぶことができます。現在は有料提供をしており、マインクラフトのサーバーは月額800円(2GBプラン)から立ち上げることができます。

開発開始

ロリポップ! for Gamersは2月9日にプロジェクトが発足され、さっそく開発が始まりました。バックエンドの開発言語はGo、またAPIを作成するためのライブラリとしてConnectを選定しました。Connectを選定したのはprotobufによるスキーマ駆動開発を行いたかったことと、protovalidateをはじめとしたprotobufのエコシステムを使って、容易に制約をかけつつ安全な開発を進めることができるという理由からです。より詳しい技術選定に関しては「ロリポップ! for Gamersの立ち上げ/lolipop for gamers launch」もご覧ください。

テスト戦略の決定

技術選定後、ユーザーへの価値提供を担保するためにバックエンドAPIのテスト戦略を決定する必要がありました。

https://speakerdeck.com/takumakume/lolipop-for-gamers-infrastructure?slide=9

そこで、我々は以下の方針に決定しました。

定義したシナリオ(「ログインできる」、「サーバーを立ち上げる」など)に沿って、実際にちゃんと動くのかを主軸にテストする。

テスト戦略決定の理由

こうした方針に決定した理由を説明します。

今回は初期リリースのため、内部の構造や実装がしっかりと定まっておらず、小さいテストは書いてもすぐ使えなくなる可能性がありました。そのため、外側から実際にちゃんと動くのかを中心にテストすることで、内部の変更に強くしたいというニーズがありました。

また、「実際にちゃんと動くのか」をしっかり担保することによって、安心してユーザーに価値提供できる状態に持っていきたいという思いもありました。そのため、ここを担保するためにさまざまなテスト手法を使い、それに伴うトレードオフも受け入れるという判断をしています。

とはいえ、トレードオフは小さい方が良いです。そのため、どうすればより効果的にこの方針に従ってテストを書いていけるのか、「コスパの良い」方法を考え、実行していくことにしました。

コスパの良い方法を考える

「実際にちゃんと動くのか」をテストしようとした場合、テストスコープとしては、ユニットテストより統合テスト、統合テストよりE2Eテストが良いはずです。比較した時に、実際の環境への忠実性が高いからです。

E2Eテストで動作を担保できる範囲

しかし、そのまま書くのではなく、まずはコスパの良い書き方の方針を考えることにしました。コスパを考える上でわかりやすい考え方として、テストサイズの考え方があります。

テストサイズとは

GoogleのTestingブログで説明されている、テストの種類の定義の曖昧さをなくすことで、認識のブレを無くそうという考え方の一つです。ユニットテスト、インテグレーションテスト、E2Eテストといったテストスコープの解釈のブレを無くすために、分類の軸を設けて複数のサイズに分けるのが基本的な考え方になります。

https://testing.googleblog.com/2010/12/test-sizes.html

上記の図の通り、Googleの開発チームでは、ミディアムテストは、「1つのマシンの中に閉じている」テストと定義されています。つまり、外部に疎通せず、そのマシンで完結するテストのことを指します。逆に言えば、テストの中で外部のテストサーバーに接続をしに行ったりしたら、それはラージテストという別の分類になります。

ラージテストになると、さらに実際に近い環境になるため忠実性が上がりますが、保守にかかるコストがかかったり、実行時間の増加などがトレードオフとなります。

スモールテスト、ミディアムテスト、ラージテストの3分類はあくまでGoogleの開発チームで使っているものではありますが、分かりやすいためこの記事ではこのテストサイズの定義を使ってコスパについて説明していきたいと思います。

テストサイズとテストスコープからコスパを考える

https://levtech.jp/media/article/column/detail_496/

t-wadaこと和田卓人さんの図が「コスパの良い」テストについて説明するのに分かりやすかったので引用させていただきます。

外部サービスへの疎通をするテストは、テストサイズで言うと「ラージテスト」になります。外部サービスへの疎通を含むため、先述した通り各コストが増大するのがトレードオフとなります。

「実際にちゃんと動くのか」だけを考えてテストをそのまま書こうとすると、図の一番右であるラージテストになってしまいます。しかし、ミディアムテスト、つまり図の左の方に落とすことでコスパ良く実現できないかを考えました。

そこでスタブサーバーです。

外部サービスのスタブサーバーをhttpstubgrpcstubといったGoのパッケージを使って作成し、外部サービスへの疎通をテストから排除できるようにしました。このスタブサーバーについては、テスト対象が「実際にちゃんと動くのか」を本番と同様に、リクエストからレスポンスまで通して確認できるように作成しています。これにより、テストが単一マシンの中で実行できるようになるので、テストサイズが「ミディアムテスト」に落ちます。また、これらのスタブサーバーはgoroutineで実現できるので、同一のGo Runtime上で動かすことができ、軽量で安定した運用が可能です。このスタブサーバーにより、安定性があり、速度も速く、「コスパ良く」テストを実行できるようになりました。

goroutineベースのミディアムテストを使うことによるメリットに関しては、 弊社エンジニアの k1LoWnet/http/httptest.Server のアプローチをテスト戦略に活用する / Go Conference 2023 にも詳しく書かれているのでぜひご覧ください。

一方で、初期リリース後、「実際にちゃんと動くのか」を担保するために、ラージテストを書くことを許容したケースもありました。例えば決済を伴うテストです。決済サービスがテストサーバーを用意してくれているため、それを用いることで簡単にテストを書くことができることが分かっていました。そのため、スタブサーバーの実装コストも含め考えた結果、ラージテストを選択することによるコスト増を受け入れ、決済サービスのテストサーバーを使ってテストを書く決断をしました。

runnでコスパ良くテストを書いていく

テストサイズをコスパ良く書けるような方法を考えたところで、同時にどのように人間がテストを書いていくとコスパが良いかを考えました。そこで、 runn というツールで実行できるシナリオテストを書いていく方針を取りました。

runn は k1LoW が開発した、YAMLに記述されたシナリオを実行するためのツールです。主にシナリオベースのテストや、特定のワークフローを自動化するためのツールとして使われています。複数のRPC、DBを、外からシナリオに従って実行できるため、「実際ちゃんと動くのか?」をテストするのに有効なツールの一つであると言えます。また、Goのテストヘルパーパッケージも提供されているため、これをうまく使うことでGoのテストに組み込んで実行することができます。

また、もう一つのrunnの優れた点はそのテストの書き心地です。runnではテストシナリオをYAML(runnにおいてはランブックと呼称)として記述します。弊社のエンジニアは別のプロジェクトでGitHub ActionsやKubernetesを使っており、YAMLを書くことに慣れていたので、一つのサンプルさえあれば簡単にテストを書き始めることができました。

そして副次的なメリットですが、テストが分かりやすいドキュメントとして機能するところも強みです。下記は実際に開発で使っている「サーバー情報更新」のシナリオのファイルですが、どのような流れでサーバー情報更新ができるようになるかが分かりやすいと思います。

ロリポップ! for Gamersのサーバー情報更新のシナリオテスト(一部折りたたみ)

こうしたメリットから、シナリオテストをランブックに書いていくことにより、テストの実装コストを下げ、より「コスパ良く」テストを書けるようにしました。

以上の方針に従うことで、

  • 「実際にちゃんと動くのか」のテストの一部をミディアムテストに落とせる
  • テストを書く際の学習コストが低い

この2つが満たせる、初期リリースにおいて「コスパの良い」戦略を取ることができました。

うまくいったこと

実際に上記のテスト戦略を使っていきましたが、初期開発時の各フェーズにおいてこの戦略はうまく機能していました。想定していた通りテストの記述はすぐに慣れますし、include: などのrunnの機能を活用することでうまく記述の工数削減をすることができました。

レビュー側からしても、 ランブックに desc: という項目があるおかげで各ステップで何をやっているかが分かりやすく、テスト対象が明確だったので、レビューのリードタイムも短かったと思います。

そして、フロントエンドを含めた手動での検証作業がスムーズでした。バックエンドの動作は正常系はほぼシナリオテストで担保できているので、フロントエンドや繋ぎ込みの部分にバグがなければ特に問題なく検証を終えることができました。

このようにテスト戦略がうまく行ったことが、13営業日でリリースを完了できた一要因であると思っています。

また、初期リリース後に恩恵を受けたこともあります。

1つ目は、リリース後のバグ発生率が体感的に低かったことです。機能数が少なかったとはいえ、これだけの短期間でリリースしたものが大きな問題なく動いたことで、運用負荷が減り次の開発へすぐに着手することができました。インシデントもリリース後1ヶ月間は0件でした。

2つ目は、書いたテストがほぼ構造の変更なしで現在まで動いていることです。内部の変更に強い、外からのテストを主軸に採用したおかげで、変更に強くメンテナンスコストを下げることができたと考えています。

生まれた課題

初期開発においてこの方針はうまく機能し、ユーザーにいち早く機能を提供する一助になりました。しかし、一部は予想していたことではありますが、以下のような課題も抱えています。

  • テスト実行時間の増加
  • Flaky testの増加
  • ラージテストの増加

初期リリースでテスト戦略を決定した後、その後はテストに関してあまり意思決定がなく、そのまま同じテスト戦略を暗黙的に使ってしまっていました。そのため、本来であればスモールテストでカバーできるテスト内容であっても、書き慣れた方法でテストを書いてしまい、結果としてテストサイズがミディアムになる、「コスパの悪い」テストを書いてしまうことが続いていました。

その結果、テスト実行時間の増加が著しく、初期リリース時は短かったもの、10分〜20分ほどかかるようになってしまい、開発効率や体験が落ちることになりました。

また、ミディアムテストは実際に同一マシン内の複数サービスをみてテストを行うため、適切に並行処理などが行われないことで、Flaky testが増える原因になりました。runnには concurrency: というオプションがあり、これを適切に付与することで、同じデータリソースを操作・参照するランブックを同時に実行しないようにするなどしてFlaky testを防ぐことができるので、これを活用するべく人間やシステムがオプションを適切に付与するための仕組み作りが必要となりました。

そして、初期リリースで選択したラージテストの許容が、現在は負荷になっている部分があります。特に、決済システムのテストサーバーを含めたラージテストを使うテストが増大してしまっています。最初こそ数が少なく安定して動いていましたが、テストが増えるにつれテストサーバーのRate limitに触れる機会も増え、こちらもFlaky testの一因となってしまっています。

次のテスト戦略へ

リリース初期のテスト戦略は、初期リリースの短期間の実現に大きな貢献をしました。しかし、プロダクトの規模が大きくなることに合わせて、テスト戦略も調節する必要があると感じています。

ここで、テストピラミッドという考え方があります。

https://levtech.jp/media/article/column/detail_496/

こちらはGoogleの開発チームのテストサイズの定義に基づき、t-wadaさんが表現した望ましいとされているテストの比率の図です。これを我々のチームに当てはめて考えると、

  • ラージテストが増えている
  • スモールテストで書けるテストをミディアムテストで書いている部分がある

ということで、明らかに三角形になっていないと思います。

当時は実装が固まっていないこともあり、逆三角形型も許容する判断をしていたのですが、初期リリースが終わったため、今後は下記のような方針をとりたいと考えています

  • スタブサーバーを実装コストを投じて実装し、ラージテストを削減する
  • 既存のミディアムテストを整理し、スモールテストでカバーできる範囲は移行していく
  • 新規のテストに関しては、どのサイズでテストを書くか適切に判断できる仕組み・文化づくりをする

これにより適切なテスト比率に近づけ、理想的なテストピラミッドを目指すことで、現状発生している課題の解決、そしてよりコスパ良くユーザーへの価値を担保できるテストを書いていけるようにしていきたいです。

また、自らがテスト戦略にオーナーシップを持つエンジニアになることで、定期的にテスト戦略の見直しと、テストの改善を行うようにしていきたいと考えています。

まとめ

今回は短期間での新規プロダクト開発において選択した、「実際にちゃんと動くのか」を主軸としたテスト戦略と、その結果と今後の課題についてお話しました。

新規プロダクトの着手から1年ほど経った今改めて感じるのは、テスト戦略は定期的にブラッシュアップが必要だということです。そのときに選択したテスト戦略は、仮にその先の開発を見通したものであったとしても、事業や開発のフェーズにおいて、テストのトレードオフとなる値は常に更新されていきます。定期的にテスト戦略を見直すことで、より強固で効率の良いプロダクト開発が行うことができ、結果としてユーザーに大きな価値を提供できるようになるのではないでしょうか。

この記事がテスト戦略を考えている開発者の一助となれば幸いです。

ブログの著者欄

大浦 優太郎

GMOペパボ株式会社

Webエンジニア。2022年4月GMOペパボ株式会社新卒入社。現在はロリポップ for GamersでエンジニアとしてGoを用いた開発に関わる。

採用情報

関連記事

KEYWORD

採用情報

SNS FOLLOW

GMOインターネットグループのSNSをフォローして最新情報をチェック