principle-test-behavior-not-implementation について
Test Behavior, Not Implementation は、内部構造ではなく観察可能な契約を検証するためのテスト原則です。動作の破損を検出できるか、内部の書き換え後も通るかという二つの観点で、テストの維持や修正を判断します。
主な機能
- 01観察可能な動作に焦点を当てるテスト
- 02動作の破損を検出できるか確認
- 03内部リファクタリングへの耐性を確認
- 04要件から期待値を導出
- 05インターフェース上の不変条件を対象化
活用シーン
- 01テストが利用者向けの契約を証明するか確認
- 02非公開ヘルパーに依存するテストを修正
- 03アサーションの緩さや過剰な限定を点検
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ backnotprop/ pstack --skill principle-test-behavior-not-implementationインストール後、Claude Code で/principle-test-behavior-not-implementationを入力して読み込まれたか確認します。
詳細
実装ではなく振る舞いをテストする
テストとは、コードが利用者(呼び出し元、エンドユーザー、別のサービス)に対して何をするかについての主張です。振る舞いとは、利用者が使用するインターフェースを通じて観察できることです。これには、戻り値、エラー、読み戻せる状態、コードの外部にあるシステムへの作用(リクエストの送信、行の書き込み、メッセージの発行など)が含まれます。実装とは、利用者が観察できないものです。プライベートなヘルパー、内部の呼び出し順序、中間データ、どの内部の協調コンポーネントが処理を担当したか、などが該当します。
次の2つの変更を想定し、それぞれのテストを評価してください。
- 振る舞いを壊す。 コードが誤った値を返す、作用を省略する、または作用を二重に実行するように変更します。それでもテストが通るなら、そのテストは何も証明していません。よくある原因は、契約より緩いアサーション、テスト対象のコードが算出した期待値、値を読み取るコードを実行せずに定数やプロンプト文字列を書き直しただけのテスト、そしてテスト自身が作成したデータに対するアサーションです。
- 内部を変更する。 観察可能な結果はすべて同じまま、コードの内部構造を変更します。そのときテストが失敗するなら、テストは実装に過度に依存しています。リファクタリングのたびに書き直す必要がある一方で、バグは検出できません。
最初の変更を検出し、2つ目の変更には耐えるテストは残してください。それ以外は書き直すか削除してください。両方を満たすテストを書くには、具体的な入力を用いて、利用者が使うインターフェースからコードを動かします。期待値はコードの実行結果ではなく、要件から導き出してください。契約が約束する内容を、過不足なくアサートしてください。
判断基準はアサーションの形ではなく、契約です。「空のカートには決して課金しない」という約束がある場合、決済クライアントが呼び出されなかったというアサーションは振る舞いを検証しています。テストで呼び出しを検出できることを示すため、課金が発生するケースも組み合わせてください。モックした外部サービスが受け取るペイロードは、そのサービスが観察するものなので振る舞いです。内部ヘルパーの呼び出しは、利用者には見えないため実装です。該当するレコードがない場合、空のリストが厳密な答えになることもあれば、テストで何らかの結果さえ得られればよいときには、形だけのチェックにもなります。呼び出し元が参照しない正確なエラーメッセージをアサートすると、文言に過度に依存します。呼び出し元が分岐に使うエラーの型こそが契約です。
呼び出しではなく、2つのテーブルに存在しなければならないキーや、コンパイル時の型チェックなどの不変条件をテストで固定することもできます。その不変条件が振る舞いです。