はじめに
こんにちは、FAANS部フロントエンドブロックでWeb開発をしている平舘です。
Webフロントエンドのテスト戦略って、結局どうすればいいのか、よくわからなくないですか?
この記事では、FAANS Webアプリケーション開発におけるテスト実装の歴史を「リリース期」「急成長期」「現在」という3つの開発フェーズに分けて振り返ります。プロダクト立ち上げからのリアルな現場感とともに振り返りつつ、主にテスト配分についてチームで議論しながらプロダクトへ反映していった歴史のレポートになっています。みなさんのテスト戦略の見直しや実践のヒントになれば幸いです。
目次
背景・課題
現代のWebフロントエンド開発において、テスト手法1やツールは多様化しています。ユニットテスト、E2Eテスト、ビジュアルリグレッションテスト(VRT)など、それぞれの用途や目的が異なるため、適切な組み合わせが求められます。しかし、多様な選択肢がある一方で、次のような課題が生じがちです。
- フロントエンドのテスト戦略に自信がない
- テストを書くのがストレス
- メンバー間でテストへの向き合い方が違う
- テストの効果を実感しにくい
FAANSでも開発フェーズに応じてこのような課題が生じ、向き合ってきました。
この記事で語らないこと
- こうすればうまくいくという万能な正解や方法論。
- テストツールの具体的な使い方。
前提:FAANSについて
内容を理解しやすくするために、まずFAANSのプロダクト背景を簡単に説明します。
- サービス特性: Webは主にバックオフィス機能を提供し、ユーザーの入力・操作が多く発生する。
- 開発体制:
- ウォーターフォール開発。
- 独立したQAチームが存在。
- アーキテクチャ構成:
- React SPA: 業務利用ツールのため採用。
- OpenAPIによるAPI開発: YAMLベースでAPIスキーマを定義し、クライアントコードを自動生成。
より詳細なプロダクトの説明は弊バックエンドブロック田島による SLOの導入は早ければ早いほどよい 〜FAANSの事例とその効果〜 - ZOZO TECH BLOG に書かれています。ぜひ併せてお読みください。
開発の歴史とテスト戦略の変遷
以下はざっくりと各フェーズの開発状況を俯瞰した表です。
フェーズ | 新規実装 | 変更・リファクタ | QAバグ検出 |
---|---|---|---|
フェーズ1 リリース期 |
↑最大 | →少ない | ↑最大 |
フェーズ2 急成長期 |
↗︎多い | ↑爆増 | ↗︎健在 |
フェーズ3 現在 |
↗︎安定傾向 | ↗︎多い | →安定傾向 |
これをふまえて、各開発フェーズで起きたことを振り返っていきます。
フェーズ1. 怒涛のリリース期
まずは立ち上げからリリースまでの、プロジェクト最初期のフェーズです。
起きたこと
(1) 関心の中心は、「何をつくるか」
どんなプロダクトでもそうだと思いますが、FAANSも立ち上げ当初は、事業価値の心臓となる部分をいかにスピーディに実現するかに集中していました。
将来的な展開を見据えながら、「誰に」「どんな価値を」「どれくらい」「いつまでに」「どうやって」提供するのか。不確実なものばかりのなかで、迅速な意思決定が求められました。
フロントエンドだけに閉じた世界で見ても、フレームワークやライブラリ選定、CI構築、実装指針のすり合わせなどを決める必要がありました。特に機能実装に直結する部分において、アーキテクチャ選定、実装などが山積みでした。
(2) こなれない実装
FAANSのテストの歴史について語るためには、この時期に実装されてしまったこなれない実装たちについて語らなければなりません。
useEffect()
を望ましくない場面で多用し、副作用が複雑に絡み合うことで、とてもテストを書けないようなロジックが量産されていました。
また、TypeScript本来の静的型チェックの効果も十分に発揮できていませんでした。特にnullableな値の扱いが未熟で、冗長なnullチェックによる認知不可が増加していました。
簡単な例としてアカウントの権限管理が挙げられます。FAANSでショップスタッフは必ずどこかのショップに所属しますが、上位権限である管理者アカウントの場合ショップ所属は任意です。これを表現する場合、所属先IDの有無は型で保証されている方が扱いやすいですが、未熟な型表現になってしまっていました。
// 型チェックが効果を発揮できていない type Account = { role: 'staff' | 'manager'; companyId: number; // 企業には必ず所属する shopId?: number; // ショップは staff のみ所属する }
// こっちの方が堅牢で扱いやすい type Account = { companyId: number; } & { role: 'staff'; shopId: number; } & { role: 'manager'; shopId: number | undefined; }
※実際の権限はもう少し複雑ですが、わかりやすくするため抽象化しています。
(3) jest-dom による差分チェック
仮想domスナップショットのリグレッションテストも試してみましたが、早々に断念しました。
デザインの方向性を試行錯誤しながら実装を進めている段階だったので、得られるメリットが小さく維持コストのほうが高かったためです。
(4) テストの目的
おそらくこれが最も本質的かつよくある問題ですが、メンバー間で共通認識を揃えられないまま、「ないよりあったほうがいいよね」ぐらいの認識でテストを実装していました。
結果として、目的地としてどの程度の範囲をどうやってカバーしたいのか意図がわからない、粒度のバラバラなテスト実装が散在してしまいました。特に、詳細度の高すぎるユニットテストが多く作られてしまいました。変更の影響を受けやすく、この時点で継続的に担保したいアプリケーションレベルの保証材料にならないテストです。これらは完全に無駄なわけではなく目的次第では有効ともとれるものであるため、特に指摘もなく実装され続けていきました。
しかしプロダクトがローンチされていないこの時点では、そもそもデザインや仕様の根幹的な価値自体が担保されていない状況です。末端の入出力、つまり外部仕様の不確実性が大きいなかで、より内部のテストは資産価値の薄いものでした。これらは、のちにメンテナンスコストをかける価値を見出せず削除することになりました。
(5) QAチームの存在
それでも大きな問題なくスケジュール通りにリリースできたのは、QAチームの働きによるものです。頻繁な変更にもかかわらず手動テストでしぶとくテストしてくれました。その作業負荷は相当なものだったと思います、当時のメンバーには頭が上がりません。
この時点でE2Eテストの自動化案も出ましたが、以下のようなメリット・デメリットの観点のうち、特に初期コストを割くのが難しかったため手動テストのみで運用しました。
E2Eテストにおける手動/自動のメリット・デメリット
項目 | 手動テスト | 自動テスト |
---|---|---|
初期コスト | ◯:手動テスト用の環境を用意するのは必須。 | ×:環境や外部依存システムに応じた構築が必要。FAANSの場合、外部提供される認証システムとAPIが大きな障壁。 |
柔軟さ | ◯:テストの詳細度や観点を都度コントロール可能。UXの直感的な評価が可能。 | ×:静的なテスト詳細が必須。 |
探索的テスト | ◯:得意。 | ×:不得意。 |
再利用性・繰り返し | △:テスト対象が増えるほど工数が増えていく。繰り返すことでテスターの心理負荷は増える。 | ◯:実装されたテストは資産として反復して利用可能。結果としてカバレッジを大きくしやすい。 |
開発サイクルへの組み込み | ×:開発サイクルへのフィードバックは最後になる。 | ◯:CIで開発サイクルに組み込むことが可能。 |
リリース期のまとめ
目的面・方法面の両方で不確実性に向き合う日々のなか、自動テストに意識を割き構築していくのは、今から考えてもやはり難しく価値が薄かったと思います。思い切って自動テストを「積極的に書かない」判断が必要でしたが、その決定に至る基礎知識がない状態でした。
また、開発健全性という側面で、第三者的な目線から一定期間、開発状況についてのフィードバックをもらう動きをとれていればよかったと思います。現在ZOZOではWebフロントエンドで事業部を横断して情報共有していく動きが増えてきており、こういった問題は改善傾向にあります。
総じて、チームの共通認識に昇華できていない点において、テストをよく書くメンバー/そうでないメンバー、どちらも同質にテストへの解像度が低かったです。当時を絵にすると、こんな感じでしょうか。
フェーズ2. 疾風怒濤の急成長期
無事ローンチ完了。基幹機能の価値が証明され、さらに機能拡充が進みます。一方で、コードベースの複雑化や、既存コードの大規模な変更が頻発しました。
起きたこと
(1) 頻発する大規模なリファクタ
実装指針が徐々にアップデートされ、機能変更が入るタイミングで、設計方針に沿うよう既存実装を大きくリファクタする場面が増えました。
(2) 統一性のないコードベース
人的リソースの都合上、スポットで領域外のエンジニアが参加することも多く人の出入りが多くなり、コード量が増えるとともに複雑さも急増していた時期です。
テスト設計指針が浸透しないまま作成されたコードは、必要機能を満たしていながらも、俯瞰するとテスタビリティを欠いたり認知コストが高かったりしました。テスト指針という観点が抜けていることによって、設計議論の観点が暗黙的に低かったとも見ることができます。
(3) 自動テストへの関心
テスティングトロフィーという概念が話題になり始めたこともあり、この頃ようやくテスト実装指針の明確化に積極的に意識を割き始めました。
(1)、(2)のような状況で、安心して積極的にリファクタしていける環境を整えることが急務でした。
(4) Storybookやるやん
そんな折、Storybookに play()
機能2が実装されました。
FAANSでもUIカタログとしてStorybookは導入済みであったため、使い慣れたツールでテスト実装できるのは願ってもないことでした。また、UIを通してモック化したAPI通信をテストできるため、信頼性の高い結果を期待できました。
(5) Chromaticもええやん
Chromatic3を活用して、play()
の実行結果をスナップショットとして保存し、API疎通を含めたUIの整合性を担保しました。これによって、静的なビジュアル差分の確認にとどまらず、インタラクティブな動作を含めた総合的なテスト4となりました。
Chromaticは金額によってスナップショットできる上限が決まる料金体系のため、FAANSではPullRequestにラベルを付与することによってトリガーするようにしています。
過去のブログ記事で実装例も載せておりますので、ご参照ください。
(6) 信頼に足るテスト
ツールの学習コスト、MSWによるAPIのモック、CIの整備など、それなりの初期コストはかかりました。しかし、コストに見合うだけの信頼に足るテストをできている手応えがありました。
手応えの理由の1つは、開発サイクルにおいて素早くテスト結果を確認できることです。これまで自分たちの手動テストでしか担保できなかったE2Eに近いレイヤーでの動作保証を、CIでリグレッション検知できるようになりました。
もう1つは、検出した不具合や込み入った仕様を、素早く自動テストに反映できることです。Storybookの play()
では実装したインタラクションテストをステップバイステップで簡単にデバッグ実行できます。手元で画面操作を確認しながら書けるテストは、思い描く仕様をスムーズに実装できる優れた機能です。
(7) 設計視点の改善
テストを書く文化が根付いてくると、「テストしやすい実装」も実感を伴ってわかってきます。「これはテストしにくいよね」という観点でスムーズに会話ができるようになり、より疎結合で責務が明確な、よい設計を議論できる土台になりました。
優れた設計はテストしやすい実装となり、信頼に足るテストは自信につながります。リリース期には苦痛ですらあったテストのメンテナンスが、開発体験において欠かせない存在となりました。
急成長期のまとめ
StorybookとChromaticという強力なテストツールを使うことにより、自動テストの恩恵を受けることができるようになりました。
早い段階でリグレッションによる不具合を検知することにより、バグを減らすだけでなく、設計の改善や開発体験の向上につながりました。
フェーズ3. 現在地とこれから
テスト基盤も少しずつ安定してきました。より効果的なテスト戦略の確立に向け、取り組むべき課題も明確化してきています。
起きていること
(1) 実行コストと信頼性の安定に向けて
テスト対象が増えるにつれて、1)実行時間の増加と2)Flakyなテストが問題になってきました。様々な改善対策をしましたが、長くなるため、ここではその項目を列挙するだけにとどめます。
- 実行時間の削減
- Storybook
- shardオプションによる並列ジョブ実行
- Chromatic
- turboSnapオプションによる差分検出
- Flakyなテストの安定化
- Storybookのバージョンを上げるタイミングで、記法が古いテストとFlakyなテストは一旦すべてコメントアウトした
- ボーイスカウト精神で直していくようにした
asyncUtilTimeout
オプションによりfindBy
のデフォルトタイムアウト時間を延長
これらの取り組みによって、現状ではStorybookとchromaticの実行時間とFlakyさは改善し、ほぼネックになっていないため、信頼性の高いテストを十分な速さで実行できている安心感があります。
(2) 俺たちのトロフィー策定
play()
とChromaticによって結合テストあたりのレイヤーを厚くする方針は決まっています。しかしテストの目的・達成したい状態をより明確にチームで共有したいところです。
そこで、チーム全員で「自分たちのテスト配分」を描くことで、テストの目的や役割について議論を深めました。これはどのツールでどの層を、そしてどんな品質を担保するのか、解像度を上げるきっかけとして非常に有効でした。
左が初期案、右が完成系です。細かいですが配置の調整や、ツールについて明示しました。
完成に至るまでのプロセスでは、以下のような重要な議論が行われました。
(3)ユニットテストの立ち位置を確認
議論の結果、ユニットテストは以下のような範囲に限定する方針を採用しました。
- 外部依存のない静的ロジックを説明・定義すること。
たとえば、計算やフォーマット変換のようなロジック。 - ドメインロジックやUIに密接な部分は結合テストに委ねること。
Storybookのplay()
機能やChromaticでカバーすることで、手動操作に近いレベルでの信頼性を確保します。
コードベースは、テスタブルなコードとそうでないものが混じった状態です。理想は、テストしやすいロジックに設計を見直すことですが、リソースが限られている現状では現実的ではありません。まずは機能レベルで壊れにくい状態を目指すことが最優先と判断しました。
(4)アプリケーション品質と開発品質を分けて考える
CIで自動テストを回すことで、漠然と「ある程度の品質保証はできているはず」という感覚に頼っていましたが、そもそもここでいう「品質保証」とは何を指しているのかをチーム内で議論しました。
この議論の中で、テスティングトロフィーを意識しすぎた結果、固定観念に囚われてしまいがちだったことが明らかになりました。具体的には、ユーザに届けられる「アプリケーション品質」と、QAテストに引き渡す段階やリリースサイクル全般で必要とされる「開発品質」を分けて考える視点が不足していたのです。この視点によって、品質保証におけるチームのアプローチを整理できました。
FAANSではアジャイルな手法を取り入れることで、各工程においてバグ検知や仕様変更などのフィードバックを柔軟に実装に反映させることができる開発サイクルになっています。しかし、基本的には静的な仕様からQAテストを経てリリースするウォーターフォール方式であるため、この品質の境界を明確に認識することが重要であるという結論に至りました。
現状、開発チームのテストが直接的に保証できるのは主に開発品質です。一方で、最終的なアプリケーション品質を担保するにはQAテストが欠かせません。この事実整理によって、現状のフロント開発が担保しているテストだけでは不十分だということを再確認しました。ただし、もちろんこれは開発チームがアプリケーション品質を負わないということは意味しません。この区別の明確化により、テスト実装の目的とその効果の測定範囲を整理し、より適切なテスト戦略を構築できるようになります。そして、次のステップとして「開発品質」と「アプリケーション品質」をそれぞれ強化するためのアプローチを模索しています。
今後の課題
(1) 測定・振り返りへの試み
さて、テスト結果(主にカバレッジ)を測定・振り返りすることで開発品質を上げ、より「根拠のある自信」5をもった開発体験にしたいところです。
しかし、フロント開発に閉じた世界で定量的に品質を振り返る指標を定めるのが難しいため、測定の設計が難航しています。以下のような方針で測定の準備を進めています。もしもよりよい方法があればぜひ教えてください。
- 機能軸で優先度づけし、ページ単位で観測。
- 事業重要度やユーザ利用頻度に基づいて優先度を定義。ページコンポーネント単位でカバレッジを観測。
- 優先度が高いページのカバレッジが落ちないようにする。
- 優先度が高いのにカバレッジの低い箇所を改善していく。
- 事業重要度やユーザ利用頻度に基づいて優先度を定義。ページコンポーネント単位でカバレッジを観測。
- SonarQube Cloudの活用
- 静的観測軸として、コードベースの健康状態を監視。
- 複雑さの増大を監視し、テストしやすさの低下を未然に防ぐ指標として活用。
- モジュール軸でのカバレッジ測定対象の定義
- 複雑性や変更頻度を考慮した重み付けし、効果的な測定対象を絞り込む。
- 静的観測軸として、コードベースの健康状態を監視。
今後また、結果を公開できればと考えています。
(2) その他
- 冒頭でご紹介した通り、FAANSではSLOにより品質を監視しています。組織全体でアプリケーション品質を維持するため、フロントエンドとしてどのようにこの指標を活用・コミットできるか模索しています。
- QAチームにより本質的なテストに注力してもらうためには、E2Eを一部自動化し効率化したいところです。開発・QAで連携し、より良い開発サイクルを生むための施策を協議していきます。
まとめ
フロントエンド開発における自動テストのあり方は、チームの成長やツールの進化とともに動的に変化していきます。今後生成AIの進化も大きく影響していくことでしょう。ベストプラクティスを静的な理想形として求めるのではなく、自分たちに合った戦略を築き上げる意識を持ち、動的で可塑性のある開発プロセスが重要だとわかりました。
今回の振り返りを通じて得られた学びは以下の通りです。
- チーム全体での目的共有
- テストの役割や目的を明確にし、同じ方向に向かって改善していくことで、安心感・納得感が高まること。
- 開発フェーズに応じた柔軟な戦略
- 特に黎明期は避けられない障害が多くある一方で、基礎知識と経験によって、ツールや環境の特性に則した戦略をとれること。
- 信頼性の可視化と測定の取り組み
- 現在は主要機能やロジックの重み付けを通じて適切な指標を模索しています。取り組み中ではありますが、信頼性向上の大きな一歩になると考えています。
現在進行形の取り組みが多い中ですが、このプロセス自体がプロダクトやチームの成長につながると信じています。今回の記事が同じ課題に向き合うみなさんの一助となれば幸いです。
ZOZOでは、一緒にサービスを作り上げてくれる方を募集中です。ご興味のある方は、以下のリンクからぜひご応募ください。
- 特に前置きなく「テスト」と指す際は、TDDなどの設計・実装手法ではなく自動テストの文脈で語っています。↩
- StorybookのPlay-functionは、ユーザーの操作をシミュレートし、コンポーネントのインタラクションを自動的にテストするための機能です。 Docs | Storybook↩
- Chromaticは、Storybookと統合されたビジュアルリグレッションテスト(VRT)ツールで、UIの見た目の変更をスナップショット比較で検出します。 Visual testing & review for web user interfaces • Chromatic↩
- 関連記事 Visual E2E Testing with Chromatic and Playwright↩
- 関連記事 ピラミッド、アイスクリームコーン、SMURF: 自動テストの最適バランスを求めて / Pyramid Ice-Cream-Cone and SMURF - Speaker Deck↩