このレッスンで学ぶこと
- ユニットテスト・結合テスト・E2Eテストを目的に応じて使い分ける考え方
- カバレッジ目標の設定方法と、現状とのギャップを分析する手順
- 既存コードへのテスト追加と、新規コードでのTDDサイクルをそれぞれ効率的に進める方法
前提条件
- Lesson 2「リファクタリングを安全に進める」を読んでいること(テストを「安全ネット」として使う考え方を前提とします)
- プロジェクトにテストフレームワークが導入済みであること
テスト生成を「戦略」として捉える
Claude Codeにテストを書かせること自体は、入門コースや実践レシピ「既存コードのテストを自動生成する」で扱った通り、プロンプト次第で十分な精度のテストが得られます。しかし、実務でテストが本当に課題になるのは「個々のテストの書き方」ではなく、どこに・どの種類のテストを・どれだけ書くかという戦略レベルの判断です。
闇雲に「全部ユニットテストで」と進めると、モックだらけで実装の詳細にしか追従できない壊れやすいテストスイートが出来上がります。逆に「全部E2Eで」と進めると、実行に時間がかかりすぎてCIが回らなくなります。このレッスンでは、テストの種類を使い分ける判断基準と、組織としてカバレッジを管理する仕組みを扱います。
ユニット・結合・E2Eを使い分ける
テストピラミッドで役割を整理する
graph TD
E2E["E2Eテスト<br/>(少数・低速・高コスト)"]
INT["結合テスト<br/>(中程度)"]
UNIT["ユニットテスト<br/>(多数・高速・低コスト)"]
E2E --- INT --- UNIT
style E2E fill:#fde8e8,stroke:#c0392b
style INT fill:#fff3e0,stroke:#e65100
style UNIT fill:#e8f4fd,stroke:#1a73e8| テスト種別 | 対象 | 実行速度 | 向いている検証内容 |
|---|---|---|---|
| ユニットテスト | 単一の関数・クラス | 速い(ミリ秒〜) | ビジネスロジック、計算処理、バリデーション |
| 結合テスト | 複数モジュールの連携 | 中程度(秒単位) | API ↔ DB連携、サービス間のデータフロー |
| E2Eテスト | システム全体 | 遅い(分単位) | ユーザーの主要な操作シナリオ |
Claude Codeに「テストを書いて」とだけ依頼すると、対象コードの構造を見てある程度自動で種別を判断してくれますが、判断の精度は指示の明確さに比例します。どの層のテストを書いてほしいかを最初に伝えることが、無駄のないテストスイートへの近道です。
src/services/discountCalculator.ts のテストを書いて。
この関数は外部依存(DB、API)を持たない純粋な計算ロジックなので、
ユニットテストとして書いて。モックは不要。
入力と出力の組み合わせを網羅的にテストしてsrc/api/routes/checkout.ts のテストを書いて。
このエンドポイントはOrderService, InventoryService, PaymentGatewayと
連携するので、結合テストとして書いて。
実際のDBの代わりにテスト用DBコンテナを使い、
PaymentGatewayの外部API呼び出しのみモックして何をどの層でテストするかを決める
新機能を実装する際は、テストを書き始める前に「どの振る舞いをどの層で検証するか」を決めておくと、テストの重複や抜け漏れを防げます。
ユーザー登録機能について、テスト戦略を設計して。
含める内容:
- ユニットテストで検証すべき項目(バリデーションロジック等)
- 結合テストで検証すべき項目(DB保存、メール送信との連携等)
- E2Eテストで検証すべき項目(あれば。新規ユーザー登録〜ログインの一連の流れ等)
- 各層で「テストしない」と判断した項目とその理由「バリデーションエラーが返ること」をユニットテストとE2Eテストの両方で詳細に検証する必要はありません。E2Eでは「エラー時にユーザーに正しく表示されるか」という統合的な観点に絞り、バリデーションロジックの細かい分岐はユニットテストに任せるなど、層ごとに検証の粒度を変えましょう。
カバレッジ目標を設定し、ギャップを分析する
目標値を機械的に決めない
「カバレッジ80%を目指す」という目標自体は一般的ですが、数値だけを目標にすると、実際には重要度の低いコードのカバレッジを稼ぐためにテストを書く、という本末転倒な状況に陥りがちです。Claude Codeを使ってギャップ分析を行う際は、**カバレッジ率そのものではなく「重要なロジックが守られているか」**を基準にしましょう。
src/services/ ディレクトリのテストカバレッジを分析して。
以下の観点でレポートして:
- ファイルごとのカバレッジ率
- カバレッジが低く、かつビジネスクリティカルな処理を含むファイル
(決済、認証、データ整合性に関わるもの)を優先度高として抽出
- カバレッジは低いが影響の小さいファイル(設定読み込み等)は優先度低として除外ギャップを埋める順序を計画する
優先度が明確になったら、着手順を計画します。一度にすべてを埋めようとせず、リスクの高いものから着手するのが定石です。
先ほどのギャップ分析の結果をもとに、テスト追加の計画を立てて。
- 優先度高のファイルから着手する順序を決める
- 各ファイルについて、想定されるテストケース数の見積もりを出す
- 計画をTEST_PLAN.mdに書き出す100%のカバレッジを目指すと、getter/setterのような自明なコードや、フレームワークが保証する処理にまでテストを書くことになり、保守コストだけが増えます。「このコードが壊れたら、ユーザーやビジネスにどの程度の影響があるか」を基準に、テストを書く優先順位を判断しましょう。
既存コードへのテスト追加 vs 新規コードのTDD
テストを生成するシチュエーションは大きく2つに分かれます。それぞれでClaude Codeへの依頼の仕方を変える必要があります。
既存コードへのテスト追加
既存の実装に後からテストを追加する場合、目的は「現在の振る舞いを記録すること」です。Lesson 2のリファクタリングでも触れた考え方と同じです。
src/utils/dateFormatter.ts には現在テストがありません。
この関数の現在の振る舞いを記録するテストを書いて。
- 実装を変更せず、現状の入出力をそのまま記録する
- 想定外に見える挙動(タイムゾーン処理など)があれば、
バグとして直さず、コメントで「現状の挙動」として明記する既存コードのテストでは、実装を信頼しすぎず、Claude Codeに「本当にその振る舞いで正しいか」を確認させることも有効です。
dateFormatter.tsのformatRelativeTime関数について、
仕様書(docs/05-api-design.md)の記述と実装が一致しているか確認して。
不一致があれば、テストを書く前に報告して新規コードでのTDD
新しい機能を実装する場合は、実装より先にテストを書く古典的なTDD(テスト駆動開発)サイクルが有効です。Claude Codeは「Red → Green → Refactor」のサイクルを高速に回せます。
仕様からテストを先に書く(Red)
実装がまだない状態で、期待する振る舞いをテストとして書かせます。
これから実装するvalidateCouponCode関数のテストを先に書いて。
仕様:
- クーポンコードは8桁の英数字
- 有効期限切れの場合はExpiredErrorを投げる
- 存在しないコードの場合はNotFoundErrorを投げる
- 有効な場合は割引率(0-100の数値)を返す
実装はまだ存在しないので、テストは現時点で失敗する想定ですテストを通す最小限の実装をする(Green)
テストをパスさせることだけを目的に、実装を進めます。
先ほど書いたテストを全てパスさせるように、
validateCouponCode関数を実装して。
過剰な作り込みはせず、テストの要求を満たす最小限の実装にしてテストを保ったままリファクタリングする(Refactor)
テストが通る状態を維持しながら、実装の品質を上げます。
テストを全てパスさせたまま、validateCouponCode関数の実装を整理して。
重複や読みにくい箇所があれば改善して。
各変更後にテストを再実行して、パスすることを確認してTDDは仕様が明確な処理(バリデーション、計算ロジック、状態遷移など)で特に効果を発揮します。逆に「触ってみないと仕様が定まらない」UIの試行錯誤などでは、先にプロトタイプを作ってから後追いでテストを整備する方が効率的なこともあります。
テストの保守性を高めるプロンプト設計
生成されたテストが「実装の詳細に依存しすぎていて、リファクタリングのたびに壊れる」状態は避けたいところです。保守性の高いテストにするための指示パターンを紹介します。
振る舞いを検証させる、実装を検証させない
テストは関数の「振る舞い(入力と出力の関係)」を検証する内容にして。
内部でどのメソッドが何回呼ばれたかのような実装の詳細には
依存しないようにして。
ただし、外部APIの呼び出し回数のように、副作用として重要なものは除くモック方針を明確にする
モックの粒度がチームでバラバラだと、テストの信頼性も保守性も損なわれます。方針を明示しましょう。
テストを書くにあたっての方針:
- リポジトリ層(DB操作)はモックする
- ドメインロジック(計算・バリデーション)はモックしない
- 外部API(決済、メール送信)はモックする
- 日時はvi.setSystemTimeで固定するテストの意図が伝わる名前にする
テスト名は「何を」「どんな条件で」「どうなるか」が
わかる形式で日本語で書いて。
例:
- "クーポンコードが8桁未満の場合、ValidationErrorを投げること"
- "有効期限内のクーポンの場合、設定された割引率を返すこと"これらの指示を毎回書くのは手間なので、プロジェクトのCLAUDE.mdにテスト方針として一度書いておくと、以降は省略できます。チームメンバー全員が同じ方針でテストを生成するようになり、テストスイート全体の一貫性も保てます。
CI連携を見据えたテスト生成
ローカルで動くテストを書くだけでなく、CI環境で安定して実行できることも重要な要件です。
実行時間を意識する
このテストファイルの実行時間を確認して。
1ファイルで5秒を超えるテストがあれば、原因(不要なsetupTimeout、
実際のネットワーク呼び出しなど)を調査して報告してFlaky(不安定)なテストを避ける
CI環境特有の問題として、ローカルでは通るがCIでは時々失敗する「Flakyテスト」があります。生成時点で予防策を組み込んでおくと、後の調査コストを削減できます。
このテストで、以下のようなFlakyテストの原因になりうる
パターンがないか確認して:
- 固定のsetTimeout/sleepによる待機
- テスト実行順序への依存
- 日時・乱数の未固定
- 並列実行時に競合しうる共有状態
見つかった場合は、決定論的な実装に修正してCI設定への組み込みを依頼する
テストの追加に合わせて、CI設定の更新もまとめて依頼できます。
新しく追加した結合テスト(src/api/__tests__/checkout.integration.test.ts)を
CIで実行できるように、.github/workflows/test.yml を更新して。
このテストはテスト用DBコンテナが必要なので、
既存のユニットテストとは別のジョブに分離してCI設定ファイルの変更は、実際にCIが動くまで問題に気づきにくい領域です。ローカルでDocker Composeなどを使って同等の環境を再現できる場合は、CIにプッシュする前に手元で確認しましょう。
よくある質問
Q. テスト戦略を考える時間がもったいなく感じます。最初から書き始めてはダメですか?
小規模な変更であれば、戦略を立てずに直接「テストを書いて」と依頼して問題ありません。このレッスンの内容は、新機能の追加やカバレッジ改善プロジェクトのような、まとまった量のテストを書く場面で特に効果を発揮します。
Q. 結合テストとE2Eテストの境界が曖昧です。どう区別すればいいですか?
明確な業界標準はありませんが、実用上は「実際のブラウザ操作やAPI呼び出しを経由してシステム全体を通すか」を境界にするとよいでしょう。バックエンドのAPI ↔ DB連携を直接テストするなら結合テスト、フロントエンドからAPIを経由して一連の操作を検証するならE2Eテスト、という分け方が一般的です。
Q. 既存のFlakyテストが大量にある場合、どこから手をつければいいですか?
CIの実行ログから失敗頻度の高いテストを抽出し、Claude Codeに原因パターン(このレッスンで紹介したような)を分析させるところから始めましょう。全部を一度に直そうとせず、頻度の高いものから着手するのが現実的です。
まとめ
- テストはユニット・結合・E2Eの3層で役割を分担し、層ごとに検証する粒度を変える
- カバレッジは数値目標ではなく「ビジネスクリティカルな処理が守られているか」を基準にギャップを分析する
- 既存コードへのテスト追加は「現状の振る舞いを記録する」、新規コードはTDDサイクル(Red→Green→Refactor)で進める
- モック方針・テスト名の命名規則をCLAUDE.mdに明記し、テストスイート全体の保守性と一貫性を保つ
- CI連携を見据え、実行時間とFlakyテストのリスクを生成時点でチェックする
個々のテスト生成プロンプトの作り方やモック設定の詳細は、実践レシピ「既存コードのテストを自動生成する」も参考にしてください。このレッスンの戦略設計と組み合わせることで、効率と品質の両方を満たすテストスイートを構築できます。
次のステップ
テストが整備されたコードベースは、ドキュメントの面でも書きやすくなります。次のLesson 4では、README・API仕様書・コードコメント・CHANGELOGなど、ドキュメントを自動生成し、コードの変化に追従させて鮮度を保つ方法を学びます。