こちらはコドモン Advent Calendar 2023の2日目の記事です🎅
こんにちは! コドモンプロダクト開発部の加藤です。 今回はGaugeで並列実行を試した話を紹介します。
Gaugeとは
Gaugeは自動テストフレームワークです。 コドモンでは、ブラウザ自動操作テストツールのPlaywrightと併用して、E2Eテストを作成しています。
テストの操作を、実行ファイル(specファイル)に日本語で表現できることが特徴的です。 ビジネスサイドの人でも読みやすいテストになるため、プロダクトの仕様書としての役割も果たせます。
Gaugeテストについては過去の記事でも触れているものがあるので、詳しく知りたい方はこちらの記事もご覧ください👇
tech.codmon.com tech.codmon.com
並列化に取り組んだ背景
GaugeのE2Eテストが増えたことで、実行時間が長くなり始めたことが並列化に取り組むきっかけでした。
今回の取り組みの対象プロダクト(保護者アプリ)は現状ローカルでGaugeテストを実行する運用*1であり、テストの実行時間が長いことが開発者にとっての大きなペインにつながります。
実行時間が長いと、頻繁にテストを実行するのがためらわれるようになり、その結果、テスト失敗に対するフィードバックを受け取るのが遅れがちです。 また、実行頻度が下がりがちな状態を放置した場合、テストシナリオが古くなっていること・不安定になっていることに気づくのも遅くなります。その結果、テストがメンテナンス不足となり、テスト自体の信頼性が下がるというリスクがあります。
今後もテストシナリオは増えるので、それに伴って実行時間は長くなる見込みです。 そのため、この問題は早めに解決する必要があると感じ、並列化による実行時間短縮に取り組みました。
並列化の取り組み
利用したコマンド
こちらのGaugeのコマンドを利用して、並列化を試しました。
並列実行するspecをタグでフィルタリングできることが特徴です。 タグに該当しないspecを直列で実行した後に、タグに該当するspecを並列実行します。
私たちのE2Eのシナリオには並列で実行できないものがいくつか存在します。 例えばデータの更新のテストは、テスト後にDBを初期状態にリセットする処理を実行していて、このDBリセット処理が他のシナリオの実行中に行われるとテスト結果に影響を与えてしまいます。 そのため、このようなDBリセット処理が必要なシナリオは直列実行する、それ以外のシナリオは並列実行する方針で進めていきました。
コマンド利用時の注意点
- Gauge v1.0.5以上で利用可能
- Gaugeの「Experimental Feature(実験的機能)」扱いであることを認識しておく
- ただ、v1.0.5のリリースが2019年のため、リリースされてから時間は経っています
- 今回実際に実行してみて、不安定さは特別感じなかったです
実施手順
1. 設定
/env/default/default.propertiesに以下の設定を追加します。
allow_filtered_parallel_execution = true
2. タグの付与
specに任意の名前のタグを設定します。 今回はテスト後にDBを初期状態にリセットする処理を入れるもの(=直列実行するもの)に「stateful」のタグをつけました。
※なぜ並列実行するものではなく直列実行するものにタグを付与したのか?は手順4で触れます。
# 新規登録 Tags: stateful ## 招待されたアカウントで登録できること * 保護者アプリを開く * "家族から招待された方はこちら"ボタンを押下する // ……以下、シナリオの実装
3. データセットアップ周りの整備
3-1. 初期データのセットアップ
テストの実行前に1度だけ初期データをセットアップします。 データセットアップ用のシナリオを作成し、これをすべてのテスト実行前に1回だけ実行するようにしました。
※この部分は並列実行との兼ね合いで、悩んでこの形にしました。詳細は「難しかったこと」の項で触れます。
# セットアップ Tags: setup ## 初期化 * setup // このシナリオを実行すると、DBに初期データをセットアップする
3-2. statefulタグがある場合のテスト実行後処理
statefulタグ付きのテスト後に、DBを初期状態にリセットする処理を実行します。 これはstatefulタグを指定したAfterScenarioフックに記述することで実現します。
@AfterScenario({ tags: ['stateful'] }) public async afterScenario() { await Databases.setup() }
4. コマンド完成
最終的な実行コマンドは以下の通りです。 (実際には以下相当のコマンドをMakefileに定義することで、開発者が1コマンドで実行できるようにしています。)
gauge run specs/setup/setup.spec && gauge run -n=5 --parallel --only '!stateful' --tags '!setup' specs
データセットアップ→直列実行→statefulタグ付き以外のテストを5並列で実行、という順序でテストが実施されます。
ポイントとしては、並列実行の対象に「statefulタグがついたもの以外」を指定したことです。 これにより明示的にstatefulタグをつけたシナリオ以外は並列実行されます。本当は並列実行でもよいシナリオがタグの付け忘れで直列実行にならないように、この指定の仕方を選びました。
よかったこと・難しかったこと
よかったこと
実行時間の短縮
全テストの実行時間が約4分短縮できました🎉(Before:14m28.363s → After:10m19.198s)
これからシナリオが増えていくことを見越すと、今後さらに恩恵を受けていけるのではないかと思います。
テストが実行しやすくなったことで、開発中にテストのフィードバックを早く受けられる・テストのメンテナンスがこまめにされる、という効果が得られるか?は今後も継続して確認していきます。
難しかったこと
初期化処理の実現
今回並列化にあたり、テストデータの初期化を「データセットアップ用のシナリオを作成し、すべてのテスト実行前に1回だけ実行すること」で実現しました。
これまでは、BeforeSuiteフックで初期データのセットアップ処理を記述し、それがテスト実行前に1回実行されていました。 すべてを直列実行する場合は問題なかったのですが、並列化コマンドを試す中で、並列実行前にBeforeSuiteフックに記述した処理がランナーの数だけ複数回実行されることが問題となりました。
BeforeSuiteフックではタグで実行有無を指定することができないため、タグの指定でコントロールするなどもできず、結果として今回の形に落ち着きました。
Makefileに必要なコマンドをまとめることで、そのコマンドを利用する限り初期化処理がもれなく行われるようになっていますが、テスト実行の前提条件として「初期化処理を明示的に実行しなければいけない」ことを理解する必要がある状態になっているのは改善ポイントかなと思っています。 他にいい方法を見つけられたら、よりよい形にしていきたいです。
最後に
E2Eテストの実行時間長くなりがち問題は開発中によくぶつかる悩みだと思うので、誰かの参考になったら嬉しいです! 引き続きアドベントカレンダーをお楽しみください🎄
*1:将来的にはCIでの実行を実現したく、準備中です。