mcpField
S
spec-driven-tests

spec-driven-tests

github.com/tldrawテスト・品質51.1k65 回インストール今日更新
リポジトリ

spec-driven-tests について

spec-driven-testsは、パッケージの実装から観測可能な動作を抽出し、それに基づいてユニットテストを再構築するコーディングスキルです。安定したルールIDと双方向のテスト参照で、カバレッジを追跡できます。

主な機能

  1. 01実装から観測可能なルールを抽出
  2. 02安定したルールIDでSPEC.mdを作成
  3. 03仕様のセクションごとにテストを再構築
  4. 04エッジケースと参照を検証
  5. 05見つかった問題を記録して対応

活用シーン

  1. 01重複するパッケージテストを統合
  2. 02各動作ルールに対応するテストを追跡
  3. 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)を自分の作業として数えると、変更を誤って伝えることになります。

コメント