「テストを書こう」という話は正しい。ただ「何を・どこまで書くか」の判断が難しい。
React + TypeScriptのプロジェクトでテスト戦略を考えるとき、理想論ではなく現実の制約の中でどう判断するかが重要だ。テックリードとして複数のプロジェクトでテスト方針を決めてきた経験から、実務での落とし所を書く。
フロントエンドのテストの種類
ユニットテスト
関数・コンポーネント単体の動作を確認する。実行が速く、問題箇所を特定しやすい。
// utils/formatDate.test.ts
import { formatDate } from './formatDate';
test('日付を日本語形式でフォーマットする', () => {
expect(formatDate(new Date('2024-01-15'))).toBe('2024年1月15日');
});
インテグレーションテスト
複数のコンポーネントや機能が組み合わさったときの動作を確認する。
// components/SearchForm.test.tsx
import { render, screen, fireEvent } from '@testing-library/react';
import { SearchForm } from './SearchForm';
test('検索ボタンをクリックすると onSearch が呼ばれる', async () => {
const onSearch = jest.fn();
render(<SearchForm onSearch={onSearch} />);
fireEvent.change(screen.getByRole('textbox'), {
target: { value: 'TypeScript' },
});
fireEvent.click(screen.getByRole('button', { name: '検索' }));
expect(onSearch).toHaveBeenCalledWith('TypeScript');
});
E2Eテスト(Playwright・Cypress)
実際のブラウザでユーザーの操作シナリオを再現する。最も信頼性が高いが実行が遅く、メンテナンスコストも高い。
「全部書く」は現実的か
結論から言うと、リソースが限られたチームで全部のテストを高いカバレッジで維持しようとすると破綻する。
経験上、テストを過剰に書いたプロジェクトでは以下の問題が起きた。
- UIの変更のたびにスナップショットテストが失敗し、更新作業に時間を取られる
- E2Eテストがフラキー(不安定)になり、CIが赤くなっても誰も信用しなくなる
- テストを書くことが目的化し、実装の品質より「カバレッジの数字」を追う
テストは書くことが目的ではなく、「変更に自信を持てるようにする」ための手段だ。
実務でのテスト優先度
最優先:ロジック層のユニットテスト
書く価値が最も高いのはここだ。
UIに依存しない純粋な関数(バリデーション・計算・変換処理)は、テストが書きやすく壊れにくい。変更の影響範囲が明確で、テストが本当に守ってくれる。
// ビジネスロジックのテストは最優先
describe('calculateDiscount', () => {
test('会員ランクに応じた割引率を返す', () => {
expect(calculateDiscount('gold', 10000)).toBe(1000); // 10%
expect(calculateDiscount('silver', 10000)).toBe(500); // 5%
expect(calculateDiscount('normal', 10000)).toBe(0);
});
});
次点:重要なユーザーフローのインテグレーションテスト
「このフォームが正しく動くか」「エラー時に適切なメッセージが出るか」という、ユーザーが直接触れる重要な動作はインテグレーションテストで確認する。
React Testing Libraryを使えば、実際のDOMに近い形でテストが書ける。
慎重に:スナップショットテスト
「コンポーネントの見た目が変わっていないか」を確認するスナップショットテストは、UIの変更のたびに更新が必要になる。
小さいチームでは形骸化しやすいため、視覚的な回帰テストが必要ならStorybook + Chromaticのような専用ツールを使う方が現実的だ。
限定的に:E2Eテスト
実行が遅く不安定になりやすいため、以下の判断基準で絞る。
書く価値があるE2Eテスト
- 決済フロー・認証フローなど、絶対に壊れてはいけないクリティカルパス
- 複数のシステムをまたぐ統合箇所
書かなくていいE2Eテスト
- ユニット・インテグレーションテストで確認できる範囲
- UIの細かい表示確認
チームへの浸透方法
「テストを書いてください」と言うだけでは定着しない。
私が実践した方法は、コードレビューの段階で「このロジックにテストを追加しませんか?」と提案することだ。強制ではなく提案として続けることで、テストを書くことが自然な流れになっていった。
また、テストのないコードをリリースして問題が起きたときに「あそこにテストがあれば防げた」と振り返ることで、チームがテストの価値を体感できる機会にした。
まとめ
フロントエンドのテストは「全部書く」ではなく「価値のある部分に集中する」が現実解だ。
- ビジネスロジック:ユニットテストを厚く書く
- ユーザーフロー:インテグレーションテストで重要箇所を抑える
- E2E:クリティカルパスのみに絞る
テストはコードベースへの投資だ。書きすぎてもメンテナンスコストで損をする。何を守りたいかを明確にして、必要な場所に集中することが長続きする戦略だと思っている。