agentleFS
trappletrapple/skills/test-driven-development/SKILL.md

test-driven-development

Skill0 starsChanged 4 months agoMITView on GitHub

Use when implementing a feature, bug fix, or behavior change that has testable logic, before writing the implementation. Not for config values, typos, generated code, or throwaway prototypes. Use when user says \"TDD\", \"テスト駆動\", \"test driven\", \"テスト先行\", or when a plan / subagent-driven-development task says to follow TDD.

190 other projects use a version of this, 7 of them edited.

Adopted by

Most recently taken up by

Used with

Projects with it also use chinese-code-review, chinese-commit-conventions and 10 more.

Other files people use alongside this one.

Files found in the same public projects as a version of this one, more often than they turn up anywhere else. Each project counts once, large collections count for less, and this file's own repository is left out.
  • Leodorareluctant259Skill

    chinese-code-review

    中文代码审查规范——在保持专业严谨的同时,用符合国内团队文化的方式给出有效反馈

    Also in 8 projects that use this

  • Leodorareluctant259Skill

    chinese-commit-conventions

    中文 Git 提交规范 — 适配国内团队的 commit message 规范和 changelog 自动化

    Also in 8 projects that use this

  • obraSkill

    diagnosing-superpowers

    Use when a superpowers session went wrong and your human partner wants to know why — repeated work, ignored plans, stumbles, poor results, a skill that didn't fire, "it took too long", "why is it so expensive", "what is it doing" — or wants to build a bug report for the superpowers maintainers, for the current session or a past one identified by id or path, on any harness.

    Also in 19 projects that use this

  • LLl0k0laDSkill

    using-agent-skills

    Use when starting a session or when you need to discover which skill applies to the current task. This is the meta-skill that governs how all other skills are discovered and invoked.

    Also in 39 projects that use this

  • LLl0k0laDSkill

    git-workflow-and-versioning

    Use when making any code change. Use when committing, branching, resolving conflicts, or when you need to organize work across multiple parallel streams.

    Also in 47 projects that use this

  • MrCipherSmithSkill

    constraint-driven-development

    Use when a project needs a persistent, measurable quality bar — starting a project without documented standards, 'set up constraints', or when coverage/perf/a11y thresholds get re-debated per-PR. Produces CONSTRAINTS.md with enforceable numbers, ratchets, and a guard against silent weakening. NOT for throwaway prototypes or a one-off CI setup.

    Also in 15 projects that use this

  • +3 more from Leodorareluctant259
  • +3 more from LLl0k0laD

The file

We split this skill into the rules it gives an agent

1 rule. 0 are common, 1 is distinctive, and 0 are very specific to this project.

  • 0

    Common

    Most of the 178 edited versions we read keep these.

  • 1

    Distinctive

    This author's own choices. Worth a look.

  • 0

    Very specific

    Tied to their project's names, files or tools. Probably skip.

  • Common
  • Distinctive
  • Very specific
  1. Distinctive:

    分類の第一原理: 失敗の帰属は表出形 (assertion / 例外 / crash) ではなく原因主体で判定する。: test 側 (setup / typo / import) 起因なら C、production 側 (欠落機能 / バグ) 起因なら valid RED …

    line 120

“Common” is measured: kept by at least 6 in 10 of the 178 edited versions of this file we could read. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.

Keep what fits and drop the rest. Your version, ready for your agents.
Files
SKILL.md

test-driven-development — テスト先行

テスト → 失敗確認 → 最小実装 → 通過確認 → リファクタ、の順で進める。

Core principle: 失敗を見てないテストは、正しいものをテストしているか分からない。

第二原則: テストは必要十分に。spec の各振る舞いをちょうど 1 回ずつ — 多いほど良い、ではない (下記「必要十分 — 過剰テストの禁止」)。

いつ使うか

対象:

  • 新機能
  • bug fix (まず bug を再現する failing test を書く)
  • 振る舞いの変更
  • リファクタ: 既存テストが green のまま進める。新しいテストは振る舞いを足すときだけ書く

対象外 (確認なしで通常どおり進めてよい):

  • 設定値・定数の変更、typo、振る舞いを変えないリネームや移動 (既存テストで守られているもの)
  • 生成コード
  • 使い捨て試作

テストを先に書く

失敗するテストを先に書き、その失敗を見てから production code を書く。

先にコードを書いてしまったときは、そのままテストを後付けして終わりにしない。テストを書き、コードを一時的に外すか壊して (下記 A-3 の手法) RED を観測してから戻す。失敗を一度も見ていないテストは、正しいものをテストしているか分からないため。

既存コードの bug fix では、失敗する再現テスト → 最小 patch でよい (既存コードを外す必要はない)。詳細は下記「例: bug fix」と「デバッグとの統合」参照。

Red-Green-Refactor

flowchart LR
    RED[RED<br/>失敗するテストを書く] --> V1{失敗を確認}
    V1 -->|期待通り失敗| GREEN[GREEN<br/>最小実装]
    V1 -->|誤った失敗| RED
    GREEN --> V2{全テスト通過}
    V2 -->|通過| REFACTOR[REFACTOR<br/>整理]
    V2 -->|未通過| GREEN
    REFACTOR --> V2
    V2 -->|完了| NEXT[次の機能]
    NEXT --> RED

RED — 失敗するテストを書く

1 つの振る舞い を 明確な名前 で 実コードに対して テストする。

良い例 (node:test):

import { test } from 'node:test';
import assert from 'node:assert/strict';
import { retryOperation } from '../src/retry.mjs';

test('retries failed operations 3 times', async () => {
  let attempts = 0;
  const operation = async () => {
    attempts++;
    if (attempts < 3) throw new Error('fail');
    return 'success';
  };

  const result = await retryOperation(operation);

  assert.equal(result, 'success');
  assert.equal(attempts, 3);
});

悪い例 (mock 中心):

test('retry works', async () => {
  const mock = jest.fn()
    .mockRejectedValueOnce(new Error())
    .mockRejectedValueOnce(new Error())
    .mockResolvedValueOnce('success');
  await retryOperation(mock);
  expect(mock).toHaveBeenCalledTimes(3);
});

→ 名前が漠然、mock の振る舞いをテストしていてコードをテストしていない。

要件:

  • 1 つの振る舞い
  • 明確な名前
  • 実コード (mock は避けられないときだけ)

Verify RED — 失敗を見る (必須)

PJ 標準のテストコマンドで実行する (例: npm test / pytest / cargo test / go test ./... / make test)。長時間 / 出力を後から見たい / 常駐 watch の場合は、PJ のコマンド実行ルール (例: .claude/rules/cmux-command-execution.md 等の別ペイン規約) に従う。

hang しうる実行には必ず時間上限を付ける (無限ループが予想される cycle 等)。目的は「実行全体を外側から有限時間で打ち切る」ことで、実行環境にある任意の機構でよい (GNU timeout / 実行ツール側の timeout 設定 / CI の job timeout 等。timeout コマンドは素の macOS には無い)。runner 内の per-test timeout は同期無限ループを中断できないことがある。観測が返ってこないと下記判別フロー D に到達できない。

# 例: Node プロジェクト
node --test tests/retry.test.mjs

確認:

  • fail している (test setup 起因の error ではなく)。production 起因の例外による fail (欠落 guard の TypeError 等) は assertion fail と同格の valid RED (判別フロー D-1)
  • fail メッセージが期待通り ("retryOperation is not defined" / "expected 'success', got undefined" 等)
  • 機能が無いから fail している (typo で fail しているのではない)

pass している? → 下記「RED が観測できなかったときの判別フロー」へ。短絡的にテストを直すと regression coverage まで消す可能性があるので、まず原因を 3 パターンに分ける。

error している? → 同じ判別フローのパターン C / D を参照。test setup error と production crash で対応が分かれる。

RED が観測できなかったときの判別フロー

テストを書いて実行したが期待した assertion fail が出ない場合、以下のいずれか。Red Flags の「テストが即 pass した」はパターン A-1 のみが該当 — パターン A-2 / A-3 / C / D は正当な観測手順。

分類の第一原理: 失敗の帰属は表出形 (assertion / 例外 / crash) ではなく原因主体で判定する。 test 側 (setup / typo / import) 起因なら C、production 側 (欠落機能 / バグ) 起因なら valid RED (D)。列挙に当てはまらない失敗もこの原理で振り分ける。

A. 即 pass した (期待: fail / 実際: pass)

  • A-1. 既存の振る舞いをテストしている (= 既存 test と重複、または書き間違いで現状を assert している) → そのテストを delete もしくは正しい振る舞いに書き直す
  • A-2. 別 cycle の最小汎用実装で先取り pass している (例: chunk の cycle 1 で slice ループを書いた → cycle 2「last chunk が短い」が即 pass。chunk の cycle 1 で literal 値を返す fake it → cycle 2 で triangulation を起こすか選択) → そのまま regression coverage として残す。trace に「Verify RED は cycle N で済んでいる」と 1 行記録すれば OK。delete しない
  • A-3. 新規振る舞いが現行実装で偶然成立している (例: await の throw 自然伝播 / 副作用ゼロ no-op / 既存 default 値が偶然正解) → production を一時的に「逆向き」に振る舞わせて RED を強制観測 → 元に戻して GREEN 再確認。trace に「forced break で RED 観測」と記録。これも valid。手法は 2 つ:
    • A-3a. negation 注入: 既存ロジックに 1 行突っ込む (throw new Error('intentional RED') / 既存 return 値を反転 / 条件を ! で反転)。「既存コードを書き換える」型
    • A-3b. counterfactual injection: 「振る舞いを成立させているのが コードの absence」(catch がない / 包んでいる try がない / fallback がない) のとき、あえて壊す wrapper を一時的に追加 → 観測 → 削除。例: await の throw 自然伝播をテストする場合、production を一時的に try { ... } catch { return null; } で包んで suppress → RED 観測 → wrapper 削除して GREEN 再確認

B. fake it vs triangulation の選択 (A-2 の派生)

  • fake it: cycle 1 で literal を返す (例: return [[1,2],[3,4]]) → cycle 2 で別データ点を試し、triangulation で汎用化を強制
  • triangulation 不要 (汎用最小): cycle 1 で最小汎用実装を直接書く。後続 cycle が即 pass しても A-2 として残す

どちらでも OK。1 PJ / 1 spec の中では一貫させる。「先に書く実装の粒度を最小にしすぎて 50 cycle 必要になった」「逆に最初から汎用にして spec 漏れの test ケースを足し忘れた」はどちらも不良。spec のデータ点数 (3〜5 件) で fake it、データ点数 (10+) で汎用最小、を目安とする

C. test setup error (import 失敗 / typo / module 解決失敗)

→ error の原因を直して再実行。fail を観測するまでが RED

  • 特例: import 先 module が未作成 (ERR_MODULE_NOT_FOUND 等) → 対象 symbol の 空 stub (export function name() {} / export const name = undefined; 等) を作って module 解決を通す。空 stub は assertion を pass させる能力がないため、「テストより先に書いた production code」には当たらない。次の実行で assertion fail まで到達する

D. production 起因で assert に到達しない / 例外 fail する (runner の crash / OOM / timeout、または production が throw した例外による test fail。無限ループ / null deref / 欠落 guard の TypeError など)

→ production 側の欠落機能 / バグが原因なら valid な RED とみなす。runner が生きていて「test fail」と表示されるか、runner ごと crash するかは問わない (原因主体で判定)。2 sub-case:

  • D-1. 失敗が「今 cycle のテスト対象 behavior の欠落」そのもの (例: nullish guard 欠落による TypeError の test fail / 「size=0 で RangeError」をテストしたら guard 未実装ゆえに無限ループ) → その失敗自体を valid RED として記録。次の edit (= guard 実装) がそのまま GREEN。「最小 guard を一時挿入 → 観測 → 本実装」の二段は不要、1 edit で完結してよい
  • D-2. crash が今 cycle のテスト対象 behavior と incidental (例: 別 path の null deref がたまたま当該 test の setup 段階で発火し、本来見たい assertion まで届かない) → production に最小 guard (loop 上限 / early return / null check) を入れて assertion メッセージまで到達させてから記録する方が望ましい (後続の Verify GREEN との対称性のため)

GREEN — 最小実装

テストを通す最小のコード。

良い例:

export async function retryOperation(fn) {
  for (let i = 0; i < 3; i++) {
    try {
      return await fn();
    } catch (e) {
      if (i === 2) throw e;
    }
  }
}

悪い例 (YAGNI 違反):

export async function retryOperation(fn, options = {}) {
  const { maxRetries = 3, backoff = 'exponential', onRetry } = options;
  // ...大量の未要求機能
}

機能を足すな。他コードのリファクタするな。テストを超えた "改善" もするな。

Verify GREEN — 通過を見る (必須)

node --test tests/retry.test.mjs

確認:

  • 該当テストが pass
  • 他のテストも通過したまま
  • 出力が pristine (warning / error が混じっていない) — ただし pristine 要件は「最終 Verify GREEN」に対して。A-3 forced break / D-1 の失敗観測など意図的な失敗実行の出力には適用しない

テストが fail? コードを直す (テストを緩めるな)。 他テストが fail? いま直す。あとでは無理。

REFACTOR — 整理する

GREEN 後だけ:

  • 重複を除く
  • 名前を改善する
  • ヘルパー関数を抽出する

テストは green を保つ。振る舞いは追加しない。

次へ

次の機能の RED から繰り返す。

良いテストの基準

観点良い悪い
最小1 つの振る舞い。"and" が入ったら分割test('validates email and domain and whitespace')
明確名前が振る舞いを説明するtest('test1'), test('works')
意図望ましい API を示すコードがどう動くか曖昧
必要十分spec の 1 振る舞い = 1 テスト同一分岐を入力違いで水増し
KISSsetup 数行 + 実行 + assert でベタ書きテスト内に分岐 / ループ / 過剰な helper

必要十分 — 過剰テストの禁止

「テスト先行」は「テストを多く書け」ではない。テストも保守対象のコード。冗長なテストは regression 検出力を上げず、refactor のコストとノイズだけを増やす。

テストを 1 本足す前のゲート:

この入力を正しく扱うために、既存テストが強制していない
production code の追加・変更が必要か?
  Yes → 別 equivalence class。テストを書く (triangulation)
  No  → 同一 class。書かない
  • class は入力の字面ではなく「要求される production code」で切る。空文字 / 空白のみ / null は見た目が似ていても、=== '' / trim() / ?. と要求コードが違えば別 class。逆に -1 と -100 は同じ guard で扱われるので同一 class
  • 代表点の選び方: spec が数値・長さ等の境界を明示するなら、境界ちょうどの値を代表にする (例: size <= 0 なら 0)。境界の内側の追加点 (-1, -100) は書かない
  • 補助質問 (迷ったら): 「このテストが fail する production の変更は、spec 違反か?」 — No (振る舞い不変の refactor で壊れる / 既存テストと同じ変更でしか壊れない) なら書かない。※ Yes でも上のゲートが No なら書かない — mutation を捏造すればどんなテストも正当化できてしまうため、主ゲートは常に「要求 production code」基準

書かないもの:

対象理由
言語 / framework / library 自体の動作自分のコードではない (JSON.parse が動く証明は不要)
同一 equivalence class の重複入力class ごとに代表 1 点 (+ spec が明示する境界ちょうどの値) で十分
実装詳細 (内部 helper の呼び出し回数 / 順序)refactor で壊れる。public な振る舞い経由で assert する
spec に無い hypothetical 入力への防御YAGNI。spec が増えたときに RED から書く
private 関数の直接テストpublic API 経由で cover されるならそれで十分

テストコード自体も KISS:

  • setup 数行 + 実行 1 行 + assert 数行、で読み切れる形が正。ベタ書きでよい
  • テスト内に if / try-catch での成否分岐を書かない。分岐したくなったら別テストに分割
  • table-driven / パラメタ化は境界値の列挙にだけ使う。同一 class の水増しに使わない
  • setup が肥大したら helper 追加より先に設計を疑う (「スタックしたとき」参照)

増やす方向の合理化:

言い訳現実
「テストは多いほど安全」冗長テストは検出力を上げない。refactor 阻害とノイズが増えるだけ
「念のため全パターン」equivalence class の代表 + 境界で証明は完結する
「カバレッジ 100% が目標」カバレッジは結果であって目標ではない。目標は spec の振る舞い網羅

ユーザーが「全パターン個別にテストして」と明示要求したとき:

  1. default: 代表点に絞って実装し、class 分割の根拠と「要求された全入力がこの N 本で cover される」ことを提示する
  2. override: 根拠を見たうえでユーザーがなお個別列挙を求めたら (監査要件等)、それを spec として従う
  3. 応答が得られない場合 (非対話実行): default のまま確定し、絞った判断と根拠を成果物 / 最終報告に 1 行残す

逆方向にも注意: spec にある振る舞い・境界・エラー経路を「過剰」と呼んで省くのは spec 違反。必要十分 = spec の全振る舞いを、それぞれちょうど 1 回ずつ。

こうなったら立ち止まる

  • テストが即 pass した (※「RED が観測できなかったときの判別フロー」A-1 のみが該当。A-2 重複先取り / A-3 偶然成立 / D runner crash は valid な観測なので除外)
  • なぜ fail したか説明できない
  • 実装後にテストを書いて、失敗を一度も見ていない

例: bug fix

Bug: 空 email を accept してしまう

RED

test('rejects empty email', async () => {
  const result = await submitForm({ email: '' });
  assert.equal(result.error, 'Email required');
});

Verify RED

node --test tests/form.test.mjs
# FAIL: expected 'Email required', got undefined

GREEN

export function submitForm(data) {
  if (!data.email?.trim()) {
    return { error: 'Email required' };
  }
  // ...
}

Verify GREEN

node --test tests/form.test.mjs
# PASS

REFACTOR: 他フィールドも増えそうなら validation を抽出。

完了前チェックリスト

work 完了マーク前に:

  • 新規 / 変更した関数すべてに test がある
  • 各 test の fail を見た あとで実装した
  • 各 test は 期待した理由で fail した (機能が無いから / typo ではなく)
  • 通過のため最小コードを書いた
  • 全 test pass
  • 出力が pristine (warning / error 無し)
  • mock は避けられない箇所だけ
  • spec 上ありうる edge case / エラー経路を cover した (spec 外の hypothetical は書かない)
  • 冗長テストが無い — 同一 equivalence class の重複 / 実装詳細 / library 自体のテストをしていない (「必要十分」参照)

✓ にならない項目があれば、その項目を満たしてから完了にする。

スタックしたとき

症状対応
テストの書き方が分からない望む API を「こう呼べたら」で書く。assertion 先。それでもダメなら聞く
テストが複雑すぎる設計が複雑。interface を簡略化
mock 多すぎ結合度が高い。dependency injection で疎にする
setup が巨大helper 抽出。それでもダメなら設計を見直す

デバッグとの統合

bug を見つけたら まず bug を再現する failing test を書く。TDD cycle に乗せる。テストが fix の証明と regression 防止の両方になる。

対象外に挙げた自明な修正を除き、bug 修正は再現テストから始める。

テスト anti-pattern (頻出 3 種)

1. mock の振る舞いをテストしている

// 悪い: mock の呼ばれ方を assert している
expect(mock).toHaveBeenCalledWith(...);
// 良い: real な値を作って結果を assert
const result = await fn(realInput);
assert.equal(result, expected);

mock は外部依存 (network, fs, time) を切るためだけに使う。production code の挙動の代わりに mock の挙動をテストしない。

2. test-only な method を production class に追加

「test しやすいように」と production 側に __testOnly_reset() 等を生やすのは禁止。dependency injection で外から差し替えられるようにする。

3. 依存を理解せず mock する

「とりあえず mock しておけば pass する」で書くと、production で何が起きるか保証されない。mock するなら production 実装と同じ contract を mock 側でも満たす (or 差し替え可能な小さい interface に切り出す)。

不完全な mock / 後付け test / 過剰 mock / 過剰テストなど他の anti-pattern と詳細な gate function は testing-anti-patterns.md 参照。

PJ 規約との接続

  • Fail Fast 原則 (PJ CLAUDE.md / .claude/rules/error-handling.md 等に定義があれば): silent skip / fallback で続行しない設計を testing でも貫く。テストでも catch して empty を返すような assertion は NG
  • コマンド実行ルール (PJ CLAUDE.md / .claude/rules/cmux-command-execution.md 等に定義があれば): test 実行が長時間 / TUI / watch の場合は別ペイン / 別 surface 規約に従う
  • 副作用は引数注入で mock 可能に: DB / 外部 API / fs などの副作用は引数で渡し、test 側で stub を差せるようにする。PJ で確立した DI パターンがあればそれに合わせる

Download now (ZIP)

Does it work?

Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.

No reviews yet

People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.

  • It worked
  • Partly
  • It didn't
What did you use it for? e.g. Reviewing pull requests in a Rails app

Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.

Your trail

Files you open will collect here, so you can find them again.

Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.

Now do this for your own.

Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.