テスト駆動開発 (TDD)
TDDとは red → green のループである。このスキルは、そのループから「残す価値のあるテスト」を生み出すためのリファレンスであり、良いテストとは何か、テストの置き場所、アンチパターン、ループのルールをまとめたものである。すべてのセクションはループの毎サイクルに適用される。ループの前後ではなく、ループの最中に参照すること。
コードベースを探索する際は、CONTEXT.md が存在すればそれを読み、テスト名やインターフェースの語彙がプロジェクトのドメイン言語と一致するようにする。また、これから触る領域のADR(Architecture Decision Record)を尊重する。
良いテストとは何か
テストは実装の詳細ではなく、公開インターフェースを通じて振る舞いを検証するものである。コードは完全に書き換わることがあっても、テストは書き換わるべきではない。良いテストは仕様書のように読める。「有効なカートでチェックアウトできる」というテストは、どんな機能があるのかを正確に伝え、内部構造を気にしないためリファクタを乗り越えて生き残る。
具体例は tests.md を、モックの指針は mocking.md を参照。
シーム: テストの置き場所
シーム(seam) とは、内部に踏み込まずに振る舞いを観察できる、テスト対象となる公開境界のことである。テストはシームに存在するものであり、内部実装に対して書くものではない。
事前に合意したシームでのみテストする。 テストを書く前に、テスト対象のシームを書き出し、ユーザーと確認する。未確認のシームに対してテストは書かない。すべてを網羅的にテストすることはできないため、事前にシームを合意することで、テストの労力があらゆるエッジケースにではなく、重要な経路や複雑なロジックに向かうようにする。
「公開インターフェースは何か、どのシームをテストすべきか?」を問う。
そのインターフェースの形そのものが疑問の対象である場合(モジュールの深さ、シームの置き場所、インターフェースが何を公開すべきか)、Skillツールで "codebase-design" を呼び出し、語彙を借りる。それはモジュール、インターフェース、深さ、シーム、アダプター、レバレッジ、局所性という用語の共通の出典であり、セッションとして実行するものではなく、参照すべきリファレンスである。
アンチパターン
- 実装結合型: 内部の協力オブジェクトをモックしたり、プライベートメソッドをテストしたり、サイドチャネル(インターフェースを使わずデータベースに直接問い合わせるなど)で検証したりする。見分け方: 振る舞いが変わっていないのにリファクタするとテストが壊れる。
- 同語反復型: アサーションが、コードと同じやり方で期待値を再計算してしまっている(
expect(add(a, b)).toBe(a + b)のような、コードと同じ手順で手計算したスナップショットや、定数を自分自身と比較するアサーションなど)。これは構造上必ず成功してしまい、コードと食い違うことが原理的にありえない。期待値は必ず独立した真実の情報源(既知の正しいリテラル値、具体例、仕様書)から得ること。 - 水平スライス: すべてのテストを先に書いてから、すべての実装を書くやり方。まとめて書かれたテストは想像上の振る舞いを検証してしまい、ユーザーに見える振る舞いではなく物事の形をテストすることになり、実際の変更に対して鈍感になり、実装を理解する前にテスト構造にコミットしてしまう。代わりに垂直スライスで作業する: 1つのテスト → 1つの実装 → 繰り返し。各テストは、前のサイクルで学んだことに応じて撃つトレーサーバレットである。
ループのルール
- Green より先に Red。 まず失敗するテストを書き、それを通すために必要最小限のコードだけを書く。将来のテストを先取りしたり、投機的な機能を追加したりしない。
- 一度に1スライス。 1サイクルにつき、1つのシーム、1つのテスト、1つの最小限の実装。
- リファクタリングはこのループの一部ではない。 それは red → green の実装サイクルではなく、レビュー段階(
code-reviewスキルを参照)に属する。