test-first について
test-firstは、エージェント向けのTDDスキルで、プロダクションコードより先に失敗するテストを書くことを求めます。合意済みの公開シームで小さなテストと実装を繰り返し、最後に範囲を定めたタスクゲートを実行します。
主な機能
- 01プロダクションコード前の失敗テスト
- 02垂直スライスでのテストと実装
- 03合意済みの公開シームでテスト
- 04範囲を定めたタスクゲートと最終確認
- 05要件との対応をドキュメントに記録
活用シーン
- 01テスト先行のサイクルで機能を追加
- 02不具合を先に再現して修正
- 03完了前に変更対象と依存先を確認
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ jayden-dang/ skills --skill test-firstインストール後、Claude Code で/test-firstを入力して読み込まれたか確認します。
同梱ファイルeval.jsonTESTS.md
詳細
TDD
鉄の掟
NO PRODUCTION CODE WITHOUT A FAILING TEST FIRST
テストより先に実装を書きましたか? それを削除してください。「参考として残す」のでも、「テストが追いつく間に調整する」のでもありません。そうすると、求められていたものではなく、自分が作ったものをテストすることになります。削除するとは、削除するということです。テストからもう一度実装してください。この掟の文言に背くことは、その精神にも背くことです。
例外が認められるのは、ユーザーが明示的に許可した場合だけです。一時的な動作確認用コード、生成コード、純粋な設定が該当します。「今回だけ省略する」は例外ではありません。このスキルが阻止するためにある、もっともらしい言い訳です。
テストを置く場所:事前に合意したシーム
シームとは、内部に踏み込まずに動作を観察できる公開インターフェースです。テストは、機能の design.md にある テスト対象のシーム に記載されたシームでのみ行います。その表で確認できないシームに対するテストの作成は拒否してください。この作業に design.md がありませんか(ティア 0/1 の変更、レガシー領域など)。最初のテストを書く前に、シームをユーザーに提案して合意を得てください。テストにかけられる労力には限りがあります。合意したシームを使うことで、あらゆる内部の詳細ではなく、重要な経路に労力を割り当てられます。
サイクル
一度に垂直スライスを一つ進めます。動作を一つ → テストを一つ → 最小限の実装を一つ → 繰り返します。各サイクルでは、直前のサイクルから得た学びを反映します。テストをすべて先に書き、その後でコードをすべて書くことは決してしないでください。まとめて書いたテストは、想像した動作を実証するだけです。
RED — 失敗するテストを一つ書きます。 動作は一つに絞り、ドメインの言葉で動作を表す名前を付け、合意したシームで実際のコードに対する期待値を設定します。アプリケーションやテストのソースに要件 ID を埋め込むことは求めないでください(ドキュメントのみの基盤)。タスクレポート/仕様レビューで、テストを要件 ID に対応付けます。バグを修正する場合は、まずルートコーズを特定するために root-cause を使うことが必須のサブスキルです。RED テストは修正前に、そのバグの理由で失敗し、そのバグを再現しなければなりません。
RED を検証 — 必須、決して省略しないでください。 docs/agents/project.md に記載された単一ファイル用コマンドでテストを実行します。失敗すること、失敗メッセージが想定どおりであること、そしてタイプミスやインポートエラーではなく、動作が存在しないために失敗していることを確認します。テストがすぐに通りましたか? すでに存在する動作をテストしています。コードではなく、テストを修正してください。失敗ではなくエラーになりましたか? 正しく失敗するまで、エラーを修正して再実行してください。
GREEN — 通過する最も単純なコードを書きます。 憶測によるパラメーターや追加機能を加えず、無関係なコードにも手を加えないでください。
GREEN を検証 — 必須です。 新しいテストの対象をビルドして実行する、最も範囲の狭いコマンド(単一ファイルのパターン)を実行します。新しいテストが通ること、その実行でほかのすべてのテストも引き続き通ること、出力に警告も余計なエラーもなく、「あとで整理する予定」のものも何もないことを確認します。新しいテストが失敗しましたか? テストを緩めず、コードを修正してください。ほかのテストが失敗しましたか? ほかの作業に取りかかる前に、今すぐ修正してください。
REFACTOR — GREEN の間だけ行います。 重複を取り除き、名前を改善し、ヘルパーを抽出します。変更のたびに、同じコマンドを再実行してください。動作は追加しないでください。
その後、次のスライスに進みます。
タスクゲート — 作業完了を宣言する前に一度だけ
上記の各ステップは対象を絞ります。広範囲の確認は最後のコミット時に一度だけ行います。
- 変更したすべてのユニットと、それに依存するすべてのユニットのテスト。プロジェクトのタスクゲートコマンドを使います。なければビルドグラフから導き出します(変更したパッケージとその逆依存関係)。
- 同じユニットに対するLint とフォーマット。セッション開始時に記録した Lint のベースラインと照合します。ベースラインにない指摘は自分のものです。ベースラインが赤くなるのを回避するために、Lint ルールを抑制してはいけません。
- 変更がビルドグラフで把握できない範囲に及ぶ場合は、スイート全体に範囲を広げます。対象となるのは、スキーマやマイグレーション、共有フィクスチャやテストデータ、ビルドマニフェストや機能フラグ、生成されたコントラクト、ほぼすべてが依存するルートパッケージです。判断したうえで、理由を明記します。
スイート全体の実行が短時間(数分程度)で済む場合は、それ自体がゲートです。実行してください。実行した範囲を報告します。Task gate: <commands> — covering <units>、または Whole suite: <command> — escalated: <reason> と記載してください。ゲートの実行をスイート全体の実行として報告してはいけません。スイート全体は完了時に実行します。
要件との紐付け(ドキュメントのみ)
要件 ID は docs/specs/**(requirements.md、設計ドキュメントの Satisfies:、タスクの _Requirements: フッター)に記載されています。テストでは、ドメインの用語で動作を検証します。
- コンシューマーアプリケーションのコードでは、テスト名に
/// REQ:、@CODE-N.M、または ID を含めることを求めないでください。レガシーの注釈はaudit-traceで無視されます。 - 新しい動作がタスクの概要にある要件に対応していることを確認してください。該当する要件がない場合、スコープを独自に作るのではなく、その不足を指摘します(ティア ≥1)。
- 例外: このスキルセットリポジトリ自体のパックテストでは、パックをテストする製品フィクスチャとして、検索可能な
CODE-N.Mトークンを埋め込んでもかまいません。これはコンシューマー向けの規約ではありません。
docs/agents/project.md がない場合は、そのことを伝えて configure-repo を提案してください。それでも、合意した接点にドメイン名を付けたテストを作成します。
テスト標準ドキュメント(任意)
適用対象: 接点、モックの境界、スイートコマンド。読み込み:
skills/project/define-system-doc/consult-recipe.md。パス: docs/standards/testing.md
(パックの SSOT は、既存の規則として扱います)。存在する場合は、このスキルの鉄則に加えて適用します(鉄則を弱めてはいけません)。存在しない場合は何もしません。重大な場合に限り /define-system-doc standards/testing を一度だけ提案してください。自動で呼び出してはいけません。
アンチパターンと対策
| アンチパターン | 見分け方 | 対策 |
|---|---|---|
| 同語反復テスト | コードと同じ方法で期待値を再計算するため、必ず成功します(expect(sum(items)).toBe(items.reduce(...))) | 既知の正しいリテラル、具体的な計算例、要件の記述など、独立した正解の情報源から期待値を取得します |
| 実装に結合したテスト | 動作に変更がなくてもリファクタリングで壊れます。呼び出し回数や順序、プライベートメソッドを検証する、またはサイドチャネル(インターフェースではなく生の DB クエリなど)経由で検証します | 公開された接点のみを通じて、観測可能な動作を検証します |
| モックの動作をテスト | モックが存在することや呼び出されたことをアサーションで確認するため、モックを削除するとテストが失敗します | 実際のコンポーネントをテストするか、モックに対するアサーションを完全にやめます |
| 本番クラスのテスト専用メソッド | テストファイルからのみ呼び出されるメソッド(例:destroy()) | テストユーティリティに移します。本番クラスには本番用 API だけを持たせます |
| 不完全なモック | テストがたまたま読み取るフィールドしか持たないため、省略したフィールドによって統合時に問題が起きます | 実際のデータ構造全体を反映します。実際のレスポンスに含まれるすべてのフィールドを含めます |
| 自分のモジュールをモックする | 「分離する」ために内部の協調要素をスタブ化します | システム境界(外部 API、時間、乱数、場合によっては FS/DB)のみをモックします。自分が所有するコードは決してモックしません。必要に思える場合は、設計の結合度が高すぎます。依存関係を注入してください |
モックのセットアップがテストロジックより長い場合、またはモックを削除すると失敗するテストがある場合は、モックに問題があります。実際のコンポーネントを使った統合テストを優先してください。
よくある合理化
| 考え | 実際のところ |
|---|---|
| 「テストするには単純すぎる」 | 単純なコードも壊れます。テストにかかる時間は1分です |
| 「すぐ後でテストを追加する」 | 後から書いたテストはすぐに通り、何も証明しません。テストが何かを検出する場面を見たことがありません |
| 「テストを後から書いても、本質的には同じことができる」 | 後から書くテストは「これは何をするのか?」に答え、先に書くテストは「これは何をするべきか?」に答えます。前者は実装に左右されます |
| 「手動で一通り試した」 | その場限りで、記録も再現もできません。次の変更で消えてしまいます |
| 「何時間もかけた作業を削除するのはもったいない」 | サンクコストです。テストされていないコードを残すことこそ、実際の無駄です |
| 「テストを書いている間、参考として残しておこう」 | それは手順を増やしてテストを後から書いているだけです |
| 「テストを書くのが難しい」 | テストが難しいということは、使いにくいということです。テストを省略せず、インターフェースを簡素化してください |
| 「TDDは作業を遅らせる」 | タイピングよりは遅いですが、デバッグよりは速いです |
| 「このファイルには、そもそもテストがない」 | 変更するのですから、その変更にはテストを追加します |
| 「安全のため、GREENのたびにスイート全体を実行する」 | タスクごとに18分の実行を6回行っても、タスクゲートと最後の実行以上の効果はありません。各ステップの範囲を絞り、ゲートは一度だけ実行してください |
| 「範囲を絞った実行が通ったので、テストは通った」 | 実行した範囲を明示してください。「テストは通った」はスイート全体についての主張です |
| 「Lintはもともと失敗していたので、ルールを許可してよい/チェックできない」 | ベースラインと比較してください。ルールを抑制すると、以前からの問題に新たな指摘が埋もれてしまいます |
| 「これはマイグレーションファイルにすぎず、クレートのテストでカバーされる」 | すべてのテストがそのスキーマを読み込みます。上位にエスカレーションしてください |
危険信号 — 作業を止めて最初からやり直す
- 実装が存在するのに、そのテストがない
- 新しいテストが初回の実行で通った
- REDでテストが失敗した理由を説明できない
- テストが、設計のシーム表にないシームを対象にしている
- 新しい振る舞いが、ブリーフの要件(ティア ≥1)に対応付けられていない
- 「今回だけ」「参考として残す」「手順ではなく本質」「このケースは別」
どれか一つでも当てはまる場合は、テストされていないコードを削除し、REDに戻ってください。
完了を宣言する前に
- 新しい振る舞いすべてについて、想定どおりの理由で最初に失敗する様子を確認したテストがある — エラーパスと境界値も振る舞いに含まれます
- 最終コミットで、範囲を明示したタスクゲートが成功している — または、エスカレーションの理由を示したうえでスイート全体が成功している。出力に問題がなく、ベースラインにないLintの指摘がない
- 新しい振る舞いはすべて、タスクブリーフ/レポートの要件に対応付けられている(IDはドキュメントに記載し、テストソースに記載する必要はありません)
- すべてのテストが、
design.mdで合意したシーム(またはユーザーと合意したシーム)に配置されている - モックはシステム境界に限られ、データ構造が完全で、モックに対するアサーションがない
チェックできない項目がありますか? その作業は未完了です。必須サブスキル:完了を宣言する前にprove-claimを使用してください。