find-untested-sources

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Find Untested Sources

查找未测试源码

Purpose

用途

Coverage tools answer "which lines were executed?" — they require a green build and a passing test run, which is minutes-to-tens-of-minutes on a real repo. The question this skill answers is different and much cheaper:
Which source files have no test file referencing any of their declared types/symbols?
That's the question an agent asks before writing a new test — and it can be answered statically in a few seconds by parsing source files, with no build, no dependency resolution, and no compilation. The output is a deterministic test-pairing map that lets the agent pick the next file to test without reading the entire codebase first.
覆盖率工具回答的是“哪些代码行被执行了?”——它们需要成功构建和通过测试运行,在真实仓库中这通常需要数分钟到数十分钟。而本工具解决的问题不同,且成本低得多:
哪些源文件没有任何测试文件引用其声明的类型/符号?
这是智能体在编写新测试之前会提出的问题——通过解析源文件即可静态地在几秒内得到答案,无需构建、无需依赖解析、无需编译。输出结果是确定性的测试配对映射,让智能体无需先通读整个代码库就能选择下一个要测试的文件。

Two engines — pick one

两种引擎——二选一

This skill ships two interchangeable analyzers with a compatible JSON contract:
EngineScriptUse when
Roslyn (C#)
scripts/Find-UntestedSources.cs
The repo is .NET-only. Parses every
.cs
file with the Roslyn syntax API and does strict namespace disambiguation, so it is materially more accurate on duplicated short names like
Settings
or
Context
.
tree-sitter (polyglot)
scripts/find_untested_sources.py
The repo is not exclusively C#, or you want one tool across Python, TypeScript/JavaScript, Go, Java, Rust, Ruby, and C#.
For a .NET-only repository, prefer the Roslyn engine — its namespace-aware pairing beats the polyglot engine's identifier overlap.
本工具提供两个可互换的分析器,它们的JSON输出格式兼容:
引擎脚本使用场景
Roslyn (C#)
scripts/Find-UntestedSources.cs
仓库为纯.NET项目。使用Roslyn语法API解析所有
.cs
文件,并执行严格的命名空间消歧,因此对于
Settings
Context
这类重复的短名称,其准确性显著更高。
tree-sitter(多语言)
scripts/find_untested_sources.py
仓库并非仅包含C#,或者需要一款支持Python、TypeScript/JavaScript、Go、Java、Rust、Ruby和C#的通用工具。
对于纯.NET仓库,优先选择Roslyn引擎——它基于命名空间的配对能力优于多语言引擎的标识符匹配方式。

Required workflow

必选工作流程

  1. Use the narrowest repository or package root named by the caller. Do not scan a parent workspace when the request identifies a subdirectory.
  2. Execute the appropriate analyzer once. Do not replace analyzer execution with manual globbing, filename matching, or visual inspection. For polyglot analysis, pass
    --include-tested
    when the answer must distinguish paired sources from unpaired sources.
  3. Base the result on the analyzer's JSON. Preserve its paired/unpaired classification and suggested relative path; do not guess a different path.
  4. When the caller named a subdirectory, prefix analyzer-relative paths with that subdirectory so reported paths are workspace-relative.
  5. Report the requested result plus the static-pairing coverage caveat. Do not append build, package-install, test-run, or coverage commands. When paired sources exist, name their covering test files so the unpaired classification is auditable.
  1. 使用调用者指定的最窄范围仓库或包根目录。当请求明确子目录时,请勿扫描父级工作区。
  2. 执行一次对应的分析器。请勿用手动通配符匹配、文件名匹配或视觉检查替代分析器执行。若进行多语言分析,当需要区分已配对和未配对源码时,需传入
    --include-tested
    参数。
  3. 基于分析器输出的JSON生成结果。保留其配对/未配对分类及建议的相对路径,请勿自行猜测其他路径。
  4. 当调用者指定子目录时,在分析器返回的相对路径前添加该子目录,使报告路径为工作区相对路径。
  5. 报告请求的结果及静态配对覆盖的说明。请勿附加构建、包安装、测试运行或覆盖率命令。当存在已配对源码时,列出对应的测试文件,以便审核未配对分类的准确性。

When to Use

使用场景

  • User asks "where should I add tests?", "which files have no tests?", "find untested code", "give me a test gap list", "what's the next file to test".
  • Before invoking a test-generation agent, to produce a prioritized worklist.
  • After generating tests, to verify each new test file pairs to a source file.
  • To enumerate "weakly paired" source files (only one referring test) for follow-up depth checks.
  • 用户询问“我应该在哪里添加测试?”、“哪些文件没有测试?”、“找出未测试的代码”、“给我测试缺口清单”、“下一个要测试的文件是什么”。
  • 在调用测试生成智能体之前,生成优先级任务清单。
  • 生成测试后,验证每个新测试文件是否与对应源文件配对。
  • 枚举“弱配对”源文件(仅被一个测试文件引用)以进行后续深度检查。

When Not to Use

禁用场景

  • Line/branch coverage — use
    coverage-analysis
    .
  • CRAP-score / risk hotspots — use
    coverage-analysis
    .
  • Are existing tests strong? — use
    test-gap-analysis
    (mutation reasoning) or
    assertion-quality
    .
  • 行/分支覆盖率分析——请使用
    coverage-analysis
  • CRAP评分/风险热点——请使用
    coverage-analysis
  • 现有测试是否足够完善?——请使用
    test-gap-analysis
    (变异推理)或
    assertion-quality

Roslyn engine (C#)

Roslyn引擎(C#)

Prerequisites

前置条件

  • .NET SDK that supports file-based apps (
    dotnet run script.cs
    ). Pinned in the repo's
    global.json
    (SDK 11 preview or later).
  • No internet access required beyond the initial NuGet restore of
    Microsoft.CodeAnalysis.CSharp
    on first run.
  • 支持基于文件的应用的.NET SDK(可通过
    dotnet run script.cs
    运行)。仓库的
    global.json
    中已固定版本(SDK 11预览版或更高)。
  • 首次运行时仅需从NuGet还原
    Microsoft.CodeAnalysis.CSharp
    ,之后无需联网。

Usage

使用方法

powershell
undefined
powershell
undefined

From the skill folder

从工具目录执行

dotnet run scripts/Find-UntestedSources.cs -- <repo-root> [--top N]
dotnet run scripts/Find-UntestedSources.cs -- <仓库根目录> [--top N]

Save the report

保存报告

dotnet run scripts/Find-UntestedSources.cs -- <repo-root> > pairing.json
dotnet run scripts/Find-UntestedSources.cs -- <仓库根目录> > pairing.json

Iterate the untested list, highest-API-surface first

按API规模从高到低遍历未测试文件列表

$report = Get-Content pairing.json | ConvertFrom-Json $report.untested | Select-Object -First 10 source, decl_count, suggested_test_path

Diagnostics go to stderr; JSON goes to stdout.
$report = Get-Content pairing.json | ConvertFrom-Json $report.untested | Select-Object -First 10 source, decl_count, suggested_test_path

诊断信息输出到stderr;JSON结果输出到stdout。

Output schema

输出 schema

jsonc
{
  "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"
    ]
  }
}
jsonc
{
  "repo": "<绝对路径>",
  "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,            // 文件中的类型声明数量
      "suggested_test_path":      // 在已发现的测试项目下镜像源文件路径
        "tests/Foo.Tests/Bar/BarTests.cs"
    }
  ],
  "source_to_tests": {
    "src/Foo/Baz.cs": [
      "tests/Foo.Tests/BazTests.cs",
      "tests/Foo.IntegrationTests/Scenarios/BazScenarios.cs"
    ]
  }
}

How it works

工作原理

  1. File discovery — recursive walk pruning
    bin/
    ,
    obj/
    ,
    node_modules/
    ,
    .git/
    ,
    .vs/
    ,
    packages/
    , and any dotted subdir. Skips generated files (
    .g.cs
    ,
    .Designer.cs
    ,
    .AssemblyInfo.cs
    ).
  2. Test vs source classification — walks up to the nearest
    .csproj
    and marks it a test project if the project name ends in
    .Tests
    ,
    .Test
    ,
    .UnitTests
    ,
    .IntegrationTests
    ,
    .E2E
    ,
    .EndToEnd
    ,
    .Spec
    ,
    .Specs
    , or the content references
    Microsoft.NET.Test.Sdk
    ,
    MSTest.Sdk
    ,
    Microsoft.Testing.Platform
    ,
    xunit
    ,
    NUnit
    ,
    TUnit
    , or
    <IsTestProject>true</IsTestProject>
    .
  3. Source index (parallel) — parse each source file with
    CSharpSyntaxTree.ParseText
    (syntax only, no compilation); record every
    BaseTypeDeclarationSyntax
    /
    DelegateDeclarationSyntax
    as
    (ShortName, EnclosingNamespace, FilePath)
    .
  4. Test scan (parallel) — parse each test file, collect
    using
    directives + enclosing namespace, walk every
    IdentifierToken
    , look it up in the short-name index, and disambiguate strictly: an identifier is attributed only if the declaration's namespace matches one of the test file's
    using
    directives, the enclosing namespace, or a prefix of them. This avoids noise where common names like
    Settings
    or
    Context
    match every project.
  5. Pairing & suggestion — invert into
    source → [tests]
    . Build a production-to-test project map from
    <ProjectReference>
    entries; for each untested source, mirror its in-project relative path under the referencing test project to suggest a path.
  6. JSON emit — ordered by declaration count desc, then alphabetical.
  1. 文件发现——递归遍历目录,排除
    bin/
    obj/
    node_modules/
    .git/
    .vs/
    packages/
    及所有点开头的子目录。跳过生成文件(
    .g.cs
    .Designer.cs
    .AssemblyInfo.cs
    )。
  2. 测试文件与源文件分类——向上遍历至最近的
    .csproj
    文件,若项目名称以
    .Tests
    .Test
    .UnitTests
    .IntegrationTests
    .E2E
    .EndToEnd
    .Spec
    .Specs
    结尾,或项目内容引用
    Microsoft.NET.Test.Sdk
    MSTest.Sdk
    Microsoft.Testing.Platform
    xunit
    NUnit
    TUnit
    ,或包含
    <IsTestProject>true</IsTestProject>
    ,则标记为测试项目。
  3. 源码索引(并行)——使用
    CSharpSyntaxTree.ParseText
    解析每个源文件(仅语法分析,无需编译);记录每个
    BaseTypeDeclarationSyntax
    /
    DelegateDeclarationSyntax
    (短名称, 所属命名空间, 文件路径)
  4. 测试扫描(并行)——解析每个测试文件,收集
    using
    指令+所属命名空间,遍历每个
    IdentifierToken
    ,在短名称索引中查找,并严格消歧:仅当声明的命名空间与测试文件的
    using
    指令、所属命名空间或其前缀匹配时,才将标识符关联到对应源文件。这避免了
    Settings
    Context
    这类通用名称导致的跨项目误匹配。
  5. 配对与路径建议——将结果转换为
    源文件 → [测试文件]
    的映射。从
    <ProjectReference>
    条目构建生产项目到测试项目的映射;对于每个未测试源文件,在引用它的测试项目下镜像其项目内相对路径,生成建议的测试文件路径。
  6. JSON输出——按声明数量降序排列,再按字母顺序排列。

Polyglot engine (tree-sitter)

多语言引擎(tree-sitter)

Prerequisites

前置条件

  • Python 3.10+.
  • pip install tree-sitter-language-pack
    (single self-contained wheel that bundles parsers for 300+ languages and the high-level
    process()
    API). No native build, no per-language grammar install.
  • Python 3.10+。
  • 安装
    pip install tree-sitter-language-pack
    (单个独立包,包含300+语言的解析器和高级
    process()
    API,无需原生构建,无需单独安装各语言语法)。

Usage

使用方法

powershell
undefined
powershell
undefined

From the skill folder

从工具目录执行

python scripts/find_untested_sources.py <repo-root>
python scripts/find_untested_sources.py <仓库根目录>

Restrict to a language (repeatable)

限制语言(可重复指定)

python scripts/find_untested_sources.py <repo-root> --lang python --lang typescript
python scripts/find_untested_sources.py <仓库根目录> --lang python --lang typescript

Truncate the report (top 20 by declared API surface)

截断报告(按API规模取前20个未测试文件)

python scripts/find_untested_sources.py <repo-root> --limit-untested 20 > pairing.json
python scripts/find_untested_sources.py <仓库根目录> --limit-untested 20 > pairing.json

Iterate, highest-API-surface first

按API规模从高到低遍历

$report = Get-Content pairing.json | ConvertFrom-Json $report.untested_sources | Select-Object -First 10 path, declaration_count, suggested_test_path

Pass `--include-tested` to additionally emit `tested_sources` (omitted by
default to keep the payload small for LLM consumption). Diagnostics go to
stderr; JSON goes to stdout.
$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处理的 payload 大小)。诊断信息输出到stderr;JSON结果输出到stdout。

Output schema

输出 schema

jsonc
{
  "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" }
  ]
}
jsonc
{
  "repo_root": "<绝对路径>",
  "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" }
  ]
}

How it works

工作原理

  1. File discovery — recursive walk pruning common build/vendor dirs (
    bin
    ,
    obj
    ,
    node_modules
    ,
    target
    ,
    dist
    ,
    build
    ,
    vendor
    ,
    __pycache__
    ,
    .venv
    ,
    .git
    , …) and generated files (
    .d.ts
    ,
    .g.cs
    ,
    .Designer.cs
    ,
    _pb2.py
    ,
    *.min.js
    ,
    AssemblyInfo.cs
    , …).
  2. Language detection
    detect_language_from_path
    maps the extension to a supported language; unknown extensions are skipped.
  3. Test-vs-source classification — per-language path heuristics:
    LanguageTest rule
    Pythonpath contains
    tests/
    /
    test/
    ; or filename starts with
    test_
    or ends
    _test.py
    ; or
    conftest.py
    .
    JS/TS/TSXpath contains
    __tests__
    ,
    tests
    ,
    test
    ,
    spec
    ,
    e2e
    ; or filename contains
    .test.
    /
    .spec.
    .
    Gofilename ends
    _test.go
    .
    Javapath contains
    test
    /
    tests
    ; or filename ends
    Test.java
    /
    Tests.java
    .
    Rustpath contains
    tests/
    /
    benches/
    .
    C#path contains
    tests/
    ; or project segment ends
    .Tests
    /
    .Test
    /
    .UnitTests
    /
    .IntegrationTests
    ; or filename ends
    Tests
    /
    Test
    .
    Rubypath contains
    spec/
    /
    test/
    ; or filename ends
    _spec.rb
    /
    _test.rb
    .
  4. Per-file extraction
    process(text, ProcessConfig(structure, imports, symbols))
    returns declared items, raw import statements, and a flat declared -name list.
  5. Pairing — for each test file, union import resolution (per language, e.g. Python
    from pkg.mod import x
    pkg/mod.py
    ; Java
    import a.b.C;
    a/b/C.java
    ; C#
    using
    is namespace-not-file, so a no-op) with identifier overlap (word-like tokens, length ≥ 4, matched against declared names).
  6. JSON emit
    untested_sources
    ordered by declaration count descending.
  1. 文件发现——递归遍历目录,排除常见构建/依赖目录(
    bin
    obj
    node_modules
    target
    dist
    build
    vendor
    __pycache__
    .venv
    .git
    等)及生成文件(
    .d.ts
    .g.cs
    .Designer.cs
    _pb2.py
    *.min.js
    AssemblyInfo.cs
    等)。
  2. 语言检测——
    detect_language_from_path
    根据文件扩展名映射到支持的语言;未知扩展名的文件会被跳过。
  3. 测试文件与源文件分类——基于语言的路径规则:
    语言测试文件判定规则
    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
    结尾。
  4. 单文件提取——
    process(text, ProcessConfig(structure, imports, symbols))
    返回声明项、原始导入语句和平坦化的声明名称列表。
  5. 配对逻辑——对于每个测试文件,结合导入解析(各语言不同,例如Python
    from pkg.mod import x
    pkg/mod.py
    ;Java
    import a.b.C;
    a/b/C.java
    ;C#
    using
    是命名空间而非文件,因此不处理)和标识符匹配(类单词令牌,长度≥4,与声明名称匹配)。
  6. JSON输出——
    untested_sources
    按声明数量降序排列。

Limitations (be honest with the agent)

局限性(需如实告知智能体)

Both engines are static, parse-only heuristics that trade a little accuracy for orders-of-magnitude lower cost than coverage. Known gaps:
  • Reflection / DI-resolved types referenced only via a string name or container resolution won't be detected — the type's short name never appears in the test source.
  • Extension methods invoked as instance methods (C#): the declaring static class is not named, so its file is not credited.
  • var
    , target-typed
    new()
    , pattern matching
    lose the type token; the file-level union usually still catches it through other references.
  • Short identifier names (polyglot, < 4 chars) are dropped to avoid noisy pairings on names like
    id
    ,
    db
    ,
    Tag
    .
  • Monorepo path aliases (TS path mapping, Java module-info) are not resolved; a suffix-match fallback may pick the wrong source if two files share a trailing path segment.
For these cases, run actual coverage (
coverage-analysis
) on the unpaired candidates the agent has already triaged.
Always label the final result as a static pairing heuristic, not evidence of line or branch coverage. Include that caveat even when every requested source file has an obvious matching or missing test.
两个引擎均为静态、仅解析的启发式工具,以少量准确性为代价换取比覆盖率分析低几个数量级的成本。已知的局限性包括:
  • 反射/依赖注入解析的类型:仅通过字符串名称或容器解析引用的类型无法被检测到——因为测试源码中不会出现该类型的短名称。
  • 扩展方法(C#):当作为实例方法调用时,声明该方法的静态类不会被命名,因此其所在文件不会被标记为已配对。
  • var
    、目标类型
    new()
    、模式匹配
    :会丢失类型令牌;通常文件级的联合匹配仍会通过其他引用捕获到对应类型。
  • 短标识符名称(多语言引擎,<4个字符):会被忽略,以避免
    id
    db
    Tag
    这类名称导致的误配对。
  • 单仓库路径别名(TS路径映射、Java module-info):不会被解析;若两个文件共享末尾路径段,后缀匹配的回退逻辑可能会选错源文件。
对于这些情况,可对智能体已筛选出的未配对候选文件运行实际的覆盖率分析(
coverage-analysis
)。
请始终将最终结果标记为静态配对启发式分析,而非行或分支覆盖率的证据。即使所有请求的源文件都有明显的匹配或缺失测试,也需包含此说明。

Outputs the agent should consume

智能体应使用的输出内容

  • untested[*].source
    /
    untested_sources[*].path
    — pick the next source file to test (highest declaration count first).
  • *.suggested_test_path
    — drop-in target for the new test file; the Roslyn engine honors the test project that already
    <ProjectReference>
    s the source's project, so
    dotnet sln add
    is not needed. The polyglot engine may suggest a co-located test when no test root is discoverable; that is a valid fallback, but prefer an established repository test directory when one exists.
  • source_to_tests
    (Roslyn) /
    --include-tested
    tested_sources
    (polyglot) — verify a newly written test file lands in the list for the intended source.
  • orphan_tests
    (polyglot) — tests that don't reference any same-language source file; useful for triaging stale or integration-only tests.
  • untested[*].source
    /
    untested_sources[*].path
    ——选择下一个要测试的源文件(优先选择声明数量最多的)。
  • *.suggested_test_path
    ——新测试文件的直接目标路径;Roslyn引擎会遵循已通过
    <ProjectReference>
    引用源项目的测试项目,因此无需执行
    dotnet sln add
    。当未发现测试根目录时,多语言引擎可能会建议同目录的测试文件,这是有效的回退方案,但如果仓库已有既定的测试目录,优先使用该目录。
  • source_to_tests
    (Roslyn)/
    --include-tested
    参数下的
    tested_sources
    (多语言)——验证新编写的测试文件是否被列入对应源文件的测试列表中。
  • orphan_tests
    (多语言)——未引用任何同语言源文件的测试;可用于筛选过时或仅用于集成测试的文件。",