spec-driven-tests について
spec-driven-testsは、パッケージの実装から観測可能な動作を抽出し、それに基づいてユニットテストを再構築するコーディングスキルです。安定したルールIDと双方向のテスト参照で、カバレッジを追跡できます。
主な機能
- 01実装から観測可能なルールを抽出
- 02安定したルールIDでSPEC.mdを作成
- 03仕様のセクションごとにテストを再構築
- 04エッジケースと参照を検証
- 05見つかった問題を記録して対応
活用シーン
- 01重複するパッケージテストを統合
- 02各動作ルールに対応するテストを追跡
- 03動作の記述中に実装の不整合を発見
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ tldraw/ tldraw --skill spec-driven-testsインストール後、Claude Code で/spec-driven-testsを入力して読み込まれたか確認します。
詳細
仕様駆動のテスト再構築
パッケージの実装から振る舞いの仕様を導き出し、その仕様の表現として単体テストスイートを再構築します。すべてのルールには安定した ID が付与され、すべてのテストは自身が表現するルールを引用するため、カバレッジを双方向に grep できます。
モデルとなるのは PR #9158(packages/state、仕様は packages/state/SPEC.md)と PR #9172(packages/store、仕様は packages/store/SPEC.md)です。開始する前に両方の PR と両方の仕様を読んでください。これらがフォーマット、トーン、PR 構造を定めています。(#9172 の仕様は、まだマージされていない場合はそのブランチ上にあります。)
プロセス
1. ベースライン
作業ツリーに自分以外による未コミットの変更がある場合は、中断してブランチを作成する前に確認してください。他人の作業を無言で stash したり引き継いだりしないでください。
ブランチを作成します。パッケージ内で pnpm test run を実行し、ファイル数とテスト数を記録します。そのうちいくつが fuzz スイートに属するかもメモしてください。fuzz スイートは手を付けず、その(しばしば大量の)生成されるカウントは、後で報告するすべてのカウントから除外します。既存のテストファイルをマッピングし、統合すべき重複または並行するスイートがないかメモしてください(store パッケージにはテストの世代がまるごと 2 つありました)。
2. 実装を読む
何かを書く前に、すべてを読んでください。仕様は既存のテストやドキュメントからではなく、ソースから導き出します。
重要な原則: あなたが記述しているのは観察された振る舞いであり、理想的な振る舞いを規定するものではありません。コードが予期しない、直感に反する、あるいは誤っているように見えても意図的なことをしている場合、そのまま仕様化してください。癖には「internal」のフラグを立てるか、人間の判断を仰ぐために PR 本文で指摘してください。コードがどう動くべきかというあなたの考えに合わせて、無言で仕様を「修正」しないでください。仕様を書くプロセスによって潜在的な不整合を表面化させ、その後、実装を修正するか振る舞いを文書化するかを決定してください。
3. SPEC.md を書く
パッケージのルートに SPEC.md を書きます。安定した ID(例: S3、MG4)を持つ番号付きのルールをセクションにグループ化し、観察可能な振る舞いごとに 1 つのルールを記述し、内部の仕組みには internal のマークを付けます。既存の仕様のヘッダーの枠組み(「テストとこのドキュメントが矛盾する場合、どちらかが間違っている — どちらかを突き止めて修正せよ。」)を維持してください。
- コードがどうあるべきかではなく、コードが実際に何をするのかを仕様化してください。振る舞いが誤っているように見えても意図が曖昧な場合は、それを契約として文書化してください。
- 観察ではなく推測したルールを組み込む前に、使い捨てのテストで検証してください。検証後、スクラッチファイルは削除してください。
- すべてのルールはランタイムテストで観察可能でなければなりません。テストスイートが実際に型アサーションで検証していない限り、純粋に型レベルの振る舞い(ユーティリティ型、推論)に関するルールは書かないでください。それ以外の場合は、型を仕様から除外してください。
4. 仕様を敵対的に監査する
テストを書く前にドラフトを監査してください。失敗モードは過大な主張です。特に以下を探してください。
- 2 つのコードパス間で主張される対称性(作成 vs 更新、トップレベル vs ネスト、上 vs 下)— 各パスを独立に確認してください。
- 同じアイデアの 2 つの実装が一致するという主張(述語 vs インデックス、ゼロから vs 増分)— 両方にエッジケース、特に欠落/未定義の値を入力してください。
- 「X は決して起こらない」または「Y は常に Z である」という包括的な主張 — それを反証する分岐を見つけてください。
- ハッピーパスのテストでは気づかない入力への副作用(変更、凍結)。
5. テストスイートを再構築する
テストファイルは仕様のセクションを反映します。すべてのテスト名は it('[S3] ...') のように、そのルールを引用します。重複したカバレッジは統合し、既存の優れたテストは書き直すのではなく移植し、未カバーのルールにはテストを追加してください。
テストを通すためにアサーションを弱めてはいけません。 仕様のルールから書かれたテストが失敗した場合、何かを見つけたことになります。つまり、ルールが間違っているか(観測された動作に合わせて仕様を修正する)、コードが間違っているか(ステップ 7 に従ってバグとして扱う)のどちらかです。アサーションを緩めること(toBe → toContain、不一致を回避する形でのアサーション、不一致を認めるコメント)は、発見を覆い隠し、コードが持っていない動作を仕様が記述したままになります。これはまさに、仕様が防ぐために存在する失敗です。
テストファイルの構成: 仕様に「§3. Atoms (A)」や「§4. Computed (C)」のようなセクションがある場合、そのセクションのルールを含む atoms.test.ts と computed.test.ts という対応するファイルを作成してください。内部セクションについては、仕様化される仕組みに対して history-buffer.test.ts のような名前を使用します。これは、読者が SPEC.md ですでに目にする構造を反映しており、セクションごとのカバレッジを grep しやすくします。
このリポジトリのテスト環境に関する知識:
- テストでは、
globalThis.__FORCE_RAF_IN_TESTS__ = trueでない限り、ストアリスナーのフラッシュは同期的です。 - 非推奨だが契約上の API には、理由を添えたファイルレベルの eslint-disable が必要です。
6. 引用を双方向でクロスチェックする
grep -oE '\*\*[A-Z]+[0-9]+\*\*' SPEC.md | tr -d '*' | sort -u > /tmp/rules
grep -rhoE '\[[A-Z]+[0-9]+\]' src --include='*.test.ts' | tr -d '[]' | sort -u > /tmp/cited
comm -3 /tmp/rules /tmp/cited
すべてのルールには、それを引用するテスト、または PR 本文における明示的な「X のため意図的にテストしない」という注記(#9158 が行ったように)が必要です。すべての引用にはルールが必要です。完了前にギャップを修正し、クロスチェックの出力(ルール数、引用数、理由付きの残りのギャップ)を PR 本文に含めて、レビュアーが主張を信頼するのではなく検証できるようにしてください。
7. 途中で見つかったバグを修正する
仕様駆動のテストは、潜在的な不整合を浮かび上がらせます。つまり、コードパス間の矛盾、実装が想定していなかったエッジケース、あるいはサイレントな失敗です。見つけたら、判断してください。コードを修正するか、動作を文書化するか?
ワークフロー: まずテストを書きます(問題を捉えることを確認するために意図的に失敗させます)。次に判断します:
- コードを修正するのは、次の場合です: 意図が明確であり(コメント、対称的な実装、git blame)、現在の動作が明らかに間違っており、呼び出し側が壊れた動作に依存している可能性が低い場合。
- 動作を文書化するのは、次の場合です: 意図が不明確、修正の影響範囲が不明、または修正が既存の使用をサイレントに壊す可能性がある場合。PR 本文で「既知の不整合」または「再検討すべき動作」としてフラグを立ててください。
修正は、メインの test(<package>): コミットの後に、引用するテストを伴う独自の fix(<package>): コミットで行います。AGENTS.md に従い、いかなるコミットにも AI の帰属表示や共著者行を含めないでください。これはあらゆるデフォルトのハーネス動作より優先されます。修正をコミットする前に:
- 意図を確認する: コメント、対称的なコードパス、git blame。
- 影響範囲を確認する: リポジトリ内の呼び出し側を grep し、依存パッケージのスイート(
editor、tldraw、sync-coreのうち、このパッケージを利用するもの)を実行する。
仕様のルールが存在するのに実装がそれと矛盾する場合、実装が正しいと確信できない限り、仕様(実装ではなく)を更新してください。仕様は契約です。コードのドリフトは起こります。仕様を書くプロセスは、それを浮かび上がらせ解決する機会です。
8. 検証
順番に実行します: パッケージのテスト、pnpm format-current の後に pnpm lint、リポジトリルートからの pnpm typecheck、pnpm api-check、そしてステップ 7 の依存パッケージのスイート。
9. PR
skills/write-pr/ に従ってください。#9172 をモデルに本文を構成します:
- 仕様/引用システムを説明する導入段落。
- コミットごとの修正セクション。
- 統合されたスイートと新しいカバレッジに関するセクション。
- 動作変更についてのみリリースノート。
- コード変更の表。注意:
git diff --numstatのリネーム行は単純なパスマッチングを壊します。最後のフィールドを使用してください。
10. フォローアップノート
最後に、修正しないことを選んだ潜在的な問題、将来のテスト作成者が知っておくべき癖、レビュー担当者が再確認すべき事項を報告してください。
件数は、それが現れるすべての場所(コミットメッセージ、PR 本文、最終レポート)で正直に報告してください。再構築したスイートのテスト件数とルール件数は、grep で測定したものであり、パッケージの合計決してではありません。手を付けていないスイート(特に fuzz)を自分の作業として数えると、変更を誤って伝えることになります。