write-tests について
write-testsは、観測可能な動作を中心に回帰テストを作成するためのテストスキルです。テストを一つずつ追加する手順や、システムレベルの検証、テスト境界の制御、意図した不具合を検出できるかの確認を重視します。
主な機能
- 01テストを一つずつ作成して検証
- 02観測可能なシステム動作を優先
- 03変数を制御しシステム境界をモック
- 04実機で動かすテストを隔離
- 05完了前に回帰テストの失敗を確認
- 06壊れやすい前提を含むアサーションを確認
活用シーン
- 01動作変更に対する回帰テストの追加
- 02複数コンポーネントを通る公開経路のテスト
- 03不安定または設定依存のテストの調査
- 04回帰テストが実際の不具合を検出するか確認
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ dzhng/ skills --skill write-testsインストール後、Claude Code で/write-testsを入力して読み込まれたか確認します。
詳細
テストを書く
優れたテストは、実際の動作が壊れたときだけ失敗し、その動作を保つリファクタリングや設定変更では合格します。質の悪いテストの多くはその逆です。無害な変更で失敗し、実際の経路が壊れていても合格します。以下のルールはすべて、この目標のためにあります。
回帰をカバーするテストは、エンドツーエンドテストまたはシステムレベルのテストを基本としてください。ユニットテストは、複雑で固有のビジネスロジックに対して、迅速なフィードバックによって開発が大幅に速くなる場合にのみ使ってください。
テストカバレッジが独立した証明になるか、どこに置くべきか、既存のテストのどれを削除できるかを判断するには、audit-testsを使ってください。
ワークフロー:一括ではなく、トレーサー・バレット方式で
- テストは一度に1つだけ書いてください。 まずアサーションを書き、修正前のコードで失敗することを確認してから、コードを合格させ、学びを得て、次のテストを書きます。最初にまとめて書いてはいけません。想像上の動作を前提に一括で書いたテストは、推測を固定してしまいます。仕組みが壊れても合格し、問題がなくても失敗するテストになります。合格までのサイクルを経るたびに、次のテストで実際に何を検証すべきかが分かります。
- 最速で対象を絞れるテストランナー(1ファイル、1つのテスト名)を繰り返し使い、全テストの実行は引き渡し前の最終確認としてのみ行ってください。出力だけでなく、終了コードも確認してください。何も出力しないランナーと、合格した実行結果は見分けがつきません。失敗があったら、1つを修正する前に、失敗したテストをすべて読んでください。多くの場合、共通の根本原因があります。
- 完了とする前に、テストが失敗し得ることを証明し(以下を参照)、レビューチェックリストを確認してください。
検証すること
- 実用上可能な最外部のエントリーポイントを通じた、観測可能な動作――戻り値、終了コード、永続化された行、HTTP レスポンス、レンダリング結果などです。どの内部関数が実行されたか、値がどのように計算されたかは検証しないでください。公開されたインターフェースを対象にするテストなら、内部をすべて書き換えても機能します。内部に踏み込むテストは、リファクタリングのたびに壊れ、動作ではなく実装を固定してしまいます。ロジックが十分に固有かつ複雑で、迅速なフィードバックによって開発が大幅に速くなる場合に限り、分離されたユニットテストを使ってください。
- コンパイラーがすでに保証することは検証しないでください。 フィールドの形状や拒否される引数の型など、型シグネチャーを再確認するテストは、コンパイラーが先に失敗しない限り失敗しません。テストの予算は、ビジネスルール、算術、分岐、順序、エッジケース、副作用に使ってください。
- コレクションのサイズではなく、実際の値を検証してください。 重複排除、正規化、冪等性の経路では、正規化が壊れていても
length == 1は合格します。保存された値が期待する正規形と一致することも検証してください。 - システムシナリオは小さく保ってください。 無関係な主張を絡めずにエンドツーエンドの動作を証明するため、必要最小限のデータと仕組みを使ってください。
- 現在の動作ではなく、設計上の契約を検証してください。 テストが失敗したとき、再計測して新しい数値に固定しようとしがちですが、それは避けてください。コードが現在出す値に合わせて調整したしきい値は、バグを暗黙の基準として埋め込んでしまいます。明記された契約に基づいてアサーションを書き、コードがそれを満たすようにしてください。契約が存在しないなら、それはオーナーに確認すべきことであり、計測して固定する数値ではありません。契約自体が変わった場合に限り、再調整は正当です。
- 決定論的な主張は厳密に、確率的な主張は分布として検証してください。 毎回成立する不変条件には、正確なしきい値を設定します。ノイズが多いものやチューニングに依存するもの(「通常はAがBに勝る」「負荷が均等に分散される」など)は、複数の実行回数やシードにわたって範囲で検証してください。確率的な結果を単一サンプルの値に固定するのは、単に弱いテストではなく、目の見えないテストです。たまたま得られた結果を認定し、何か月も系統的な偏りを見逃すおそれがあります。ノイズの多い差分を検証せざるを得ない場合は、許容幅を広げ、コメントに
chaos-marginalと明記してください。
シームとモック
- テストを設定に依存させず、シームをモックします。 実際の設定値(どのフラグが有効か、どのティアがデフォルトか)を基準にしたテストは、誰かが正当に設定を変更すると壊れます。解決策は決して
skipIfや条件分岐ではありません。アサーションをスキップすると依存関係が隠れ、その経路のテストも行われなくなります。関数に固定の入力を渡すか、検索処理をスタブして、フィクスチャの ID が固定値に解決されるようにします。判断基準は、「ロジックを変えずに設定値だけが変わったら、このテストは壊れるか」です。 - テストが依存する境界値は、テスト自身で設定します。 チューニングデータ(ユニットのステータス、範囲、価格、タイミング)は必ず変更されます。常に調整されているため、そのデータを読み込むテストや今日の値をハードコードしたテストは、調整のたびに壊れます。そこで、境界を検証するテスト(範囲内か範囲外か、表示されるか隠されるか、収まるかはみ出すか)では、テスト自身が数値を定義したフェイクを使い、その値の両側を調べます。それ以外のテストは、振る舞いを示せる最小限の設定で、境界値から十分離して実行します。こうすれば調整後も壊れず、実行も速くなります。
- モックしすぎないでください。モックはすべて、固定された前提です。 システムの境界(ネットワーク、時計、ファイルシステム、サードパーティ SDK、設定の検索処理)をモックし、内部の連携先は決してモックしません。内部処理をスタブすると、現在の契約がハードコードされます。その処理をリファクタリングすると、テストは誤った結果を示します。観測可能な結果がある場合、「この引数で呼び出された」を主要なアサーションにしないでください。1つの関数をテストするために内部モジュールを3つモックするなら、それらを実際に使う1つ上のレイヤーをテストします。
- ハーネスは、本番と同じ方法でシステムを接続する必要があります。 バグが実環境でしか現れなかったなら、ハーネスが本番では必ず設定される入力を省いています。バグだけを直すのではなく、ハーネスを修正してください。
制御変数とプローブ
- 比較ごとに変数は1つだけにします。 それ以外はすべて固定します。両方の条件でシード、件数、設定を同じにし、対象ではないメカニズムは無効にします。比較テストが壊れたら、テスト対象のコードに手を加える前に、無関係なメカニズムが実験に紛れ込んでいないかを確認してください。
- 推測せず、検証してください。 どの経路が実行されたのか、失敗の原因はどこにあるのかといった、原因についての結論を推論だけで出し、それに基づいて対応してはいけません。公開状態を読み取って出力する、使い捨てのプローブを書いてください。もっともらしい説明が間違っていて時間を浪費することはよくあり、プローブを1つ実行すれば作業全体の方向を変えられます。プローブは一時的なものです。コミットされない場所に置き、疑問が解消したらすぐ削除してください。振る舞いを継続的に守る価値があるなら、正式なテストに組み込みます。
誰かの実機で実行されるテスト
デスクトップを占有するテストスイートは、やがて誰も実行しなくなります。テストに必要なものは何であれ、最も邪魔にならない形で用意します。
- フォーカスを奪わないでください。 アプリのアクティブ化、
makeKeyAndOrderFrontの呼び出し、ウィンドウの前面表示、ポインターの移動、セッションへのクリックのシミュレーション、音声の再生はしません。ユーザーの操作を妨げてはいけない UI も、十分テストできます。ウィンドウを構築し、そのモデルを操作して、描画されたビューをアサートします。スクリーンショットが証拠となる場合は、画面を撮影せず、ウィンドウ自身の画像をオフスクリーンで取得します。アクティブ化はユーザーが実行するコードパスに限定し、アプリを何度も起動するのではなく、ハンドラーを呼び出してテストします。 - ユーザー自身の状態が保存されている場所には書き込まないでください。 すべてのテストで、一時的なホームディレクトリ、一時的な環境設定ドメインまたはスイート、一時的なインストール先を使用します。テストで実際の設定、ライブラリ、ログイン項目、インストール済みアプリケーションを変更してはいけません。本番コードではテストが設定したオーバーライドを読み取って構いませんが、オーバーライドがない場合は通常どおり動作する必要があります。
- 避けられない可視操作はまとめて実行してください。 実際の起動が本当に必要な確認では、アサーションごとに再起動するのではなく、1回の実行ですべての観察を行い、可視状態で実行したことを証跡に記載します。
- 実行中のものを残さないでください。 テストが起動したすべてのプロセス、ウィンドウ、サーバー、一時ファイルは、失敗時も含めて停止・削除します。
テストが失敗することを証明する
失敗するのを一度も見たことがないテストは飾りです。回帰テスト、特に修正後に書いたテストは、一度反証してください。バグが起きるように本番コードを元に戻すか壊し、想定した理由で赤になることを確認してから、コードを復元し、緑になることを確認します。元に戻す操作が実際に機能したことも確認してください。間違った pathspec を使った stash や checkout では何も元に戻らず、そのことに気づかないまま「赤」の実行で修正済みコードをテストしてしまいます。見分けるポイントは、「赤」の数値が緑の数値と同じことです。
変更後にテストが赤になったとき
何かを編集する前に、可能性の高い順に原因を切り分けてください。
- テストが削除されたセマンティクスを固定している — 古い実装で偶然そうなっていたことを前提にしています。仕様を見直し、契約が実際に主張する内容に合わせてください。
- 実験で変数を制御できなくなった — 新しい仕組みが漏れ込んでいます。その変数を固定してください。
- マージンがカオスに近いほど小さかった。余裕を広げ、その理由をコメントに書いてください。
- コードが実際に間違っている。状態を調べ、仕組みを特定してください。
隣接するテストを再実行せずに、1つのテストを通すためだけに定数を調整してはいけません。連動するシステムでは、調整によって結果が入れ替わります。連続する2回の変更で、それぞれ別のテストが壊れたら、手探りの調整はやめてください。制御面が間違っています。仕組みを特定してください。
レビューチェックリスト
テストに関する差分には必ずこのチェックリストを適用し、同じ作業で修正してから、テストスイートを再実行してください。
- アサーションが型の保証を言い換えているだけですか? → 削除してください。
- ロジックを変えずに設定を編集しただけで失敗しますか? または設定に応じて
skipIfされていますか? → 境界をモックしてください。 - アサーションの大半が、モックされた内部処理の「この引数で呼び出された」という確認ですか? → 1つ上のレイヤーで、観測可能な出力をテストしてください。
- 確率的な結果のサンプルを1つだけ固定していますか? → 分布をアサートしてください。
- 重複排除/正規化の処理で、値を確認せず件数だけをアサートしていますか? → 値のアサーションも追加してください。
- テスト対象の関数は本番環境でも呼び出されていますか? → 呼び出し元がテストだけなら、その関数とテストを一緒に削除してください。
- 正しい理由で失敗する可能性がありますか? → 一度反証して確認してください。
- フォーカスを奪う、ポインターを動かす、音を鳴らす、またはユーザー本人の設定、ライブラリ、アプリケーションに書き込みますか? → 代わりにモデルとスクラッチ領域を使って操作し、ウィンドウを画面外でキャプチャしてください。