find-untested-sources について
テスト作成前に、対応するテストファイルがないソースファイルを探す静的解析スキルです。Roslyn と tree-sitter のエンジンを備え、ビルドやテスト実行なしで対応関係を出力します。
主な機能
- 01Roslyn と tree-sitter の選択
- 02C# と複数言語の解析
- 03ソースとテストファイルの静的解析
- 04対応・未対応ファイル一覧の出力
- 05テストファイルの候補パス提示
活用シーン
- 01新しいテスト対象のソースを選ぶ
- 02生成したテストとソースの対応を確認する
- 03多言語リポジトリのテスト不足を整理する
- 04対応するソースがないテストを特定する
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ dotnet/ skills --skill find-untested-sourcesインストール後、Claude Code で/find-untested-sourcesを入力して読み込まれたか確認します。
同梱ファイルscripts/find_untested_sources.pyscripts/Find-UntestedSources.cs
詳細
テスト未作成のソースを探す
目的
カバレッジツールが答えるのは「どの行が実行されたか」です。カバレッジツールには、成功するビルドとテストの実行が必要で、実際のリポジトリでは数分から数十分かかります。このスキルが答えるのは、それとは別の、はるかに低コストな質問です。
宣言された型やシンボルを参照するテストファイルがないソースファイルはどれですか?
これは、新しいテストを書く前にエージェントが尋ねる質問です。ソースファイルを解析すれば、ビルドも、依存関係の解決も、コンパイルも行わずに数秒で静的に回答できます。出力されるのは決定論的なテスト対応マップです。エージェントはコードベース全体を先に読まずに、次にテストするファイルを選べます。
2 つのエンジン — どちらかを選択
このスキルには、互換性のある JSON コントラクトを備えた、相互に置き換え可能な 2 つのアナライザーが含まれています。
| エンジン | スクリプト | 使用する場合 |
|---|---|---|
| Roslyn (C#) | scripts/Find-UntestedSources.cs | リポジトリが**.NET 専用の場合。Roslyn の構文 API で各 .cs ファイルを解析し、厳密な名前空間の曖昧性解消**を行うため、Settings や Context など短い名前が重複している場合に、実質的に高い精度を発揮します。 |
| tree-sitter (多言語対応) | scripts/find_untested_sources.py | リポジトリがC# 専用ではない場合、または C#、Python、TypeScript/JavaScript、Go、Java、Rust、Ruby、Kotlin、Swift、PowerShell、C++ に対応した単一のツールを使用したい場合。 |
.NET 専用のリポジトリでは、Roslyn エンジンを優先してください。名前空間を考慮した対応付けにより、ポリグロットエンジンの識別子の重複に基づく方法よりも優れた結果が得られます。
必須のワークフロー
- 呼び出し元が指定したリポジトリまたはパッケージのルートのうち、最も範囲の狭いものを使用します。リクエストでサブディレクトリが指定されている場合、親ワークスペースをスキャンしないでください。
- 適切なアナライザーを 1 回実行します。アナライザーの実行を、手動での glob 検索、ファイル名の照合、目視確認に置き換えないでください。
ポリグロット解析では、対応済みのソースと未対応のソースを区別する必要がある場合、
--include-testedを渡します。 「静的な対応付けのみ」という指定は、対象リポジトリのコンパイルとテストの実行を禁止するものです。このスキルの解析のみを行うアナライザーの起動は禁止されません。呼び出し元が「ビルドしない」とも指定している場合は、この違いを簡潔に説明してください。 アナライザーの依存関係は、環境の前提条件として扱います。パッケージをインストールしたり、誤ったエンジンを試したり、リポジトリをビルドしたり、アナライザーの実行に失敗した際に手動スキャンへ切り替えたりしないでください。前提条件の不足として報告してください。 - 結果はアナライザーの JSON に基づいてください。対応済み/未対応の分類と、提案された相対パスをそのまま使用し、別のパスを推測しないでください。
- 呼び出し元がサブディレクトリを指定した場合、報告するパスがワークスペースからの相対パスになるよう、アナライザーの相対パスの先頭にそのサブディレクトリを付けます。
- 求められた結果に加え、静的な対応付けのカバレッジに関する注意点を報告します。ビルド、パッケージのインストール、テストの実行、カバレッジの各コマンドは追加しないでください。対応済みのソースがある場合は、対応付けを監査できるよう、それらをカバーするテストファイル名を示します。
使用する場面
- ユーザーが「ソースとの対応付けに基づいて、どこにテストを追加すべきか」「テストがないファイルはどれか」「対応するテストのないソースファイルを探して」「静的なテストギャップの一覧を出して」と尋ねた場合。
- テスト生成エージェントを呼び出す前に、ソースとの対応付けに基づく作業リストを作成する場合。
- テストを生成した後に、新しい各テストファイルがソースファイルに対応しているか確認する場合。
- 「対応が弱い」ソースファイル(参照するテストが 1 つのみ)を列挙し、追加の確認を行う場合。
使用しない場面
- 行/分岐カバレッジ —
coverage-analysisを使用してください。 - 実際のカバレッジデータに基づく優先順位 —
coverage-analysisを使用してください。 - CRAP スコア/リスクの高い箇所 —
coverage-analysisを使用してください。 - 既存テストの品質確認 —
test-gap-analysis(ミューテーションに基づく評価)またはassertion-qualityを使用してください。
Roslyn エンジン (C#)
前提条件
- ファイルベースアプリをサポートする .NET SDK(
dotnet run script.cs)。リポジトリのglobal.jsonで固定されています(SDK 11 プレビュー以降)。 - 初回実行時に
Microsoft.CodeAnalysis.CSharpを NuGet で復元した後は、インターネット接続は不要です。
使い方
# From the skill folder
dotnet run scripts/Find-UntestedSources.cs -- <repo-root> [--top N]
# Save the report
dotnet run scripts/Find-UntestedSources.cs -- <repo-root> > pairing.json
# Iterate the untested list, highest-API-surface first
$report = Get-Content pairing.json | ConvertFrom-Json
$report.untested | Select-Object -First 10 source, decl_count, suggested_test_path
診断情報は stderr に、JSON は stdout に出力されます。
出力スキーマ
{
"repo": "<absolute path>",
"elapsed_ms": 8883,
"counts": {
"source_files": 3036,
"test_files": 867,
"untested_files": 1852,
"paired_files": 1184
},
"untested": [
{
"source": "src/Foo/Bar.cs",
"decl_count": 8, // # of type declarations in the file
"suggested_test_path": // mirror of source under a discovered test project
"tests/Foo.Tests/Bar/BarTests.cs"
}
],
"source_to_tests": {
"src/Foo/Baz.cs": [
"tests/Foo.Tests/BazTests.cs",
"tests/Foo.IntegrationTests/Scenarios/BazScenarios.cs"
]
}
}
仕組み
- ファイルの検出 — 再帰的に走査し、
bin/、obj/、node_modules/、.git/、.vs/、packages/およびドットで始まるサブディレクトリを除外します。生成されたファイル (.g.cs、.Designer.cs、.AssemblyInfo.cs)はスキップします。 - テストとソースの分類 — 最も近い
.csprojまで上方向にたどり、プロジェクト名が.Tests、.Test、.UnitTests、.IntegrationTests、.E2E、.EndToEnd、.Spec、.Specsのいずれかで終わる場合、またはコンテンツがMicrosoft.NET.Test.Sdk、MSTest.Sdk、Microsoft.Testing.Platform、xunit、NUnit、TUnit、あるいは<IsTestProject>true</IsTestProject>を参照する場合に、テストプロジェクトとして判定します。 - ソースインデックス(並列処理) — 各ソースファイルを
CSharpSyntaxTree.ParseTextで解析し(構文のみ、コンパイルなし)、すべてのBaseTypeDeclarationSyntax/DelegateDeclarationSyntaxを(ShortName, EnclosingNamespace, FilePath)として記録します。 - テストのスキャン(並列処理) — 各テストファイルを解析し、
usingディレクティブと 包含する名前空間を収集して、すべてのIdentifierTokenを走査し、 短い名前のインデックスを検索します。さらに 厳密に曖昧さを解消 し、宣言の名前空間がテストファイルのusingディレクティブ、包含する名前空間、またはそれらのプレフィックスのいずれかと一致する場合にのみ、識別子を関連付けます。これにより、SettingsやContextなどの一般的な名前がすべてのプロジェクトに一致するノイズを防ぎます。 - ペアリングと候補の提示 —
ソース → [テスト]の形に反転します。<ProjectReference>エントリから 本番プロジェクトとテストプロジェクトの対応表を作成し、テストされていない各ソースについて、参照元のテストプロジェクト内でプロジェクト相対パスを再現した候補パスを提示します。 - JSON の出力 — 宣言数の降順、次にアルファベット順で並べます。
ポリグロットエンジン(tree-sitter)
前提条件
- Python 3.10 以降。
pip install tree-sitter-language-pack(300 以上の言語用パーサーと高レベルのprocess()API をバンドルした、単体で完結する wheel)。ネイティブビルドも、言語ごとの文法のインストールも不要です。
使い方
# From the skill folder
python scripts/find_untested_sources.py <repo-root>
# Restrict to a language (repeatable)
python scripts/find_untested_sources.py <repo-root> --lang python --lang typescript
# Truncate the report (top 20 by declared API surface)
python scripts/find_untested_sources.py <repo-root> --limit-untested 20 > pairing.json
# Iterate, highest-API-surface first
$report = Get-Content pairing.json | ConvertFrom-Json
$report.untested_sources | Select-Object -First 10 path, declaration_count, suggested_test_path
--include-tested を渡すと、tested_sources も出力されます(LLM での利用時にペイロードを小さく保つため、デフォルトでは省略されます)。診断情報は stderr に、JSON は stdout に出力されます。
出力スキーマ
{
"repo_root": "<absolute path>",
"summary": {
"source_files": 3138,
"test_files": 761,
"tested_source_files": 1419,
"untested_source_files": 1719,
"orphan_test_files": 15,
"languages": ["csharp"]
},
"untested_sources": [
{
"path": "src/Foo/Bar.cs",
"language": "csharp",
"declaration_count": 8,
"declarations": ["Bar", "BarOptions", "IBar", "..."],
"suggested_test_path": "src/Foo/BarTests.cs"
}
],
"orphan_tests": [
{ "path": "tests/SomeIntegrationTest.cs", "language": "csharp" }
]
}
動作の仕組み
-
ファイルの検出 — 一般的なビルド/ベンダーディレクトリ(
bin、obj、node_modules、target、dist、build、vendor、__pycache__、.venv、.git、…)や、生成ファイル(.d.ts、.g.cs、.Designer.cs、_pb2.py、*.min.js、AssemblyInfo.cs、…)を除外しながら、再帰的に走査します。 -
言語の検出 —
detect_language_from_pathで拡張子をサポート対象の言語に対応付けます。未知の拡張子はスキップします。 -
テストとソースの分類 — 言語ごとのパス判定ヒューリスティックを使用します。
言語 テスト判定ルール Python パスに tests//test/が含まれる、またはファイル名がtest_で始まるか_test.pyで終わる、またはconftest.pyである。JS/TS/TSX パスに __tests__、tests、test、spec、e2eが含まれる、またはファイル名に.test./.spec.が含まれる。Go ファイル名が _test.goで終わる。Java パスに test/testsが含まれる、またはファイル名がTest.java/Tests.javaで終わる。Rust パスに tests//benches/が含まれる。C# パスに tests/が含まれる、またはプロジェクトのセグメントが.Tests/.Test/.UnitTests/.IntegrationTestsで終わる、またはファイル名がTests/Testで終わる。Ruby パスに spec//test/が含まれる、またはファイル名が_spec.rb/_test.rbで終わる。Kotlin パスに test//tests//spec/が含まれる、またはファイル名がTest.kt/Tests.kt/Spec.ktで終わる。Swift パスに test//tests//uitests//integrationtests/が含まれる(大文字と小文字を区別しない)、またはファイル名がTest.swift/Tests.swiftで終わる。PowerShell パスに test//tests//pester/が含まれる、またはファイル名が.Tests.ps1/.Test.ps1で終わる。C++ パスに test//tests//testing/が含まれる、またはファイル名がtest_で始まるか_test.cpp/_tests.cppで終わる。 -
ファイルごとの抽出 —
process(text, ProcessConfig(structure, imports, symbols))は、宣言された項目、生のインポート文、および宣言名のフラットなリストを返します。 -
対応付け — 各テストファイルについて、インポート解決(言語ごとに実施。例:Python の
from pkg.mod import x→pkg/mod.py、Java のimport a.b.C;→a/b/C.java。C# のusingはファイルではなく名前空間を参照するため、何もしません)と、識別子の重複(単語形式のトークン。長さ ≥ 4。宣言名との照合)の和集合を使用します。 -
JSON の出力 —
untested_sourcesは宣言数の降順で並べます。
制限事項(エージェントに正直に伝えること)
どちらのエンジンも静的な解析のみを行うヒューリスティックであり、カバレッジ解析よりも桁違いに低いコストと引き換えに、多少の精度を犠牲にしています。既知の制限事項は次のとおりです。
- リフレクション/DI によって解決される型:文字列名やコンテナーによる解決を介してのみ参照される型は検出できません。型の短縮名がテストソースに現れないためです。
- 拡張メソッド(C#):インスタンスメソッドとして呼び出される場合、宣言元の静的クラス名が記述されないため、そのファイルは対応付けられません。
var、ターゲット型指定のnew()、パターンマッチング:型トークンが失われます。ただし通常、ファイル単位の和集合ではほかの参照を通じて検出できます。- 短い識別子名(複数言語で使用されるもの。4 文字未満):
id、db、Tagなどによるノイズの多い対応付けを避けるため、除外されます。 - モノレポのパスエイリアス(TS のパスマッピング、Java の module-info):解決されません。サフィックス一致によるフォールバックでは、2 つのファイルが末尾のパスセグメントを共有している場合、誤ったソースが選ばれることがあります。
このようなケースでは、エージェントがすでにトリアージした未対応候補に対して、実際のカバレッジ解析(coverage-analysis)を実行してください。
最終結果には必ず、これが静的な対応付けヒューリスティックであり、行カバレッジや分岐カバレッジの証拠ではないと明記してください。要求されたすべてのソースファイルについて、対応するテストが明らかに存在する場合も、存在しない場合も、この注意書きを含めてください。
エージェントが利用する出力
untested[*].source/untested_sources[*].path— 次にテストするソースファイルを選びます(宣言数の多い順)。*.suggested_test_path— 新しいテストファイルの配置先です。Roslyn エンジンは、ソースのプロジェクトをすでに<ProjectReference>しているテストプロジェクトを優先するため、dotnet sln addは不要です。ポリグロットエンジンは、テストルートを特定できない場合、ソースと同じディレクトリにテストを配置するよう提案することがあります。ソースと同じディレクトリにある兄弟ファイルにすでにテストが対応している場合は、その兄弟ファイルに対応するテストディレクトリが既定の規約となっているため、未対応の兄弟ファイルにも同じディレクトリを使い、ソースと同じディレクトリへの配置に切り替えないでください。source_to_tests(Roslyn)/--include-testedtested_sources(ポリグロット)— 新しく作成したテストファイルが、対象のソースに対応する一覧に含まれていることを確認します。orphan_tests(ポリグロット)— 同じ言語のソースファイルを参照していないテストです。古くなったテストや、統合テストのみのテストを仕分ける際に役立ちます。