maintainable-tests について
maintainable-tests は、自動テストの作成、リファクタリング、レビューに取り組む開発者向けのスキルです。振る舞いを示す具体例、明確なテスト意図、決定性、保守しやすいテストダブルを重視します。
主な機能
- 01振る舞いに基づくテスト名
- 02明確で具体的な例
- 03フィクスチャとテストダブルの指針
- 04決定性と副作用の分離
- 05レビューと検証のチェックリスト
活用シーン
- 01新しい振る舞いのテスト追加
- 02回帰テストと境界ケースの作成
- 03テスト変更の脆弱な結合のレビュー
- 04外部作用やグローバル状態を伴うコードのテスト
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ jpcaparas/ skills --skill maintainable-testsインストール後、Claude Code で/maintainable-testsを入力して読み込まれたか確認します。
同梱ファイル
SKILL.md と一緒にインストールされる参考資料やスクリプト。
AGENTS.mdmetadata.jsonREADME.mdskill-card.prompt.mdagents/openai.yamlassets/.gitkeepevals/evals.jsonevals/files/publish-config.spec.tsreferences/doubles-and-boundaries.mdgotchas.mdlegacy-and-characterization.mdnaming-and-intent.mdprinciples.mdreview-rubric.mdside-effects-and-compatibility.mdsource-notes.mdstructure-and-fixtures.mdscripts/analyze_maintainable_tests.pytest_skill.pyvalidate.pytemplates/test-review.md詳細
保守しやすいテスト
数か月後に戻ってきた開発者が、動作、エッジケース、そして重要な各テストが存在する理由を理解できるように、テストを作成、編集、リファクタリング、レビューします。
パッシブトリガー
タスクに自動テスト、テストとして使われる例、フィクスチャ、モック、スタブ、フェイク、テストデータビルダー、回帰テスト、特性テスト、不安定なテスト、またはテスト容易性を高めるためのプロダクションコードの変更が含まれる場合は、このスキルをバックグラウンドで読み込みます。小規模な編集では軽量に適用します。基本ルールを黙って適用し、最終的な実装に影響するテスト設計上の判断だけを伝えます。
判断ツリー
これらのルールやローカルの例だけでは判断しにくいテスト設計上の決定がある場合に限り、関連するリファレンスを参照します。トピックが一致するだけでは、リファレンスを読む必要はありません。
-
新しい動作のテストを追加する場合: 各テストに、ユーザーから見えるルールまたはドメイン不変条件に基づいた名前を付け、動作を理解するのに役立つ具体例を使います。意図が不明確な場合は、
references/principles.mdまたはreferences/naming-and-intent.mdを参照します。 -
バグを修正する場合、または回帰テストを追加する場合: 既存の十分なカバレッジがあれば再利用し、問題が起きていたシナリオと期待される動作を証明します。レガシー動作を保持する場合や、不確実性について慎重な説明が必要な場合は、
references/legacy-and-characterization.mdを参照します。 -
エッジケースをカバーする場合: ハッピーパスを明確に示したうえで、意味のある境界条件をカバーし、十分なテストとの重複を避けます。フィクスチャやケースの構成が難しい場合は、
references/structure-and-fixtures.mdを参照します。 -
プロダクションコードが複雑に絡み合っている、またはモックできないためにテストが難しい場合: まず、フレームワークのオーバーライドや、確実に元に戻せるスコープ限定のレガシーパッチなど、既存の安全な接続点を使います。必要性と影響の大きさに見合い、許可されている場合に限り、プロダクションコードをリファクタリングします。テストのみを変更する作業では、必要なプロダクションコードの変更を提案するにとどめます。境界に関する判断が未解決の場合は、
references/doubles-and-boundaries.mdまたは利用可能であれば {{ skill:maintainable-code }} を参照します。 -
動作を変更する前にレガシーコードをリファクタリングする場合: 十分な動作カバレッジを再利用し、重要な不足箇所に限って特性テストを追加します。意図的なレガシー動作であることを明記し、小さく検証可能な手順でプロダクションコードを変更します。保持すべき契約が不明確な場合は、
references/legacy-and-characterization.mdを参照します。 -
テスト差分をレビューする場合: 動作が壊れていても通る可能性があるテスト、壊れやすい実装への結合、エッジケースのカバレッジ不足を、スタイル上の問題より先に指摘します。判断が難しいレビューでは、
references/review-rubric.mdを参照します。形式は任意です。 -
モック、スタブ、フェイク、フィクスチャ、インテグレーションテストを選ぶ場合: 動作を証明できる範囲で最も強力でないテストダブルを優先し、アダプター間でずれが生じる可能性がある場合は、十分な契約テストまたはインテグレーションテストも行います。選択が明白でない場合は、
references/doubles-and-boundaries.mdを参照します。既存のカバレッジも考慮します。 -
テストがネットワーク、待機、グローバルなフレームワーク状態、環境条件、ファイルシステムや CLI の作用、または複数のサポート対象依存関係バージョンに関わる場合: 意図しない作用を防ぎ、グローバル状態をリセットし、関連する設定の組み合わせをテストし、対象となるアーティファクトまたは機能分岐を正確に検証します。分離や互換性に関するリスクが未解決の場合は、
references/side-effects-and-compatibility.mdを参照します。
クイックリファレンス
| 状況 | デフォルトの対応 |
|---|---|
| 新しいドメインルール | 通常ケースを示す読みやすい例を1つ書き、重要な境界条件には焦点を絞った例を用意します |
| テスト名 | 動作と結果を明示します:rejects withdrawals that exceed the current balance |
| テスト本体 | Arrange / Act / Assert を優先し、ストーリーとして読めるよう、セットアップを十分近くに配置します |
| 繰り返されるセットアップ | ヘルパー名がドメインの意味を保つ場合にのみ、ヘルパーに切り出します |
| パラメーター化テスト | 各行を用意する理由が分かる、名前付きのケースを使います |
| レガシーな動作 | 互換性、データ移行、顧客との契約、バグの参照、または明示的に不明であることを示す短い根拠を追加します |
| テストしにくい本番コード | 安全な接合点を再利用します。必要で、変更の程度が適切であり、許可された範囲内に限ってリファクタリングします |
| モックに依存しすぎたテスト | 付随的なインタラクションのアサーションを、動作のアサーション、フェイク、またはアダプターのコントラクトテストに置き換えます |
| エッジケース | 「無効な入力を処理する」だけでなく、境界条件を名前に含めます |
| テスト内のネットワーク呼び出しやサブプロセス | 計画外の呼び出しは失敗させます。テスト対象のコントラクトに限り、明示的にフェイク、スタブ、または統合します |
| スリープまたは再試行の遅延 | 実際の待機をフェイクのスケジューラー/クロックに置き換え、それが動作として重要な場合は、要求された遅延をアサートします |
| グローバルなフレームワーク状態 | フレームワークで分離できない場合は、各テストの前に既知のベースラインに戻し、テスト後にクリーンアップします |
| 設定可能な動作 | コントラクトで定められた要素をテストします。通常はデフォルト/上書き/効果であり、環境や機能の分岐は関係する場合に限って追加します |
| ファイルや CLI の効果 | 終了コード/結果に加え、重要となる正確なパス、種類、内容、バックアップ、およびキャンセル時に変更されないことをアサートします |
| 互換性の保証 | サポート対象の最低構成と現行バージョンでテストします。本番コードにオプションの機能がある場合は、機能あり/なしの経路も追加します |
| レビュー | そのテストによって、新しいメンテナーが重要な動作を理解できるかを確認します |
コアルール
- テストを生きたドキュメントとして扱います。読者がテスト名や例から、機能、その用語、重要な境界を理解できるようにします。
- 実装名ではなく、振る舞いを表す名前を優先します。
validateInviteが false を返すことではなく、「期限切れの招待を拒否する」ことをテストします。 - 具体的で、ドメインに即した例を使います。ルールを明確にするために役立つ場合は、実際の値、通貨、ロール、状態、日付、ID を使います。
- 意図がその場で分かるようにします。振る舞いを理解するために、離れた場所にあるフィクスチャファクトリー、隠れたグローバル設定、フレームワークのマジックを読む必要がないようにします。
- 可読性と DRY が相反する場合は、DAMP なテストを優先します。シナリオを明確に保つ重複は、気の利いた共通セットアップより望ましいことがよくあります。
- 各テストでは 1 つの振る舞いに焦点を当てます。ただし、その振る舞いを証明するために必要な結果はすべてアサートします。状態の変化、返された結果、発行されたイベントは、1 つの観測可能なルールを構成する場合、一緒に扱えます。
- 契約に応じてテストダブルを選びます。スタブはクエリに応答し、フェイクは単純な状態をモデル化し、スパイは重要な効果を観察し、モックは本質的なやり取りのみを強制します。
- プライベートな内部実装を露出させたり、悪い抽象化を固定化したり、テストを通すだけのために広範なインターフェースを追加したりしません。既存の安全な境界を優先します。必要かつ相応な場合に限り、本番コードの境界を再設計し、テストのみを対象とする範囲を決して超えないようにします。
- 名前だけでは理由を伝えられないエッジケースやレガシーな振る舞いは、ドキュメントに残します。将来の読者が、その振る舞いが原則に基づくものか、歴史的経緯によるものか、契約上のものか、一時的なものかを知る必要があります。
- テストの決定性を保ちます。時間、乱数、外部サービス、ロケール、タイムゾーン、並行処理、永続化を明確な境界で制御します。
- 正常系だけでなく、失敗の仕方も検証します。回帰テストは、対処対象のバグがあると失敗する必要があります。
- 新しいパターンを取り入れる前に、ローカルのテストフレームワークとスタイルに合わせます。
- 意図しない影響がテストハーネスで即座に失敗するようにします。スタブされていないネットワーク呼び出し、実際のスリープ、破壊的なコマンド、共有されたプロセス状態が、気付かれずに通ることがあってはなりません。
- 適切な対象と型で、実際に観測できるものをアサートします。ファイルの内容と比較する真偽値の存在チェックや、正しいアサーションを誤ったパスに対して行うことは、誤検知につながります。
- 設定可能な振る舞いでは、契約上の判断軸とその効果を分けてテストします。本番コードがオプション機能に対応している場合は、その機能がある場合とない場合の両方のパスをテストします。
保守可能なテストのゲート
テストの変更を完了する前に、可能な範囲でこのゲートを頭の中とローカルツールで確認します。
| ゲート | 合格条件 |
|---|---|
| 意図 | テスト名が、ドメインの言葉で振る舞いと期待される結果を示している |
| ストーリー | Arrange/Act/Assert、または同等のフェーズが分かりやすい |
| 証拠 | アサーションが、付随的な実装ではなく観測可能な振る舞いを証明している |
| フィクスチャ | セットアップが小さく、名前が付いており、コードを掘り下げなくても理解できる程度に近くにある |
| エッジケース | 境界のシナリオが明示され、その境界が重要な理由を説明している |
| レガシーの背景 | 歴史的な振る舞いや互換性のある振る舞いに、根拠またはチケットへの参照がある |
| 決定性 | 時間、乱数、I/O、ネットワーク、データベース、プロセス状態が制御されている |
| 分離性 | 予期しないネットワーク接続、待機、破壊的なコマンド、漏洩したグローバルなフレームワーク状態は失敗するか、明示的にリセットされる |
| 互換性 | サポート対象の最低構成と最新構成がテストされており、オプション機能が契約に含まれる場合は、その機能がある場合とない場合の分岐もテストされている |
| 成果物の証拠 | アサーションが、効果を証明するための正確なパス、値の型、内容、変更されていない状態を検査している |
| テストダブル | モック、スタブ、フェイク、スパイが、ルールを証明できる最小限の強さになっている |
| 本番コードの設計 | 既存の安全な境界を利用しており、本番コードのリファクタリングは必要かつ相応で、許可されている |
| 引き継ぎ | 新しいメンテナーが、振る舞いを把握するための道しるべとしてテストを利用できる |
運用ワークフロー
小さな編集は小さく保ちます。焦点を絞ったコンテキストと既存の十分なカバレッジを活用し、説明文や固定のレポートテンプレートは作成しません。
-
まず調査します。 パターンを追加する前に、近くにあるテスト、フィクスチャ、ファクトリー、ヘルパー、本番コードの処理経路を読みます。
-
挙動の説明を特定します。 ルールを特定し、無関係な懸念事項は分けて扱います。曖昧さの解消に役立つ場合に限り、内容を書き留めます。
-
例を選びます。 既存のカバレッジを確認してから、実際の製品リスクを説明できる、未対応の通常ケース、境界ケース、失敗ケースを選びます。
-
コードを観測可能な形に整えます。 確実に復元・分離できるスコープ限定のレガシーパッチなど、既存の安全な接点を使います。挙動を安全に証明できる接点がない場合は、最小限の本番コードのリファクタリングを提案し、許可された範囲内でのみ実施します。この制限を回避するために、実際の副作用や漏出するグローバル変数を使ってはいけません。
-
テストをドキュメントとして書きます。 セットアップには意味を持たせ、アクションは明確にし、アサーションは具体的にします。履歴、不変条件、または自明でないドメイン上のトレードオフがある場合に限り、短いコメントを追加します。
-
失敗時の挙動と読みやすさを検証します。 対象を絞ったテストを実行します。可能であれば、修正前には失敗すること、または防止対象のリグレッションが起きれば失敗することを確認します。オンボーディング資料として読み直します。
-
簡潔に報告します。 対象となる挙動、検証内容、残るリスクを報告します。重要な場合に限り、境界に関する判断にも触れます。内容のないセクションや、定型的なプロセスの説明は省略します。
オプションのヘルパー
ヘルパーは、素早く確認するためのプロンプトスキャナーとして使い、判定結果として扱わないでください。
python3 scripts/analyze_maintainable_tests.py /path/to/project
python3 scripts/analyze_maintainable_tests.py /path/to/project --json
このヘルパーは、ディレクトリのスキャン対象を一般的なテストパスと注釈付き Rust ソースに絞ります。一方、明示的に指定された対応ソースファイルは直接検査します。認識された宣言について、名前、アサーション、フィクスチャ、テストダブル、結合、実時間、実際のスリープ、乱数、レガシーに関する根拠など、限定されたシグナルを確認します。ネットワークアクセスは検出しません。スキャンで何も見つからなくても、テストスイートの品質が保証されるわけではありません。また、スキャンで警告が出ても、テストに問題があるとは限りません。
ガイダンスが役に立たなくなった場合
同梱のテストパターンが機能しない場合や、リポジトリのランナーと競合する場合は、インストールされているバージョンと公式のフレームワークドキュメントを確認します。副作用の分離を維持しながら、挙動を証明するための慣用的な方法を優先し、未実行のチェックは正直に明記します。ソースとバージョン、そして判別可能なリグレッションを示して、標準スキルの修正または削除を提案します。インストール済みのコピーを黙って編集したり、このスキルの例を満たすだけの脆いテストを追加したりしないでください。
読み方ガイド
| 必要な内容 | 読むもの |
|---|---|
| 既定の背後にある原則 | references/principles.md |
| 挙動のドキュメントとしてテストに名前を付ける方法 | references/naming-and-intent.md |
| Arrange / Act / Assert、フィクスチャ、データセット、ヘルパー | references/structure-and-fixtures.md |
| モック、スタブ、フェイク、境界、本番コードの分割 | references/doubles-and-boundaries.md |
| レガシーシステム、特性テスト、リグレッションの根拠 | references/legacy-and-characterization.md |
| 副作用の分離、設定マトリックス、生成物の厳密なアサーション、互換性テスト | references/side-effects-and-compatibility.md |
| 重要度優先のテスト差分レビュー | references/review-rubric.md |
| よくある落とし穴とアンチパターン | references/gotchas.md |
| ソースの影響と適用に関する注記 | references/source-notes.md |
注意点
- 重要な説明がファクトリーや汎用ヘルパーに隠れていると、テストが短くても読みにくくなります。
- 重複によって独立した動作が明確になるなら、テストが重複していても保守しやすい場合があります。
- モックを多用したテストは、プロダクトの動作ではなく、実装上の判断を記録していることがよくあります。やり取り自体が動作である場合にモックを使いましょう。
- 各行が単なるデータになっていると、パラメーター化テストは分かりにくくなります。ケースに名前を付け、期待される結果を見える形にしておきましょう。
- 根拠の説明がないレガシーテストは、混乱を固定化します。その動作が意図的なものか、過去の事情によるものか、一時的なものか、あるいは不明なのかを明記しましょう。
- 苦痛を伴うテストを避けられないものとして受け入れてはいけません。その苦痛は、より明確な境界が必要な本番コードからの設計上のフィードバックかもしれません。
- 一見もっともらしいマッチャーでも、対象の型が間違っていたり、誤った成果物を参照していたりすれば、何も証明できないことがあります。すべてのアサーションを、観測可能なコントラクトまでたどって確認しましょう。
- 最新の依存関係でのみテストに通っても、謳っている互換性の範囲が証明されたことにはなりません。サポート対象の最古の組み合わせと、オプション機能のフォールバックをテストしましょう。