フロントエンドテスト戦略を考える
実際のプロジェクトで本格的にフロントエンドテストを導入したことがないため、まずはインプットからということで、Testing Trophyとフロントエンドのテスト戦略について調べて考えたことをまとめてみます。
Testing Trophyとは
Testing Trophy(テスティング・トロフィー)とは、どの種類のテストにどれだけのリソースを投入すべきかを、トロフィーの形で視覚的に表現したフレームワークです。
効果とコストのバランスを最適化することを目的としており、以下の4層で構成されます。
1. Staticテスト(静的解析)
TypeScript、ESLint、Prettier などによる静的解析の層です。
実行せずにコードを検証し、型の安全性やスタイルの統一、潜在的なバグを防ぎます。
- 特徴:実行コストが最も低く、即座にフィードバックが得られる
- 配分:一度設定すればほぼメンテナンス不要で、開発体験の基盤となる
2. Unitテスト(単体テスト)
個別のコンポーネントや関数の動作を検証する層です。
- 特徴:高速・安定・コストが軽い
- 配分:複雑なロジックやクリティカルな処理、再利用性の高い関数に重点的に適用
3. Integrationテスト(結合テスト)
複数のコンポーネントやモジュールが連携した状態での動作を検証します。
Testing Trophyでは、最も重視すべき層とされています。
- 特徴:実際の使用に近い状況を再現しつつ、適度なコストで実施できる
- 配分:トロフィーの中央の膨らみ。最もバランスが良く、効果が高い
4. End to End(E2E)テスト
CypressやPlaywrightなどを用いて、実ブラウザ上でアプリ全体の動作を検証する層です。
- 特徴:実環境に最も近く信頼性が高い一方、実行・維持コストが最大
- 配分:すべてをE2Eでカバーせず、ビジネス上クリティカルな導線に限定して行う
Testing Trophyの考え方とBDD
Testing Trophyが強調するのは、結合テストに最もコストをかけるべきという点です。
理由:
- フロントエンドでは、単体のコンポーネントだけで成立する機能はほとんどない
- 内部ロジックだけを検証しても、ユーザー体験を保証できない
- 結合テストは、コストと効果のバランスが最も優れている
この考えを実現する手法のひとつが BDD(Behavior-Driven Development:振る舞い駆動開発) です。
BDDでは、「ユーザーがこれをすると、こうなる」という粒度でテストを書きます。
そのため、自然とユースケース単位の結合テストを書くことができます。
例:
- ユーザーから見える要素(ボタン、テキストなど)はテスト対象
- 内部の関数やstateなどユーザーから見えないものは対象外
TDD(Test-Driven Development)の位置づけ
Testing Trophyの考え方を取り入れつつ、開発手法として TDD(テスト駆動開発) を採用することも可能です。
TDDの基本サイクル:Red → Green → Refactor
- Red:テストを書く(まだ失敗する状態)
- Green:テストを通す最低限の実装を書く
- Refactor:コードを改善しつつテストが通ることを確認
TDDのメリット:
- 実装前にテストを設計するため、テストしやすい設計になる
- コード品質が向上し、リファクタリング時の安全性も高い
- IntegrationテストやUnitテストと相性が良い
Testing Trophyで推奨される結合テスト中心の戦略をTDDで進めると、ユーザー観点の振る舞いを確認しつつ、堅牢な実装が行えます。
テスト戦略
Testing Trophyをもとに戦略を考えると以下の通りです。
- トレードオフの観点でバランスのよい結合テストを厚めに書く
- E2Eテストは課金導線やタイムラインなど、ビジネス上クリティカルな箇所のみ
- 単体テストは、結果が明白なロジックを除外し、複雑なビジネスロジックのみ対象
- 静的テストは必須。導入が後回しになるほどコストが増えるため、プロジェクト初期に導入する
ここから具体的な方法を見ていきます。
各テスト層の実施方法
1. 静的テスト(Static Tests)
静的解析は「テストの前提条件」ともいえます。
導入が後回しになるほどコード全体の修正コストが増すため、プロジェクト開始時に必ず導入することが推奨されます。
目的
- 型安全性の担保(TypeScript)
- コード品質・スタイルの統一(ESLint / Prettier)
- 潜在的バグの早期発見
推奨設定
- TypeScriptのstrictモードは可能な限り有効化
- ESLintは最小限のルールから始め、プロジェクト規模に応じて追加
- Prettierはコードフォーマットを自動化し、チームで統一
注意点
- 導入が遅れるほど既存コードの修正コストが膨らむ
- 静的解析のルールは厳しすぎると開発効率を阻害するため、段階的に調整する
2. 単体テスト(Unit Tests)
Unitテストはシンプルに見えますが、フロントエンドでは単体だけで意味をなさないことが多いという点が重要です。
単体で動作するロジックよりも、コンポーネント間の連携やユーザー操作を伴う処理のほうが実際の不具合に近いため、後述する結合テストに比べると優先度は下がります。
目的
- 複雑なビジネスロジックの正当性を保証
- 将来的なリファクタリング時の安全弁
使用ツール
- 
Vitest / Jest + React Testing Library - AIツールの利用により、導入コストはそこまで高くない
- 特にVitestは高速でモダンな構成を持ち、Viteとの親和性も高い
 
Storybookとの連携
Storybookはコンポーネント駆動開発において強力な味方です。
Component Story Format(CSF)3.0により、Storybookの定義をテストコードにも再利用できます。
- play関数:Storybook上で確認する操作コードをテストに流用できる
- composeStories関数:Story定義をJestテストとして再利用できる
これにより、
- Storybook → 見た目と代表的シナリオの検証
- Jest/Vitest → 振る舞いとa11y構造の検証
という責務分離が可能になります。
テストパターン
テストは以下の2つのパターンのいずれかで構成します。
| 概念 | フェーズ | 説明 | 
|---|---|---|
| AAAパターン | Arrange → Act → Assert | 準備 → 実行 → 検証 | 
| Given-When-Thenパターン(BDD) | Given → When → Then | 状態 → 操作 → 結果 | 
BDDスタイルは読みやすく、テストの目的が明確になるため、チーム開発に特に向いています。
注意点
- 「結果が明白な単純な関数」はテストしない
- 1テスト1観点を徹底し、複数の振る舞いを同時にテストしない
3. 結合テスト(Integration Tests)
Testing Trophyで最も重要視される層です。
単体テストよりも高い信頼性を持ちながら、E2Eほどコストはかかりません。
フロントエンドの多くの機能は、複数コンポーネントの連携で成立するため、結合テストが中心となります。
目的
- 複数コンポーネントやモジュール間の連携を検証
- ユーザー視点での機能保証
- 内部実装に依存しないテストで変更に強い
実施方法
- React Testing Libraryで、ユーザー操作のシミュレーション中心に書く
- 外部APIはモック化して、UIや振る舞いを検証
- BDDスタイルを意識し、「ユーザーが○○したら△△になる」という粒度でテスト
注意点
- 範囲を広げすぎると単体テストとE2Eの中間のようになり、メンテナンス性が低下
- UIの見え方ではなく、ユーザー操作の結果にフォーカス
4. E2Eテスト(End-to-End Tests)
最も信頼性の高いテストですが、実行コスト・メンテナンスコストも最大です。
プロジェクト全体の回帰テストとして有効ですが、全てのパスを網羅する必要はありません。
課金フロー、ログイン、投稿、タイムライン表示など、不具合が発生するとユーザー体験や売上に直結する部分のみを対象とします。
目的
- 実際のユーザー環境に近い形で機能を検証
- ビジネス上クリティカルな導線の安全を担保
使用ツール
- 
Playwright - クロスブラウザ・並列実行が容易
- TypeScript対応が充実し、モダン開発に最適
 
- 
Cypress - デバッグが視覚的にわかりやすく学習コストが低い
 
実施方針
- 課金フロー、ログイン、投稿、タイムラインなどのクリティカルパスに限定
- テストの安定性を確保するため、モックやステージング環境との組み合わせも活用
注意点
- 範囲を広げすぎるとテストの実行時間とメンテナンスが肥大化
- UI変更が多い箇所は、テストのメンテナンス負荷に注意
以上の方針をベースにプロジェクトの状況に応じて配分を調整してければいいのかなと思います。
参考: