writing-unit-tests について
単体テストを作成・修正するエンジニア向けのスキルです。実装ではなく振る舞いの契約を検証し、決定性、明確なテスト名、失敗経路の適切なアサーションを重視します。
主な機能
- 01実装ではなく公開動作をテスト
- 02テストごとに一つの動作を命名
- 03スリープなしの決定的なテスト
- 04外部境界を選択的にモック
- 05結果と失敗経路をアサート
活用シーン
- 01変更したユニットのテスト作成
- 02内部実装に結合したテストの置き換え
- 03非同期テストの不安定さの調査
- 04エラー動作のテスト追加
インストール
コマンドをコピーしてターミナルで実行します。
$
npx skills add https:/ / github.com/ riekelt/ principal-engineer --skill writing-unit-testsインストール後、Claude Code で/writing-unit-testsを入力して読み込まれたか確認します。
詳細
ユニットテストの作成
必須の背景知識: principal-engineering スキル。どのテストが変更に必要かは testing-changes が規定します。このスキルでは、テストそのものの作成技術を扱います。
概要
ユニットテストとは、名前が付いた振る舞いに関する主張であり、ビルドが失敗したときに次のエンジニアが読むものです。基本原則は、実装ではなく、契約をテストすることです。 テスト名が主張を表し、テスト自体が間違いようのないほどシンプルに保ちます。
実装よりも契約を優先する
- ユニットの公開された契約を通してテストします。振る舞いを維持するリファクタリングでテストが壊れるなら、そのテストは実装を検証していたということです。
- 呼び出し順序、内部状態、メソッド A がメソッド B を呼び出したことは、やり取りそのものが契約(境界における必須の副作用)でない限り、検証しないでください。
- 本番コードの算術処理を再実装したり、値を計算する共有ヘルパーを呼び出したりして、期待値を導き出してはいけません。期待値は、独立して手計算したもの、仕様から求めたもの、または実データに基づくリテラルとし、その導出方法をコメントに記載します。
テストごとに振る舞いは一つ、名前は主張を表す
- テストごとに振る舞いは一つにします。複数のアサーションが失敗したときに原因を掘り下げるより、テストを分けるほうが簡単です。
- 名前には対象、状況、期待する結果を記述します。
test_auth_3ではなく、expired_token_is_rejected_with_401のようにします。テスト名では振る舞い、状態遷移、不変条件を記述し、実装順序、チケットキー、フェーズは記述しません。 - 準備、実行、検証を、目に見える形でこの順序に並べます。テスト内での分岐、ループ、ロジックは禁止です。ロジックがあるテストには、それ専用のテストが必要です。共通のセットアップにはビルダーか、役割を名前にしたフィクスチャを用意します。正体不明の巨大なフィクスチャは、アサーションがどの準備済み事実に依存するのかを隠してしまいます。生成や反復処理はテスト本体ではなく、ビルダーやヘルパーに記述します。不変条件にはプロパティベーステストが適切な方法であり、各フレームワークの形式に従います。具体例ベースのテストではロジックを使いません。
決定性
- ユニットテストで実際の時刻、乱数、ネットワーク、ファイルシステムを使わないでください。クロックを注入し、乱数にはシードを設定するか注入し、境界をモックします。
- スリープは使いません。非同期処理の待機には、時間に基づくのではなく、条件に基づく方法を使います。期限を設けて、観測可能な結果をポーリングします。
- 不安定なテストは失敗です。修正するか、担当者を明示して、目に見える形で隔離します(
testing-changesの red-test ルールを参照)。緑になるまで再実行するのは、検知器を無効にする行為です。
モックは前提を表す
- 自分たちが所有していない境界(ネットワーク、クロック、ファイルシステム、サードパーティーサービス)をモックし、ユニットの範囲内で自分たちが所有するコードには、実際の協調オブジェクトを使うようにします。所有していないリソースを扱う自作のラッパー(データベースの前段に置く自作のリポジトリークラスなど)では、自作コードが所有していないリソースに最後に触れる境界でモックし、どちらの場合もテストデータの役割を名前で示し、見えるようにします。モックはすべて、契約に関する前提をハードコードします。古くなったモックがあると、実際の連携が壊れていてもテストスイートが緑のままになります。
- テストの大半がモックの配線になっているなら、そのテストが検証しているのはモックです。ユニットの範囲を広げて実際の振る舞いを持たせるか、この境界には代わりに統合テストが必要だと判断し、どちらなのかを明記します。
- フィクスチャは現実を切り取ったスナップショットにラベルを付けたものです。最小限にし、シナリオ内での役割が名前から分かるようにし、契約が変わったら意図を持って更新します。テストを緑にするために、むやみに再生成してはいけません。
アサーションと失敗経路
- 例外が発生しなかったことではなく、値を使って結果を検証します。「例外をスローしなかった」という主張から分かることは、ほとんどありません。
- 失敗経路も重要なテスト対象です。型付きの失敗が表面化すること、縮退モードへの移行が明確であること、ガードが実際に機能することを検証します(
handling-failuresを参照)。 - テスト自体も、「失敗を黙って握りつぶさない」という契約に従います。テストコードで例外を捕捉して無視したり、前提条件がないときに黙ってスキップする条件付きアサーションを使ったりしてはいけません。実行できないテストは失敗させるか、理由を明示して目に見える形でスキップします。
よくある間違い
- ミラーテスト:本番ロジックを再実装して期待値を計算します。
- モックのエコーチェンバー:自分のクラスをモックし、そのモックを検証します。
- 巨大テスト:12 個のアサーションを 1 つの名前にまとめ、どの主張が失敗したのか分かりません。
- テストの実行順に左右される、変更可能なフィクスチャの共有。各テストがそれぞれ独自の状態を構築するか、受け取るようにします。
- 不安定さを起きにくくするだけの「修正」としての sleep。
- フィクスチャのグリーンチェック:新しい値が正しい理由を導き出さずに、実際の出力に合わせて期待値を編集します。
コメント
Markdown 対応