testing について
開発者やチーム向けの、技術スタックに依存しないテストリファレンスです。テスト設計、テスト階層、TDD、テストダブル、信頼できるテストスイートの維持方法を扱います。
主な機能
- 01ユニット・統合・エンドツーエンドテストの指針
- 02Arrange-Act-Assert によるテスト設計
- 03TDD の Red-Green-Refactor サイクル
- 04テストダブルと境界での利用指針
- 05不安定なテストとアンチパターンの解説
活用シーン
- 01新機能に適したテスト階層を選ぶ
- 02不安定または脆いテストスイートを調査する
- 03テストの明瞭さと保守性をレビューする
- 04TDD で設計上の判断を検討する
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ krzysztofsurdy/ code-virtuoso --skill testingインストール後、Claude Code で/testingを入力して読み込まれたか確認します。
同梱ファイル
SKILL.md と一緒にインストールされる参考資料やスクリプト。
references/tdd-schools.mdtest-doubles.mdtesting-strategies.md詳細
テスト
ソフトウェアが正しく動作し、変更後も安定性を保ち、将来の開発者に意図を伝えられることを検証するための、規律あるアプローチです。優れたテストは、生きたドキュメントであり、リファクタリング時の安全網であり、設計へのフィードバック手段でもあります。
このスキルでは、言語、フレームワーク、ツールにかかわらず適用できる、普遍的なテストの概念を扱います。
使用する場面
- 新しいプロジェクトや機能のテスト戦略を設計するとき
- コードに必要なテストのレベル(ユニット、統合、E2E)を判断するとき
- 既存のテストが価値を生んでいるか、負担になっているかを評価するとき
- TDD を適用して設計上の判断を導くとき
- 不安定なテストスイートや壊れやすいテストスイートをデバッグするとき
- テストコードの品質と保守性をレビューするとき
テストピラミッド
テストピラミッドは、3 つのレベルにおけるテストの理想的な分布を示します。下層ほどテストを多く、上層ほど少なくします。
/ E2E \ Few, slow, expensive
/----------\
/ Integration \ Moderate number, moderate speed
/----------------\
/ Unit Tests \ Many, fast, cheap
/____________________\
ユニットテスト(基盤)
- 単一の動作単位(関数、メソッド、小さなクラス)を分離してテストします
- I/O、データベース、ネットワーク、ファイルシステムは使用しません
- ミリ秒単位で実行されます
- テストスイートの大部分を占めるべきです(およそ 70%)
- 高速なフィードバックループにより、迅速な反復が可能になります
統合テスト(中層)
- 複数の単位がどのように連携するか、またはコードが外部システムとどのようにやり取りするかをテストします
- 実際のデータベース、メッセージキュー、HTTP エンドポイントを使用する場合があります
- 秒単位で実行されます
- コンポーネント間の配線、設定、契約が機能することを検証します
- テストスイートのおよそ 20% を占めます
エンドツーエンドテスト(上層)
- システム全体を通じたユーザーの一連の操作をテストします
- ユーザーと同じようにアプリケーションを操作します
- 最も遅く、最も壊れやすく、保守コストも最も高くなります
- 重要なビジネス上の経路にのみ使用します
- テストスイートのおよそ 10% を占めます
アイスクリームコーン型のアンチパターン
ピラミッドを逆さにした状態です。E2E テストが多く、ユニットテストが少なくなっています。次のような症状があります。
- テストスイートの実行に何時間もかかる
- UI の変更やタイミングの問題で、テストが頻繁に失敗する
- 開発者がローカルでテストを実行しなくなる
- フィードバックループが遅すぎて、継続的デリバリーを支えられない
解決策: 各 E2E テストが実際に何を検証しているかを特定します。その検証を可能な限り低いレベルに移します。ほとんどのビジネスロジックは、ユニットレベルでテストできます。
テスト設計の原則
Arrange-Act-Assert(AAA)
すべてのテストは、次の 3 つの明確なフェーズに従うべきです。
- Arrange — 前提条件と入力を設定します
- Act — テスト対象の動作を実行します
- Assert — 期待される結果を検証します
各フェーズを明確に分けます。Arrange がテストの大部分を占める場合は、ビルダーまたはファクトリーに切り出します。Act に複数の手順が必要な場合は、一度に多くのことをテストしている可能性があります。
1 つの概念につき 1 つのアサーション
テストでは、1 つの論理的な概念を検証するべきです。これは、文字どおり assert の呼び出しを 1 回だけにするという意味ではありません。1 つの結果について複数のプロパティを検証するのは問題ありません。重要なのは、テストが失敗する理由が 1 つだけであることです。
// Good: one concept — "completed order has correct totals"
assert order.subtotal == 100
assert order.tax == 21
assert order.total == 121
// Bad: two unrelated concepts in one test
assert order.total == 121
assert emailService.wasCalled()
テストの命名
テスト名には実装ではなく、動作を記述します。優れたテスト名は、「どのシナリオをテストしていて、何が期待される結果か」に答えます。
さまざまな言語で使えるパターン:
should_return_zero_when_cart_is_emptyrejects_negative_quantitiesapplies_discount_for_premium_customers
testCalculate、test1、testGetterSetter のような名前は避けます。
テストの独立性と分離
各テストは、ほかのすべてのテストから完全に独立していなければなりません。
- テスト間で変更可能な状態を共有しない
- 実行順序に依存しない
- 各テストがそれぞれの前提条件を設定し、実行後に後片付けを行う
- 1 つのテストの失敗が、ほかのテストの失敗に連鎖しない
決定論的なテスト
テストは、次の条件に左右されず、実行するたびに同じ結果を出さなければなりません。
- 現在の時刻や日付
- テストの実行順序
- 実行するマシン
- ネットワークの可用性
- 並列実行中のほかのテスト
非決定的なテスト(不安定なテスト)は、テストスイートへの信頼を損ない、テストがまったくない状態よりも悪いものです。
FIRST の原則
| 原則 | 意味 |
|---|---|
| 高速(Fast) | テストは数分ではなく、数秒で実行できる必要があります。遅いテストは実行されなくなります。 |
| 独立(Independent) | あるテストの結果に依存するテストがないこと。 |
| 再現可能(Repeatable) | ローカル、CI、ステージングなど、どの環境でも同じ結果になること。 |
| 自己検証可能(Self-validating) | 人が解釈しなくても合格か不合格か判断できること。 |
| 適時(Timely) | 適切なタイミングで作成すること。理想的には、本番コードの作成前か、作成と並行して作成します。 |
テスト駆動開発(TDD)
TDD は、短いフィードバックループに従って本番コードより先にテストを作成する、設計の規律です。
Red-Green-Refactor サイクル
- Red — 期待する動作を記述する、失敗するテストを作成します
- Green — テストを合格させるために必要な、最も簡潔な本番コードを作成します
- Refactor — すべてのテストを合格させたまま、コードの構造を改善します
ルール:
- 失敗するテストなしに、本番コードを書いてはいけません
- テストが失敗するために必要な分だけ書きます(コンパイルエラーも失敗に含まれます)
- 現在失敗しているテストを合格させるために必要な分だけ、本番コードを書きます
TDD の 2 つの流派
| 観点 | Chicago(古典派) | London(モック派) |
|---|---|---|
| 検証方法 | 状態ベース | 相互作用ベース |
| 進め方 | 内側から外側へ | 外側から内側へ |
| 協調対象 | 実際のオブジェクト | モック/スタブ |
| 強み | リファクタリングに強いテスト | インターフェース設計を促進 |
| リスク | 深い依存関係の設定が複雑 | テストが実装に密結合する |
詳しい比較とガイダンスについては、TDD 流派のリファレンスを参照してください。
TDD が特に役立つ場面
- 明確なルールやエッジケースがあるビジネスロジック
- アルゴリズムの設計
- API コントラクトの定義
- バグの再現と修正(最初に失敗するテストを書きます)
TDD が適さない場合がある場面
- 探索的なプロトタイピング(全体像を把握してからテストを書きます)
- UI のレイアウトやスタイル設定
- 一度限りのスクリプト
テストダブル
テストダブルは、テスト中に実際の依存関係を置き換えます。それぞれの種類には異なる用途があります。
| テストダブル | 用途 | 検証するか |
|---|---|---|
| ダミー(Dummy) | パラメーターリストを埋めます。実際に使われることはありません。 | いいえ |
| スタブ(Stub) | メソッド呼び出しに対して、あらかじめ決めた応答を返します。 | いいえ |
| スパイ(Spy) | 後でアサーションするために、相互作用を記録します。 | はい(後から) |
| モック(Mock) | 期待する動作を事前に設定します。正しく呼び出されなければ失敗します。 | はい(その場で) |
| フェイク(Fake) | 簡略化した動作する実装です(例:インメモリリポジトリ)。 | いいえ |
それぞれの種類を使う場面について詳しくは、テストダブルのリファレンスを参照してください。
重要な原則:境界でモックする
テストダブルは、内部の協調対象間ではなく、アーキテクチャ上の境界(ポート、外部サービス)で使用します。内部クラスをモックすると、テストが実装の詳細に密結合し、リファクタリングが困難になります。
テストするもの/しないもの
価値が高い — 常にテストする
- ビジネスルールとドメインロジック
- エッジケース、境界条件、エラー経路
- 状態遷移とワークフロー
- 入力の検証とサニタイズ
- セキュリティ上重要な経路(認証、認可)
- データ変換と計算
価値が低い — 通常は省略する
- ロジックのない単純なゲッター/セッター
- フレームワークが生成するコード(ORM マッピング、ルーティング設定)
- サードパーティライブラリの内部実装(ライブラリのコードではなく、統合部分をテストします)
- プライベートメソッド(パブリック API を通じてテストします)
- ロギングとテレメトリー(ビジネス上重要な場合を除く)
実装と動作、どちらをテストするか
実装ではなく、動作をテストします。 良いテストは、システムが内部で どのように 動作するかではなく、システムが 何を するかを記述します。
実装をテストしている兆候:
- 動作を変えずにリファクタリングしただけでテストが失敗する
- テストが内部メソッドの呼び出し順序をアサートしている
- テストがパブリックな出力ではなく、プライベートな状態を検証している
- 内部クラスの名前を変更すると、無関係な機能のテストが失敗する
動作をテストしている兆候:
- テストがユーザーにとって意味のあるシナリオを記述している
- 内部のリファクタリング後もテストが合格する
- テストがパブリック API を通じて確認できる出力、副作用、状態変化をアサートしている
レイヤー別のテスト戦略
アーキテクチャのレイヤーごとに、適したテストアプローチは異なります。詳しいガイダンスについては、テスト戦略のリファレンスを参照してください。
| レイヤー | 主なテストの種類 | 主な手法 |
|---|---|---|
| ドメイン/ビジネスロジック | 単体テスト | 状態ベースの検証、I/O なし |
| アプリケーションサービス | 単体テスト+統合テスト | インフラストラクチャポートのテストダブル |
| データアクセス | 統合テスト | 実際のデータベース(テストコンテナー、インメモリ) |
| API エンドポイント | 統合テスト+コントラクトテスト | リクエスト/レスポンスの検証 |
| UI コンポーネント | コンポーネントテスト | インタラクションのシミュレーション |
| システム全体 | E2E(選択的) | クリティカルパスのみ |
よくあるアンチパターン
| アンチパターン | 症状 | 対処法 |
|---|---|---|
| 壊れやすいテスト | 動作が変わっていないのに、リファクタリングのたびにテストが失敗する | 内部構造ではなく、公開 API を通じて動作をテストする |
| 実装のテスト | メソッド呼び出しの順序、プライベート状態、内部の配線を検証する | 出力と、観察可能な副作用を検証する |
| 遅いテストスイート | テストスイートの実行に10分以上かかり、開発者がテストの実行を避ける | テストをピラミッドの下層に移し、I/O にはテストダブルを使う |
| 不安定なテスト | コードを変更していないのに、テストがランダムに成功したり失敗したりする | 時間への依存、共有状態、実行順序への依存を取り除く |
| 過剰なモック | 実際のテストロジックよりモックのセットアップが多く、テストが読みにくい | 可能な限り実際の協調オブジェクトを使い、境界でのみモックを使う |
| テストデータの結合 | テストがフィクスチャを共有し、共有データの変更時に失敗する | 各テストで独自のデータを作成し、ビルダー/ファクトリーを使う |
| エラーパスの欠落 | 正常系しかテストされず、本番環境で障害が発覚する | エラーケース、エッジケース、境界条件を明示的にテストする |
| コメントアウトされたテスト | 失敗するテストを修正または削除せず、無効にしている | テストを修正するか、動作が意図的に変更された場合は削除する |
| 巨大なテストメソッド | 複数の実行フェーズとアサーションを含む、50行以上のテスト | 対象を絞ったテストに分割し、セットアップをヘルパーに切り出す |
| アサーションなし | コードは実行するが、何も検証しない | すべてのテストに、少なくとも1つの意味のあるアサーションを含める |
品質チェックリスト
テストの作成時やレビュー時に、このチェックリストを使ってください。
- 動作に焦点を当てている:システムの「どのような動作をするか」を記述し、「どのように」動作するかは記述しない
- 独立している:ほかのテストの実行や状態に依存するテストがない
- 決定的である:どのマシンでも、毎回同じ結果になる
- 高速である:単体テストはミリ秒単位で実行され、スイート全体は5分以内に完了する
- 読みやすい:新しいチームメンバーが実装を読まなくてもテストを理解できる
- 明確に構成されている:各フェーズが明確に分かれた AAA 構造になっている
- 具体的な名前が付いている:テスト名からシナリオと期待される結果が分かる
- エラーパスが網羅されている:正常系だけでなく、エッジケースや失敗もテストされている
- セットアップが最小限である:不要な依存関係やフィクスチャがなく、必要に応じてビルダー/ファクトリーを使っている
- 不安定さがない:時間、順序、環境に依存するテストがない
- 適切なレベルである:信頼性を確保できる最も低いピラミッドレベルでテストしている
- 境界でテストダブルを使っている:内部クラスではなく、アーキテクチャ上のポートでモック/スタブを使っている