tdd について
振る舞いを重視したテスト駆動開発を導くスキルです。テスト境界の合意からレッド・グリーンの反復までを扱い、リファクタリング後も有用なテストと、フィクスチャや実行結果の確認方法を示します。
主な機能
- 01公開テスト境界の合意
- 02振る舞いに基づくテスト
- 03レッド・グリーンの反復
- 04実装依存テストの回避
- 05フィクスチャと実行の検証
活用シーン
- 01報告された不具合のテスト設計
- 02テスト主導で段階的に機能を追加
- 03テストが対象ケースに届くか確認
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ hexsleeves/ skills --skill tddインストール後、Claude Code で/tddを入力して読み込まれたか確認します。
同梱ファイル
SKILL.md と一緒にインストールされる参考資料やスクリプト。
LICENSEmocking.mdtests.mdagents/openai.yaml詳細
テスト駆動開発
TDD は、赤 → 緑のループです。このスキルは、テストに価値を持たせるためのリファレンスです。優れたテストとは何か、テストをどこに配置するか、アンチパターン、ループのルールを説明します。すべてのセクションは各サイクルに適用されます。ループの後ではなく、ループの前と途中に参照してください。
コードベースを調べる際は、テスト名やインターフェースの用語がプロジェクトのドメイン用語と一致するように、GLOSSARY.md(存在する場合)を読み、作業対象の領域に関する ADR に従ってください。
優れたテストとは
テストは実装の詳細ではなく、公開インターフェースを通じて動作を検証します。コード全体が変わっても、テストは変える必要がありません。優れたテストは仕様のように読めます。「ユーザーが有効なカートでチェックアウトできる」とあれば、どの機能があるのかが正確にわかり、内部構造を気にしないためリファクタリング後もそのまま使えます。
ゲームやシミュレーションのルールでは、ルールのテストはルールが正しいことを証明するものであり、バランスが取れていることを証明するものではありません。退化した戦略(何もしない、最適な場所に居座る、機械的に一掃する、1 人の Player だけが行動し、ほかは何もしない)を使い、本番の移動またはインタラクションモデルを通じてルールを実行し、それぞれのスコアが注意深く行動する戦略を明確に下回ることを検証するテストを少なくとも 1 つ作成してください。ドライバーはルールを言い換えるのではなく、本番のヘルパーから入力を受け取ります。また、実際のプレイヤーより有利にならないようにしてください。移動し、1 ティックですべての場所で行動することはありません。
例については tests.md、モックのガイドラインについては mocking.md を参照してください。
シーム:テストを行う場所
シームとは、テスト対象となる公開境界です。内部に立ち入らずに動作を観察するインターフェースのことです。テストはシームで行い、内部実装を対象にしてはいけません。
事前に合意したシームだけをテストしてください。 テストを書く前に、テスト対象のシームを書き出してユーザーに確認します。確認されていないシームでテストを書いてはいけません。ユーザーが承認した計画に基づいている場合、モジュールの既存の仕様ですでにテストされているシームは合意済みとみなせます。最初のテストの前に、シームを 1 行で述べてそのまま進めてください。新しいシームを追加する場合、またはモジュールに仕様がない場合に限り、質問してください。すべてをテストすることはできません。そのため、事前にシームに合意しておけば、あらゆるエッジケースではなく、重要な経路や複雑なロジックにテストの労力を集中できます。
シームを提示する前に、リポジトリに記載されたテストルール(CODING_STANDARDS.md、CONTRIBUTING.md、または AGENTS.md がレビュー規則として指定するファイル)を読んでください。「競合状態の修正には Postgres テストを行う」のように、変更の種類に応じたシームを定めるルールがある場合、その範囲では既存仕様に基づく既定の判断より優先されます。
次のように尋ねてください。「公開インターフェースは何で、どのシームをテストすべきですか?」提案する各シームについて、何を検出し、何を検出できないかを 1 行で説明してください。
本番のラッパーを通してテストしてください。 本番コードが呼び出しをラップする場合(クライアント設定、キャッシュ、ミドルウェアなど)、統合テストでもそのラッパーを通すか、同じ設定を使い、テスト内でラッパーを明示してください。ラッパーを迂回するシームでは別の経路を検証することになります。たとえば、デプロイ済みクライアントがクエリーキャッシュを有効にして実行するクエリーは、そのキャッシュ設定を適用してテストする必要があります。
プロセスシグナルとターミナル状態は、注入されたシームの外側でも 1 回確認する必要があります。偽のエミッターやストリームでは、ターミナルのハングアップ、それに続く stdio エラー、またはデフォルトの終了動作を確認できません。ビルド済み CLI に対して実際の PTY を使った実行(SIGHUP には tmux kill-session、SIGINT には send-keys C-c)を追加するか、ウィンドウが短すぎて確認できない場合は、ハンドラーを再現する最小限のスクリプトを追加してください。
重なり合うタイマーを扱うには、タイマーを保留できるクロックが必要です。同時に 2 つのタイマーまたはリトライが保留されることに関わる動作(重複排除、集約、キャンセル、既存の待機への合流)を扱う場合、偽の sleep は、テストが解放するまでリゾルバーを保持します。時間を進めると同時に解決するクロックでは、タイマーが直列化され、重なりを確認できません。赤の段階でテストが成功することは、シームを変更する合図であり、アサーションを変更する合図ではありません。
アンチパターン
- 実装との結合:内部の協力コンポーネントをモックする、プライベートメソッドをテストする、またはサイドチャネル(インターフェースを使わずにデータベースを照会するなど)を通じて検証します。見分けるポイントは、振る舞いが変わっていないのに、リファクタリングでテストが失敗することです。
- 同語反復:コードと同じ方法で期待値を再計算するアサーション(
expect(add(a, b)).toBe(a + b)、手作業で同じ方法により作成したスナップショット、自分自身と等しいと主張する定数など)を使うため、最初から必ず成功し、コードと食い違うことがありません。期待値は、正しいことが確認済みのリテラル、解答付きの例、仕様など、独立した信頼できる情報源から取得する必要があります。 - 水平分割:すべてのテストを先に書き、その後ですべての実装を行います。テストをまとめて書くと、_想像上の_振る舞いを検証することになります。ユーザーに見える振る舞いではなく、ものの_形_をテストするため、実際の変更に対するテストの感度が低くなり、実装を理解する前にテストの構成を確定してしまいます。代わりに垂直スライスで進めます。つまり、テストを1つ書き、実装を1つ行うサイクルを繰り返し、各テストを前のサイクルで得た知見に応じて軌道を修正するトレーサーバレットとして使います。
ループのルール
- 失敗してから成功させます。 まず失敗するテストを書き、それを通すために必要な最小限のコードだけを書きます。将来のテストを先回りしたり、憶測で機能を追加したりしないでください。
- 失敗は実行数で確認します。 失敗数だけでなく、テストランナーのテスト実行数を確認します。失敗とは、少なくとも1件のテストが実行され、想定どおりの理由で失敗した状態です。テストが0件の実行は、計測器の故障です。引数を glob 展開するテストランナー(
node --testなど)は、シェルで引用符を付けていても、Next.js の動的ルートのような[...]セグメントを含むパスには一致しないことがあります。ファイルに一致する glob を渡し、テスト数を確認してください。 - 固定した動作は、ミューテーションで失敗を確認します。 コードがすでに実現している動作を固定するテスト(特性テスト、呼び出し回数の予算、回帰テストなど)は、最初の実行で成功します。また、ヘルパーがないために発生するコンパイルエラーは、失敗ではありません。最初の実行の前に、コードまたは仕様から期待値を導き出し、そのテストが検出するはずの最小限の一時的な変更をテスト対象のコードに加えて、失敗することを確認してから元に戻します。この手順をシームの提案に記載し、事前にユーザーの同意を得てください。ミューテーションを加えてもテストが成功する場合は、テストフィクスチャが誤っています。テストが依存する状態(マージされていない部分、コールドキャッシュ、順序、クエリテキストに一致する障害フック)がアサーション時点でも残っているか、ストレージエンジンやランタイムによって先に消されていないかを確認してください。
- フィクスチャが対象ケースに到達するようにします。 対象ケースに到達する前に上限に達する(壁、上限、空のキュー、タイムアウトなど)実行は、誤った理由で成功しています。そのため、テストでは到達したこと(歩いた距離が許容値を超えたこと)をアサートしてください。システムが自力で修復する最終状態ではなく、ガードが保護するプロパティをアサートします。何かが拒否またはブロックされることを確認するテストでは、ブロッカーを完全に取り除くミューテーションを1つ加え、ガードを取り除いたミューテーションに対して新たにファズテストを実行してから、成功を確認します。また、実行回数に数える前に、ミューテーションの手順でファイルが変更されたことをアサートしてください。
- 許容値を変更したら、記録されたケースを再実行します。 許容値、タイムアウト、リトライ回数、しきい値を縮小または置き換える変更を行う場合、以前の値を設定したコミットと PR(
git log -S <CONSTANT>)を特定し、そこに記録されたストレステストまたはケースを再実行します。同じ条件で main と交互に実行して比較してください。通常速度での成功は、低速経路向けに設定された定数の妥当性を示すものではありません。 - 実行ごとに変わる値には、3回の成功が必要です。 実行ごとに変わる値(コミット SHA、タイムスタンプ、ランダム ID、一時パスなど)を含むフィクスチャを使った新しいテストは、成功を数える前に少なくとも3回成功する必要があります。それらの値から作られた期待値のコレクションは、実際のコレクションと同じ方法で正規化します(両方をソートするか、集合として比較します)。
- 普遍性を主張する名前では、到達可能な範囲をすべてテストします。 あらゆる入力についてのプロパティを主張する名前(「すべて」「決して」「常に」「どちらも…できない」など)を持つテストでは、その機能が最初に使う値ではなく、入力を生成するコードパスから導いた、本番環境で到達可能な範囲を実行します。その範囲が大きすぎて全件をテストできない場合は、何をサンプリングするのかが名前で分かるようにします。
- シード付きファズテストでは、異なるケースの数を数えます。 JavaScript では、整数の安全性を保つ PRNG(
Math.imulベースの mulberry32 やライブラリなど)を使います。積が 2^53 を超えて短い周期に陥る、倍精度浮動小数点数を使った古典的な LCG は決して使わないでください。失敗数を発生率として扱う前、または2つの失敗数を比較する前に、生成された異なるケース数(new Set(cases.map(JSON.stringify)).size)を数え、実行数とともに報告します。 - 値の伝播はセンチネルで確認します。 設定値または環境変数が利用先に渡されることをテストする場合、フィクスチャの値は、コードにあるすべてのデフォルト値とフォールバック値と異なるものにします(
{}ではなく{"test":"key"})。また、それぞれの利用先に値が届いたことをテストでアサートしてください。フィクスチャがデフォルト値と同じだと、値が渡された場合も破棄された場合もテストは成功します。 - テストランナーがなければ、本番コードを書きません。 テストランナーを利用できない場合(リモートマシンがオフライン、依存関係をインストールできないなど)、その動作のための本番コードを書くのを中止します。テストランナーを復旧して、失敗を確認してから再開するか、中止してユーザーに報告してください。
- 一度に1つのスライスだけを扱います。 1サイクルにつき、1つのシーム、1つのテスト、1つの最小限の実装だけにします。
- リファクタリングはこのサイクルに含めません。 リファクタリングはレビュー段階で行います(
code-reviewスキルを参照)。失敗 → 成功の実装サイクルでは行いません。