test-generation について
コーディングエージェント向けのテスト生成スキルです。既存コードや修正済みバグに対し、プロジェクトの流儀に沿ったテストを作成・実行し、未検証箇所も報告します。境界値やエラー経路の選定を重視します。
主な機能
- 01プロジェクトのテストフレームワークに沿った生成
- 02境界値とエラー経路の検証
- 03回帰・単体・統合・UIテストへの対応
- 04テスト実行と未検証箇所の報告
- 05スタックに応じたテスト配置
活用シーン
- 01関数やモジュールへのテスト追加
- 02修正済みバグの回帰テスト作成
- 03カバレッジの低いモジュールで次のテストを選定
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ jablonkai/ skills --skill test-generationインストール後、Claude Code で/test-generationを入力して読み込まれたか確認します。
同梱ファイル
SKILL.md と一緒にインストールされる参考資料やスクリプト。
references/flutter-dart.mdkotlin-kmp.mdother-languages.mdrust-cpp.mdswift-apple.mdtest-design.md詳細
テスト生成
目的
既存のコードを、実際に不具合を検出できるテストに変換します。入力は関数、クラス、モジュール、またはバグ報告です。出力は、プロジェクトで採用されているフレームワークを使い、適切なソースセットに配置され、実行して成功するテストファイルと、未テストの項目についての簡潔な説明です。
価値があるのは、テストを書くことではなく、ケースを選ぶことです。正常系だけをたどるテストはコードの説明にはなりますが、コードを保護するものではありません。実際の不具合の多くは境界やエラー経路で発生するため、そうした箇所をケースの対象にします。
使用する場面
- 「
<file/function/class>のテストを書いて」— 新規コードにも既存コードにも対応します - バグを修正したばかりで、修正がなければ失敗する回帰テストが必要な場合
- テストのないモジュールに、テスト一式を用意する場合
- カバレッジが低く、次にテストする「量」ではなく「対象」を決めたい場合
このスキルの対象外:
- テスト不足にも触れる、プロジェクト全体の監査 →
code-analyzer - テストやアプリが失敗しており、根本原因を特定したい場合 →
error-debugging - プロジェクトにまだ存在しないテストフレームワークのセットアップ → 先にセットアップしてください(スタックのリファレンスを参照)。その後、このスキルに戻ってください
先に、より具体的なスキルを使う
一部のスタックには、専用のスキルがあります。該当する場合は、ここで説明を再実装せず、専用スキルを呼び出してください。このスキルは、周辺の判断(どのケースを選ぶか、どのソースセットを使うか、カバレッジ)に使い、専用スキルがない場合に限って適用してください。
| 状況 | 呼び出すスキル |
|---|---|
package:test を使った Dart のユニットテスト | dart-add-unit-test |
mockito と build_runner を使った Dart のモック | dart-generate-test-mocks |
| Flutter のウィジェットテスト | flutter-add-widget-test |
Flutter の integration_test フロー | flutter-add-integration-test |
| Compose / CMP の UI テスト、セマンティクス、スクリーンショットテスト | compose-ui-testing-patterns |
Swift、Rust、C++、および UI 以外の Kotlin/KMP には専用スキルがありません。このスキルで直接対応します。
前提条件
1. 使用しているスタックと、すでに採用されているフレームワークを確認します。 拡張子だけで判断せず、ビルドファイルから検出してください。KMP の commonTest ソースセットにある .kt ファイルと、Android のユニットテストにある .kt ファイルでは、テストの方法が大きく異なります。
| ビルドファイル / シグナル | スタック | リファレンス |
|---|---|---|
build.gradle.kts に kotlin("multiplatform") と commonTest がある | Kotlin Multiplatform | kotlin-kmp.md |
build.gradle(.kts) に com.android.application と androidTest/ がある | Kotlin/JVM、Android | kotlin-kmp.md |
androidx.compose.* または compose-multiplatform の依存関係がある | Compose / CMP | compose-ui-testing-patterns スキル |
pubspec.yaml がある | Flutter / Dart | flutter-dart.md |
Package.swift、*.xcodeproj、*.xcworkspace がある | Swift(iOS/macOS) | swift-apple.md |
Cargo.toml がある | Rust | rust-cpp.md |
CMakeLists.txt、conanfile.txt、*.cpp がある | C / C++ | rust-cpp.md |
package.json、pyproject.toml、go.mod、Maven の pom.xml がある | JS/TS、Python、Go、Java | other-languages.md |
スタックのリファレンスは、該当するものを1つだけ読み込んでください。複数読み込むと、適用されない慣習が混在し、コンテキストを浪費します。
2. テスト一式はすでにありますか? 何かを書く前に、既存のテストファイルを2〜3個読んでください。プロジェクトの命名規則、アサーションライブラリ、フィクスチャのスタイル、モックの方法はすでに決まっています。このスキルの慣習よりも、それらに合わせることが重要です。
# find the existing suite and its conventions
find . -type d \( -name test -o -name tests -o -name '*Test*' -o -name commonTest \) -not -path '*/build/*' -not -path '*/node_modules/*' | head -20
テスト一式がまったくない場合は、その旨を伝えてください。プロジェクトで最初のテストを追加する際は、フレームワークの依存関係やテストランナーの設定が必要になることが多く、ユーザーが想定しているより大きな変更になる可能性があります。
ワークフロー
ステップ 1:テスト対象のコードを読む
シグネチャだけでなく、実際の実装を読んでください。テストでアサートできる点と、テストを難しくしている点を確認します。
- 契約 — 入力、出力、スローまたは返却されるエラー、文書化された不変条件
- 分岐 — すべての
if、when/switch、早期リターン、ループ境界、?:のフォールバック - 依存関係 — 置き換えが必要なもの(時計、ネットワーク、ファイルシステム、データベース、乱数)
- 状態 — 結果は入力だけで決まる純粋関数か、それとも過去の呼び出しに依存するか
- 並行処理 — サスペンド関数、async/await、スレッド、アクター、チャネル
Serena などのシンボルを認識するツールを使い、ファイル全体ではなく、対象のシンボルとその呼び出し箇所を読みます。呼び出し箇所を見るとコードが実際にどう使われているかがわかり、現実的なケースを導き出せます。
コードが現状のままではテストできない場合(ハードコードされたシングルトン、隠れた時計、I/O を行うコンストラクターなど)は、そのことを伝え、解決に必要な最小限の変更を示してください。テストを可能にするために、本番コードを黙ってリファクタリングしないでください。提案にとどめるかどうかは、ユーザーに判断してもらいます。
ステップ 2:ケースを選ぶ
テンプレートではなく、契約と分岐からケースを導き出します。テスト対象の各単位について、次のカテゴリを検討し、このコードに実際に当てはまるものを選びます。
| カテゴリ | 確認すること |
|---|---|
| 正常系 | 典型的な値を使った一般的な呼び出し — メソッドごとではなく、意味のある動作ごとに 1 つ |
| 境界値 | 0、1、多数、最初、最後、最小、最大、すべてのインデックスと長さの境界を 1 つ超える値 |
| 空/不在 | 空文字列、空のコレクション、null/None/nil、省略されたオプション値、デフォルト引数 |
| 無効な入力 | 不正なデータ、範囲外の値、言語が許す場合は型の不一致 |
| エラー経路 | スローされるすべての例外、Err、失敗した Future と、それらからの復旧処理 |
| 状態遷移 | 2 回呼び出す、順序を変えて呼び出す、close/dispose の後に呼び出す |
| 並行処理 | キャンセル、タイムアウト、呼び出しの競合 — 実際に並行処理が行われる場合に限る |
ケースリストの大半を決めるルールは 2 つです。
- 1 テストにつき 1 つの動作。 5 つのことをアサートするテストは、1 つのメッセージで失敗し、残り 4 つについては何もわかりません。
- 失敗する可能性があるケースだけを選ぶ。 想定できる実装変更をしてもテストが失敗しないなら、そのテストは何も検証していないため、削除します。
バグ修正のリグレッションテストでは、順序を逆にします。まずバグを再現するテストを書き、修正前のコードで失敗することを確認します(それが不可能な場合は、その理由を説明します)。その後、そのテストを残します。
ケース選択のヒューリスティック、命名、フィクスチャ/モック/フェイクの選び方については、test-design.md を参照してください。契約からケースが明らかでない場合に読み込みます。
ステップ 3:ファイルを正しい場所に配置する
配置が間違っていることが、生成されたテストが実行されない最も一般的な原因です。スタックのリファレンスに正確なルールが記載されています。基本的な構成は常に同じです。
- テストのソースルートでソースパスを再現し、テスト対象の単位にちなんだファイル名を付ける
- テストに必要なものに応じてソースセットを選ぶ。共有ロジックや純粋なロジックは common または plain の単体テストセットに配置し、デバイス、実際のフレームワーク、UI が必要なものは platform/instrumented のセットに配置する
- 既存のテストスイートのパッケージ/モジュール宣言に合わせる
ステップ 4:テストを書く
ステップ「Prerequisites 2」で確認した規約と、スタックリファレンスに記載されたイディオムに従います。
- Arrange、act、assert — この順に、目で見てわかるように分けます
- 説明的な名前 — 条件と期待する結果を名前に含めます。ファイルを開かなくても失敗内容がわかります
- 実装ではなく動作をアサートする — 内部呼び出しをアサートすると設計が固定され、リファクタリングのたびに壊れます
- 必要なものだけを置き換える — フェイクより実オブジェクト、モックよりフェイクを使います。自分が所有する型をモックするのは、通常、設計上の問題を示しています
- 構造上、決定的になるようにする — クロックを注入し、乱数ソースにシードを設定し、ディスパッチャー/スケジューラーを制御します。非同期処理を待つために
sleepを使ってはいけません - 重複よりフィクスチャを優先する — ただし、誰も把握できないフィクスチャ階層を作るより、テストで多少重複するほうが低コストです
プロジェクトの既存テストスイートに自然に馴染むテストを書きます。
ステップ 5:テストを実行する
実行していない生成テストは、まだ下書きです。テストスイートを実行します。まず新しいテストに絞り、その後、ファイル全体を実行し、失敗したものを修正します。
正確なコマンドはスタックリファレンスに記載されています。出力は失敗箇所に絞り、実行結果全体を貼り付けないようにします。
次に、テストに価値があることを確認します。テストが失敗するはずの方法で実装を変更し(頭の中で試すか、実際に変更して元に戻します)、該当するテストで検出できることを確認します。意図的に壊した実装でもテストが通る場合は、正直に報告します。つまり、そのテストは何もアサートしていません。
ステップ 6:カバレッジと未対応箇所を報告する
プロジェクトでカバレッジツールが設定されている場合は実行します(Kover、flutter test --coverage、xccov、cargo llvm-cov、gcov/llvm-cov、または JS/Python の同等ツール。スタックリファレンスを参照)。設定されていない場合、依頼されていないツールの追加は行わず、分岐を読んで未対応箇所を報告します。
報告するのは割合ではなく、未対応箇所です。
## Tests added
<file paths, and one line per behavior covered>
## Verification
<command run, and the real result>
## Still untested
<specific branches or paths, as `file:line` — with why, if there is a reason>
カバレッジは未テストの分岐を見つけるためのもので、目標値ではありません。数値を上げることだけを目的にテストを追加してはいけません。
操作
| 操作 | ユーザーの意図 | 出力 |
|---|---|---|
unit(デフォルト) | 「この関数/クラスのテストを書いて」 | 依存関係を置き換えた分離テスト。実行して成功済み |
integration | 「これをエンドツーエンドでテストして」「モジュールをまとめてテストして」 | 実際の連携先(DB、HTTP、ファイルシステム)を使ったテスト。セットアップ/ティアダウンを含む |
ui | 「この画面/ウィジェット/ビューをテストして」 | スタックの UI ハーネスによる UI テスト。専用スキルがある場合は、そこに引き継ぎます |
regression | 「修正したばかりのバグのテストを書いて」 | 修正がなければ失敗する、焦点を絞ったテストを 1 つ |
coverage | 「何がテストされていないか教えて」 | カバレッジの実行と、未テストの分岐を優先度順に並べた一覧。依頼された場合に限りテストを追加 |
重要な制約
- 作り上げた動作をアサートしてはいけません。 実装を読みます。意図した動作が本当に曖昧な場合は、アサーションとして推測を組み込まず、質問します。
- 実行していないテストを書いてはいけません。 失敗を隠さず報告します。
- テストを通すために本番コードを変更してはいけません。 それでは本来の目的が逆転します。コードがテスト不可能な場合は、別途変更を提案します。
- テストを追加する際、既存のテストスイートを書き換えたり「改善」したりしてはいけません。
- 自分が書いていない失敗テストを削除したり、弱めたりしてはいけません。 赤いテストは情報です。
- ユニットテストでは、スリープ、実時間への依存、ネットワーク利用を禁止します。 不安定なテストは、赤を無視する習慣を生むため、テストがないよりも悪いものです。
- カバレッジの数値を追い求めてはいけません。 アサーションのないテストやゲッターのテストはカバレッジを引き上げるだけで、何も守りません。
- 実際の認証情報、トークン、本番データをフィクスチャに含めてはいけません。 テストデータはコミットされ、公開されます。
参考資料
- test-design.md — 横断的なテストケースの選定、命名、フィクスチャとフェイクとモックの違い、カバレッジレポートの読み方
- kotlin-kmp.md —
kotlin.testと JUnit、ソースセット、runTest/TestDispatcher、Turbine、MockK、Kover - flutter-dart.md — 専用の Dart/Flutter スキルへの案内、ゴールデンテストとカバレッジ
- swift-apple.md — Swift Testing、XCTest、XCUITest、非同期テスト、
xccov - rust-cpp.md —
cargo test、ドキュメントテスト、proptest、criterion、llvm-cov、GoogleTest、Catch2、CTest - other-languages.md — Jest/Vitest、pytest、Go の
testing、標準の JUnit