この記事は、CYBOZU SUMMER BLOG FES '24 (kintone Stage) DAY 1の記事です。
初めに
kintoneチームの前田です。 kintoneチームでは最近E2Eテストを部分的に実行するという実験を始めています。 これによりテストの実行時間が短縮されフィードバックが迅速になり、 たとえばフロントエンド刷新に貢献するのではないかと期待しています。 本記事ではこのE2Eテストを部分的に実行するという取り組みについて紹介します。
E2Eテストと問題点
kintoneチームのE2Eテストは機能が期待通り動いていることをユーザー視点で確認するテストです。 E2EテストはSeleniumとJavaで実装されています。 試験対象であるkintoneは本番環境とほぼ同じ構成で開発環境にデプロイされ、これに対してテストが実行されています。 kintoneチームでは通常機能が追加される度にE2Eテストを追加しており、kintoneのリリース当初から続いています。 これによりある程度の品質を保つことができたと考えてはいるのですが、E2Eテストの数自体は膨大になっています。
実際、E2Eテストを全て実行するには現在では60分くらいかかっており、この実行時間が問題になっています。 できるだけ並列実行するなど実行時間を短縮する改善は施されてきてはいましたが、それでもこれだけ時間がかかっています。 E2Eテストの実行タイミングはフィーチャーブランチをメインブランチにマージする前と、 リリースアーカイブを作成する前です。 これらのタイミングでE2Eテストを流しているため、フィーチャーを追加したことで既存の機能を壊してないかのフィードバックが遅れる、リリース作業が重くなるなどの問題が生じています。
マージ前のE2Eテスト部分実行
今回紹介する仕組みは、フィーチャーブランチをメインブランチにマージする前に実行するE2Eテストの時間短縮を目的としたものです。 kintoneチームではスクラムを使った開発プロセスになっており、毎スプリントで機能のほんの一部分に対して修正が入ります。 こうした修正はフィーチャーブランチに対して行なわれ、メインブランチにマージする前に全体のE2Eテストが実行されます。 つまり、機能のほんの一部分を修正するのにもかかわらず、E2Eテストは全体が実行されるというプロセスになっています。
全体のE2Eテストが実行されることで、機能修正と関係ないどうせ通るであろうテストも実行されてしまっており、このことでマージまでに無意味に時間がかかっていると考えられます。 たとえば、ファイルから読み込むという機能を修正しても、ピープルのコメントにいいねできるなどの修正と関係ないテストが流れるわけです。 ファイルから読み込む機能を修正してメインブランチマージする前のE2Eテストはファイルから読み込む機能に絞って実行し、 ピープルのコメントにいいねできるなどの関係ないテストは除外することで、テスト実行時間の短縮が期待できます。 どうせ通るテストを実行することは無駄な作業であり取り除かれるべきです。 なぜなら、そのようなテストを実行しても製品に価値が付与されないためです。
そこで、kintoneの機能に絞ってE2Eテストを部分実行する仕組みを導入しました。
まずはkintoneのE2Eテストを機能毎に分割しました。
たとえば、ファイルから読み込む機能のE2Eテストはすべてcom.cybozu.kintone.e2e.importfromfile
下にある、という状態を作りました。
すると、ファイルから読み込む機能のテストのみを実行するにはcom.cybozu.kintone.e2e.importfromfile
下にあるテストのみを実行すればいいということになります。
これはGradleのTestFilterにあるincludeTestsMatchingを使って次のように書けば実現できます。
includeTestsMatching "com.cybozu.kintone.e2e.importfromfile.*"
E2Eテスト実行時にcom.cybozu.kintone.e2e.importfromfile.*
というようなパターンをパラメータとして受け取るようにすれば、
E2Eテストを部分実行できるようになります。
実際にファイルから読み込む機能に絞ってE2Eテストを部分実行してみたところ、 60分かかっていたテストが30分弱で終わるようになりました。 したがってフィードバックを得るために約30分の短縮になります。 E2Eテストが落ちると修正してまた流す、というようなサイクルを何回か繰り返すことになると思うので、 この短縮効果は大きいと思います。 このようにフィードバックが迅速に返ってきそうなことがわかってきており、今後の開発速度の向上が期待できます。
メインブランチにマージする前のテスト部分実行は、 既存の仕組みや製品品質を大きく変えることがなかったため、試しやすかったように思います。 仕組みにおいては、変更箇所は機能に従ったテストの分割とテスト実行時のパラメータの追加です。 これらを元に戻すのは難しくないですし、 機能に従ったテストの分割はそれだけでもテストの管理がしやすくなるといった価値があります。 製品品質においても大きく変わることはありませんでした。 E2Eテストを部分実行するのはメインブランチへのマージ前だけであり、 リリースアーカイブ作成前に全体のE2Eテストを流す部分は良くも悪くも変えていません。 今回の改善はメインブランチへのマージとリリースアーカイブ作成とで2回流れていたE2Eテストを、言わば1.5回だけ流すように変えるというものになります。 結局リリース前までには全てのE2Eテストが流れるためリリースされるkintoneの品質は下がりません。
いつ部分実行が可能か
一番わかりやすいのはフロントエンド刷新やフロントエンド刷新後のコードを修正したときです。 フロントエンド刷新ではチームが自律的に動けるようになることを重視しているため、 あるチームの変更が別のチームに影響を与えないようにコードが分割されています1。 そのため、ある機能のコードを修正して別の関係ない機能が変わるということが構造上ありえなくなっています。 したがって、フロントエンドである機能を修正してもその機能に対応するE2Eテストだけ部分実行すれば問題ありません。
フロントエンドだけでなくサーバーサイドにもテスト部分実行を適用していくには、 先ほど曖昧に述べた「関係ない」ということを精緻化していく必要があると考えています。 なぜなら、サーバーサイドでは機能間に依存関係が発生しており、 他の機能に影響を与えないということをフロントエンドのようにコードの構造だけで担保できないためです。 たとえばアプリ設定機能はアプリ機能から依存されます。 というのも、アプリ設定機能で設定した内容はアプリ機能から参照されるということがkintoneの振る舞いだからです。 したがってフロントエンドと同じ論理で適用することはできません。 これは今後の課題として探求していきたいと考えています。
E2Eテストの分割とQAメンバー
テスト部分実行の実験はkintoneチームのQAメンバーのおかげでスムーズに進めることができました。 テスト部分実行の仕組みを作るにはテストの機能ごとの分割が必要になります。 テストが膨大にあるのでテスト分割も膨大な作業になるかと思われたのですが、 とくにそのようなこともなく、単純な機械処理で終わらせることができました。 これはQAが、事前にE2Eテストを機能毎に整理していたからでした。
各E2Eテストはe2e-1234
のようなIDが振られており、QAはどのテストIDがどの機能に対応するのかを整理していました。
このIDは実装の中にもpublic void e2e_1234() { ...
のようにメソッド名に残すようにしていました。
したがって、テスト分割ではQAが整理したデータを使ってgrepすることで、
ある機能のE2Eテスト実装を機械的に検索し、所定のパッケージ移動することができました。
kintoneチームのQAが事前にE2Eテストを機能毎に整理していたのはチーム分割が理由です。 kintoneチームではkintoneの機能ごとにサブチームを作り、その機能のオーナーシップを持って開発を進めるような体制になっています2。 たとえば次の記事で紹介されているようにアプリ設定機能にオーナーシップを持つサブチームがあります。
このサブチームにはソフトウェアエンジニアだけでなくQAも所属しています。 QAは自分の所属するサブチームで実施、修正するE2Eテストを把握できるようにするため、 チームごとにテストを割り当てていく作業を自律的に進めていました。 この割り当て作業によってE2Eテストが機能ごとに整理され、そのおかげでテスト分割を機械的に済ませることができました。 このようにチーム分割がきっかけでテストも整理され、それがE2Eテストの部分実行につながりました3。
加えて、E2Eテストの分割は現在では主にQAが行っております。 kintoneチームのQAは自動テストを実装可能にする取り組みを行っています。
E2Eテストの分割作業はJavaで実装されたE2Eテストを所定のパッケージに移動して、 コンパイルが通るようにすることなのですが、 Javaの経験が少ないQAにとってこのタスクは最初の一歩としてちょうど良いようです。 さらに、Javaの経験があるQAの中にはテスト分割を終えた後で、テスト構成のリファクタリングを行っている人もいます。 このようにテスト分割はQAの活動の幅を広げる機会にもなっています。
今後の課題
今後の課題としては、テスト部分実行を開発プロセスに定着させるということがあります。 テスト部分実行はまだ効果がありそうなことが分かった段階であり、 開発プロセスの中に組み込まれたとは言えない状態です。 E2Eのテスト分割もまだまだこれからです。 現場で開発しているサブチームと情報交換しながら実験を進めていきたいと考えています。
テスト部分実行が定着したら、 E2Eテストを他の適切なレイヤーでのテストに置き換えることで、E2Eテストを減らしていきたいと考えています。 テスト分割により、たとえばファイルから読み込む機能のE2Eテストは60個ほどであることが分かっていて、 どのテストに一番時間がかかっているかも把握可能になっています。 2分かかるテストがあったとして、60分のうちの2分だとたいしてインパクトはありませんが、 テスト部分実行により30分のうちの2分となったため減らすことのインパクトが上がっています。 また、E2Eテストを減らしていくことで、 リリースアーカイブ作成前に流すE2Eテストの実行する時間も短縮することができるので、 これによりリリース作業が軽くなることも期待しています。
先ほど述べたようにフロントエンドだけでなくサーバーサイドにもテスト部分実行を適用していくことも重要な課題です。 これができればフロントエンド、サーバーサイドに関係なく、どのような修正であってもそのテストを部分実行できるようになります。 するとkintoneの開発作業自体がその機能で閉じるようになります。 これはkintoneのチーム分割との相性も良く、大きな改善になるのではないかと期待しています。
サーバーサイドでのテスト部分実行は、サーバーサイドコード分割を済ませれば適用できるのではないかと考えています。 サーバーサイドコード分割は機能の実装を専用のパッケージに配置し、他の機能からある程度独立して管理できる状態にします。 したがって機能の修正は基本的にその機能に閉じ、機能外に影響が出なくなるので、部分実行でほぼ問題はないはずです。
しかし、前述の通りサーバーサイドでは他の機能からの依存が仕様上発生してしまうことがあるため、機能外にも影響が出ることがありえます。 このような依存に対しても、影響が出ていないか確認するテストを書くことで、部分実行可能な状態になるのではないかと考えています。
まとめ
E2Eテストの部分実行について紹介しました。 kintoneチームではE2Eテストの実行時間が問題になっており、 これを短縮するためにE2Eテストを部分実行する実験をしています。 実際、60分かかっていたE2Eテストが30分になるなど効果は現れてきております。 今後の課題としては、E2Eテスト部分実行の定着や、E2Eテストの削減、 サーバーサイドを修正する場合であっても部分実行を可能にすることがあります。 特にサーバーサイドを修正する場合でもテスト部分実行が可能になると、 開発作業自体がその機能に閉じるようになるため、大きな改善になると期待しています。 引き続き探求を続けていきたいと思います。
- 詳細はフロントエンドが技術的にもチーム的にも分割されているを参照ください。↩
- 詳細は肥大化・モノリス化するプロダクト開発組織を自律的で小さなチーム群に変えていく ―kintone開発チームの事例―を参照ください。↩
- チームを分割した結果テストが分割されたということはチームトポロジーの逆コンウェイ戦略にある通りのことが起きていたということだと思っています。チーム分割はこの逆コンウェイ戦略を狙った取り組みでした。今回のことで逆コンウェイ戦略やチーム分割の効果を改めて確認することができました。↩