test-lens について
test-lensは、テストの保護効果が保守コストに見合うかを評価するスキルです。開発者がテスト形式を選び、既存テストを評価し、4つの評価軸に基づいて具体的な改善を示します。
主な機能
- 014つの品質軸でテストを評価
- 02内部実装より観測可能な振る舞いを重視
- 03テスト形式とモック境界を案内
- 04アンチパターンとリファクタリング箇所を特定
- 05ファイルと行番号付きで指摘を報告
活用シーン
- 01テストの脆さと価値をレビュー
- 02新しいコードの単体・結合テストを選択
- 03状態管理や非同期変更のテストを改善
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ robsonrung/ rar-skills --skill test-lensインストール後、Claude Code で/test-lensを入力して読み込まれたか確認します。
同梱ファイルreferences/decision-trees.md
詳細
Test Lens — 価値あるユニットテスト
モデルを選ぶ前に、ブランチを選択してください。コーディネーターとしての作業と決定的なコマンドは、呼び出し権限の範囲内で進めます。現在の役割でこの手法を適用してください。ワーカーをディスパッチすることはありません。ワーカーをディスパッチする場合は、shared/references/model-preview.md と、そこで承認されている唯一のルートスナップショットを使用してください。ネストされたスキルやランナーはそれを利用し、改めて選択したりワーカーを起動したりしません。
Unit Testing: Principles, Practices, and Patterns(Vladimir Khorikov)のフレームワークを使って、テストを評価または作成してください。これはレッド・グリーンのサイクルではありません(進め方には tdd を適用してください)。また、バグ探しでもありません(それはコードレビューの工程で行います)。答えるのは、次の1つの問いです。このテストは、保守コストに見合う価値があり、適切な対象を適切な方法でテストしていますか?
テストスイートの目標はプロジェクトの持続可能な成長です。機能を追加したりリファクタリングしたりするときの不安をなくします。価値の低いテストを大量に作るより、価値の高いテストを少数作るほうが優れています。テストコードも負債です。 高い基準を設け、基準に満たないテストは削除してください。
以下の例では TypeScript + Jest を使用します。この観点で評価したら、具体的に指摘事項を報告してください。file:line を示し、ルールを挙げ、修正案を提示します。すでに良いテストであれば、率直にそう伝えてください。
中核となる評価基準:4つの柱
すべてのテストは、4つの属性それぞれについて0〜1で評価されます。価値 = 4つの属性を掛け合わせた値です。いずれかの柱が0なら、ほかの3つが完璧でも、そのテストに価値はありません。
- 回帰に対する保護 — テストが実行するコード(自分のコードとライブラリのコード)の量を、複雑さとドメイン上の重要性に応じて評価します。単純なコードでは保護効果は低くなります。
- リファクタリングへの耐性 — 誤検知(動作に問題がないのに失敗すること)がどれだけ少ないかで決まります。実装詳細との結合度だけが影響します。妥協できない要件です。
- フィードバックの速さ — 実行速度です。
- 保守性 — 読みやすさと実行のしやすさ(セットアップ、プロセス外の依存関係)です。
重要なトレードオフ: 最初の3つは互いに両立しません。3つすべてを最大化することはできません。リファクタリングへの耐性は高く維持する必要があるため、保護効果とフィードバックの速さの間で調整します。よくある3つの失敗パターンを以下に示します。それぞれ、2つの柱を最大化する代わりに、残る1つをゼロにしています。
| テストの種類 | 犠牲にするもの | 価値が低い理由 |
|---|---|---|
| エンドツーエンドのみ | フィードバックの速さ | カバレッジが高く堅牢ですが、スイート全体をこれだけで構成するには遅すぎます |
単純なテスト(プレーンなゲッターに対する expect(user.name).toBe('John')) | 保護効果 | 検出できるものがありません |
| 壊れやすいテスト(SQL文字列、呼び出し順序、非公開の構造をアサートする) | リファクタリングへの耐性 | リファクタリングのたびに壊れ、無視されるようになり、実際のバグがすり抜けます |
壊れやすいテストは危険です。失敗を無視するようチームに学習させ、その結果、ノイズに紛れて実際の回帰が本番環境に入り込みます。
観点1 — 実装詳細ではなく、観察可能な動作
最も重要なルールです。 偽陽性は、コードが_どのように_動作するかにテストを結び付け、_何を_生み出すかを確認しないことで発生します。ドメインの専門家やエンドユーザーにとって意味のある最終結果を検証し、そこに至る手順は無視してください。
危険信号(いずれも実装に結び付いています):
- 内部メソッドが呼び出されたか、その順序や回数(スタブに対するもの)をアサートする。
- 生成されたSQL文字列、内部の協調オブジェクトの一覧や型、非公開の状態をアサートする。
- 本番コードのアルゴリズムを一手ずつ再現するテスト(「ドメイン知識の漏洩」)。
- 非公開メソッドの名前を変更したり、内部構造を組み替えたりするたびに、編集が必要なテスト。
良いテストは、問題領域についてのストーリーを語ります。失敗したなら、そのストーリーとコードが食い違っています。それこそが、注意を向ける価値のある唯一の失敗です。
観点 2 — 適切なスタイルを選ぶ(推奨順位)
| スタイル | 検証するもの | 品質 | 優先する場面 |
|---|---|---|---|
| 出力ベース | 純粋関数の戻り値 | 最良 — リファクタリングによる影響を自然に受けにくい | コードが許すなら常に |
| 状態ベース | 処理後のオブジェクト/システムの状態 | 問題ありません — 状態の漏えいにつながる可能性があります | ミューテーションが実際の結果である場合 |
| 通信ベース | コラボレーターの呼び出し(モック) | 最も脆弱 | システムの境界で、管理対象外の依存関係に限る |
判断とアクションを分離して、コードを出力ベースに寄せましょう。つまり、出力ベースのテストが容易な関数型コア(純粋なビジネスロジック)を、I/O のみを担う薄い可変シェルで包みます。これは、極限まで推し進めたヘキサゴナルアーキテクチャです。references/decision-trees.md を参照してください。
観点 3 — モック:スタブとモック、管理対象と管理対象外
エージェントが最も間違えやすいルールです。フローチャートの全体は references/decision-trees.md にあります。
- スタブ = 入力 側のインタラクション(SUT が読み取る入力データ)をエミュレートします。スタブへの呼び出しをアサートしてはいけません — これは過剰な仕様化であり、脆弱なテストの最大の原因です。
- モック = 出力 側のインタラクション(SUT が引き起こす副作用)をエミュレートします。その副作用自体が観測可能な結果である場合に_限り_、アサートしてかまいません。
- 管理対象の依存関係(完全に制御でき、アプリだけが使用するもの。例:自分の DB):インタラクションは実装の詳細です → 統合テストでは実物を使います。
- 管理対象外の依存関係(外部から観測可能なもの。SMTP、メッセージバス、サードパーティ API など):インタラクションは観測可能な振る舞い/契約です → モックします。
- モックはシステムの境界でのみ、かつ自分が所有する型のみを対象にします。サードパーティ SDK は独自のアダプターでラップし、ライブラリではなくアダプターをモックします。
観点 4 — そもそも何をテストするか(コードの象限)
テスト対象のコードを 2 つの軸、すなわち複雑さ/ドメイン上の重要性 × コラボレーターの数で分類します。
| コラボレーターが少ない | コラボレーターが多い | |
|---|---|---|
| 複雑さ/重要性が高い | ドメインモデル/アルゴリズム → 積極的にユニットテストする(ROI が最も高い) | 過度に複雑 → リファクタリングし、以下の 2 つに分割する |
| 低い | 些細 → テストしない | コントローラー → 少数の統合テストを行う |
コードが重要であるほど、また複雑であるほど、コラボレーターの数は少なくあるべきです。
過度に複雑な象限(たとえば、実際のロジックと I/O の両方を担う肥大化したコントローラー)は落とし穴です。Humble Object パターンを使って分割します。ロジックを純粋なアルゴリズム/ドメインクラスに抽出して出力ベースのユニットテストを行い、依存関係をつなぐだけの薄い humble なラッパーを残して、統合テストで軽くカバーします。100% のカバレッジが目標ではありません — カバレッジが高くても価値のないテストはあり得ます(その逆も同様です)。重要なのは、テスト 1 件あたりの価値です。悪いテストをするくらいなら、テストがないほうがましです。
アンチパターンのチェックリスト(該当するものに注意)
- プライベートメソッドのテスト → 複雑な処理を行うプライベートメソッドは、抽象化が不足している状態です。クラスに抽出します。
- プライベートな状態をアサートするためだけに公開する → 代わりに観測可能な振る舞いをテストします。
- ドメイン知識の漏えい → テスト内に、アルゴリズムの中間計算の期待値をハードコードします。
- コードの汚染 → 本番コードにテスト専用のスイッチ/フラグを追加します。
- 振る舞いの一部を残すために具象クラスをモックする → SRP 違反です。クラスを分割します。
- 暗黙的なコンテキストとしての時刻(
Date.now()を直接呼び出す) → 時刻を明示的な依存関係として注入します。 - スタブとのインタラクションをアサートすること、または目標としてカバレッジ数値を追い求めること。
メカニクス(テストを書く際)
- AAA — Arrange/Act/Assertをそれぞれ1つずつにします。Actが複数必要なら、おそらくインテグレーションテストです。テストに
if文を使わないでください(テストを分割します)。1つの振る舞いに対するActは1行に収めます。 - テストには、振る舞いを示すドメイン上の記述で名前を付けます。
IsValid_PastDate_ReturnsFalseではなく、delivery_with_a_past_date_is_invalidのようにします。タイトルにメソッド名を使わないでください。プログラマーでない人にも読める名前にします。 - 類似するケースはパラメーター化します(
it.each)。ただし、異なる振る舞いは別々のテストにします。 - 共有可変状態を
beforeEachで使うのではなく、ファクトリー関数でフィクスチャを再利用します(テスト間の結合が強くなるのはアンチパターンです)。
ワークフロー
- このテストは誰のものですか? 新しいコードなら、書き始める前にスタイルの選択をガイドします(Lens 2/4)。レビュー対象の既存テストなら、採点します。
- 4つの柱を採点します。 最も弱いものを明示します。いずれかが≈0なら、書き直しまたは削除を推奨します。
- 該当するレンズを適用します。 観測可能な振る舞い(常に)、モックのルール(テストダブルがある場合)、四象限(何をテストするか、またはテストするかどうかを判断する場合)。
- アンチパターンのチェックリストを実行します。
- 各指摘を具体的に報告します。
file:line→ 該当するルール → 修正方法。該当しないレンズは、内容を水増しせず省略します。
スタイルの移行、モックのフローチャート、Humble Objectのリファクタリング、インテグレーションテストのルール(パスの選択、ロギング、ピラミッド)、およびコードの変更前後を含むアンチパターンの全カタログについては、詳しい判断ツリーがreferences/decision-trees.mdにあります。
失敗シーケンスとフィクスチャの境界
状態を持つ変更や非同期の変更については、最初のレビューの前に、実際のステートマシンから小さなケース表を作成します。各ハンドラーを一度ずつ実行するだけでなく、意味のあるイベント順序と維持される不変条件を含めます。同じ因果経路がある場合は、関連するスライスにも以前の不具合ケースを引き継ぎます。エディターのキャンセル、置き換え、スコープの変更、または遅れて届く応答が関係する場合は、advanced-reactの非同期処理リファレンスを参照します。
テストを通過したフィクスチャが証明するのは、それが対象とする境界だけです。本番アダプターは、スキーマ上許容される場合、値が存在しないケース、レガシー形式のケース、不正な形式のケースを含む、代表的な保存値で確認します。ロックやロールバックに関する主張には、実際のトランザクションを使用します。フィクスチャによって、本番環境より厳格なスキーマや整ったデータ契約を作り出さないでください。不正なデータに対するデフォルト値やフォールバックは、プロダクトの契約に従う必要があります。
受け入れレポートでは、モデルのテスト、DOMの操作テスト、部分的なアプリケーションのブラウザー確認、アプリケーション全体の確認を分けて報告します。DOMテストやスクリーンショットでは、デプロイ済み環境の認可を確認できません。テスト数だけでは、網羅性を証明できません。