1. 简介
Fucini Coverage Studio 把您的构建已经生成的覆盖率跟踪文件变成编辑器内的一等覆盖率 —— 在 VS Code 和 Visual Studio 中。它读取二十五种格式,归一化为一个规范的 Fucini 模型,确定性地合并多次运行,并在编辑器本身中呈现 —— 装订线、CodeLens、悬停提示和 Covered Files 视图 —— 因此无需离开编辑器去打开单独的查看器。两个版本共用一个读取器:同一份报告在两者中给出的数字精确到每一位都相同。本指南以 VS Code 为基准撰写;Visual Studio 有差异之处,In Visual Studio 框会说明。
该扩展在设计时充分考虑了功能安全工作流程:MC/DC 覆盖率、区域和 C++ 实例化覆盖率、安全加权的综合评分,以及与 IEC 61508、ISO 26262 和 DO-178C 理念保持一致的、可配置的 COMPLIANT / NON-COMPLIANT 判定。
Screenshot pending — add assets/img/marketplace-demo.gif (see SCREENSHOTS.md).2. 安装与要求
- VS Code ≥ 1.88 —— 此扩展支持的最低版本 —— 或 Visual Studio 2022 或 2026,Community、Professional 或 Enterprise(见下方的框)。
- 项目已经生成的覆盖率跟踪文件(见 §4)。
从 Marketplace 安装,或通过 Extensions → ⋯ → Install from VSIX… 从 .vsix 文件安装。该扩展会在启动时激活,并在活动栏中添加一个 Coverage 图标。
在 Visual Studio 中。同一产品作为第二个扩展 Fucini Coverage Studio for Visual Studio 发布,适用于 Visual Studio 2022 和 2026 —— Community、Professional 和 Enterprise。它不使用 Visual Studio 自带的代码覆盖率:在 Visual Studio 2022 中那是 Enterprise 功能,因此在 Community 和 Professional 上,这是该 IDE 唯一能显示的覆盖率。打开 .vsix 即可安装 —— VSIX Installer 会询问安装到哪个 Visual Studio —— 或通过 Extensions → Manage Extensions;打开解决方案或文件夹时扩展即加载。Coverage 窗口需要 Microsoft Edge WebView2 Runtime,Windows 11 和 Visual Studio 2026 已自带;编辑器中的标记无需它也能工作。
3. 快速开始
想在接入自己的项目之前先看看效果吗?下载演示项目(约 6 KB——一个带有现成 lcov.info 的小型 C 项目):
- 解压到任意位置,然后在 VS Code 中打开
coverage-studio-demo.code-workspace(File → Open Workspace from File…)。 - 如果尚未安装,请安装 Fucini Coverage Studio(见上文 §2)。
- 该工作区自带的
.vscode/settings.json已指向随附的coverage/lcov.info,因此扩展一激活,装订线和 Covered Files 视图就会立即出现——无需执行Load Coverage步骤。 - 想自己重新生成跟踪文件吗(例如使用不同的编译器)?运行
scripts/gen-coverage.sh(Windows 上为.ps1)——可用的编译器目标见该工作区自带的README.md。
在 Visual Studio 中。打开一个解决方案或文件夹(File → Open → Folder,CMake 项目就是这样打开的)—— 上面的演示以文件夹方式打开,其 .vscode/settings.json 在那里同样会被读取。位于设置指定位置的报告会自动加载,输出窗口的 Fucini Coverage 窗格会说明加载了哪些文件;否则用 Tools → Coverage Studio → Load Coverage File…,一次即可 —— 会被记住。打开一个源文件并选择 Show Coverage in Editor:红色是从未执行的行,琥珀色是只走过一个方向的判定,Ctrl+Shift+U, N 跳到下一个未覆盖的行。View → Other Windows → Coverage 是显示每个文件数字及总分的树,状态栏会显示该分数 —— 针对整次运行和当前文件。Tools → Coverage Studio → Getting Started 把这些步骤作为一个页面打开,每步都有一个按钮;第一次会自动打开。
- 使用您常规的测试工作流生成覆盖率跟踪文件。
- 命令面板(
Ctrl+Shift+P)→Fucini Coverage: Load Coverage。在watch.enabled开启(默认)的情况下,匹配的文件会自动加载。 - 打开一个已覆盖的源文件:状态栏显示其百分比,Coverage 视图随即填充,悬停在某行上显示其命中次数,运行也出现在原生的 Test Coverage 面板中。未覆盖行上的条、命中次数热力图和行内
×N计数只需一个设置即可开启(editorGutter.mode、hitCounts.inline)。
Screenshot pending — add assets/img/gutters.png (see SCREENSHOTS.md).4. 生成跟踪文件
以下任意一种格式在加载时都会被自动检测:
每种编译器的完整复制粘贴技巧——包括 Swift、Rust、Go、Python、.NET、PHP 等——见于 覆盖率技巧: Swift · Clang · GCC · Rust · Go · Python · Java/Kotlin · JS/TS · .NET · PHP · Ruby.
C / C++ —— LLVM(llvm-cov)
clang --coverage -fprofile-instr-generate -fcoverage-mapping test.c -o test
LLVM_PROFILE_FILE=test.profraw ./test
llvm-profdata merge -sparse test.profraw -o test.profdata
llvm-cov export ./test -instr-profile=test.profdata --format=json > coverage.json
llvm-cov JSON 是信息最丰富的输入 —— 除了行、分支和函数外,还携带区域、MC/DC 和 C++ 模板实例化信息。
C / C++ —— GCC(gcov → lcov)
gcc --coverage calc.c calc_test.c -o test && ./test
lcov --capture --directory . --output-file lcov.info
# or gcovr --cobertura coverage.xml
# or skip the conversion entirely: gcovr --json coverage.json
JavaScript / TypeScript —— Istanbul / nyc
nyc --reporter=json mocha # emits coverage/coverage-final.json
Java —— JaCoCo
mvn test # emits target/site/jacoco/jacoco.xml
Ruby —— SimpleCov
# .resultset.json (built-in) or coverage.json via the simplecov-json gem
Go
go test ./... -coverprofile=coverage.out # read directly, no conversion
Python — coverage.py
coverage run --branch -m pytest && coverage json # emits coverage.json
PHP — PHPUnit (Clover)
XDEBUG_MODE=coverage phpunit --coverage-clover clover.xml
.NET / C# — Coverlet (OpenCover)
dotnet test /p:CollectCoverage=true /p:CoverletOutputFormat=opencover
| 格式 | 典型文件 | 额外指标 |
|---|---|---|
| LCOV | lcov.info | 行、分支、函数 |
| llvm-cov JSON | coverage.json | + 区域、MC/DC、实例化 |
| Cobertura XML | coverage.xml | 行、分支 |
| JaCoCo XML | jacoco.xml | 行、分支、函数 |
| Istanbul / nyc | coverage-final.json | 语句、分支、函数 |
| SimpleCov | .resultset.json | 行 |
| Clover XML | clover.xml | 行、分支、函数 |
| OpenCover XML | coverage.opencover.xml | 行、分支、函数 |
| coverage.py JSON | coverage.json | 行、分支 |
| gcov / gcovr JSON | coverage.json | 行、分支、函数 |
| Go coverage profile | coverage.out | 行 |
| SonarQube generic | coverage.xml | 行、分支 |
| GNATcoverage XML (Ada) | *.xml | 行、分支、MC/DC |
| BullseyeCoverage XML | coverage.xml | 行、条件/判定 |
| Visual Studio | *.coveragexml | 行、分支 |
| Coveralls JSON | coveralls.json | 行、分支 |
| Xcode xccov JSON | coverage.json | 函数与文件级汇总 — 无逐行数据 |
| gcov plain text | *.gcov | 行、分支、函数调用 |
| VectorCAST | coverage.xml | 行、分支、MC/DC 配对、函数、函数调用 |
| Testwell CTC++ | *.txt | 行、条件/判定 |
| Lauterbach TRACE32 | *.xml | 语句、判定、条件、MC/DC、函数、调用、目标代码 |
| EMMA XML (Java) | coverage.xml | 行 |
| UCIS / Aldec UCDB | *.xml | 语句、判定分支 |
| NCover XML | Coverage.xml | 行、函数 |
| dotCover DetailedXML | dotCover.xml | 行、函数 |
| PIT (mutation) | mutations.xml | 变异得分 — 不提供覆盖率 |
| Stryker (mutation) | mutation-report.json | 变异得分 — 不提供覆盖率 |
在 Visual Studio 中测量原生 C++ 项目
在 Visual Studio 中。Visual Studio 自带的 C++ 项目覆盖率只限 Enterprise,而填补这一空缺的免费 OpenCppCoverage 插件已经停止维护。Tools → Coverage Studio → Run Native Project with Coverage 取启动项目 —— 一个 .vcxproj —— 向 MSBuild 询问它构建到哪里、用的是哪个工具集,然后以两种方式之一测量它。在 ClangCL 工具集上,若机器上有 LLVM 的 llvm-profdata 和 llvm-cov(Visual Studio 的 C++ Clang tools for Windows 组件会带上它们),它会通过一个让 MSBuild 优先导入的 props 文件,用 -fprofile-instr-generate -fcoverage-mapping 把项目再构建一次到 coverage\native\ 中 —— 项目本身不做任何改动 —— 在设置了 LLVM_PROFILE_FILE 的情况下运行程序,合并 profile 并导出 coverage\native\lcov.info,精确到区域。在任何其他工具集上,若安装了 OpenCppCoverage,它会按原样构建项目 —— Debug 构建带有 PDB —— 并在 OpenCppCoverage 下运行它,后者写出 coverage\native\opencppcoverage.xml。每条命令都输出到 Fucini Coverage 窗格,报告则像测试运行的报告一样被加载并记住。无法测量的项目会被告知原因以及该安装什么。MSBuild 17.8(Visual Studio 2022 17.8)或更高版本,用于它所提出的那个询问。
Screenshot pending — add assets/img/vs-native-run.gif (see SCREENSHOTS.md).5. 加载与发现
默认情况下,扩展会从工作区根目录开始扫描。您可以通过两个设置更精确地指定:
{
"fuciniCoverage.coverageFile.defaultLocation": "${workspaceFolder}/build",
"fuciniCoverage.coverageFile.paths": [
"coverage.json",
"reports/*.info",
"${env:CI_ARTIFACTS}/lcov.info",
"https://ci.example.com/artifacts/latest/coverage.xml"
]
}
- 每个
paths条目都相对于defaultLocation(未设置时相对于工作区根目录)。 - 包含
*的条目会被递归搜索;单纯的文件名仅在基础文件夹中直接匹配。 - 当
paths为空时,扩展会先查找coverage.json,然后查找lcov.info,并加载先找到的那个。 - Pick Coverage File 会使用你选择的文件来代替这些路径——每次选择都会加入集合,并全部合并——直到你卸载它们或修改这些设置为止。
- 变量——每个路径设置都会展开
${workspaceFolder}、${workspaceFolderBasename}、${userHome}、${env:NAME}、${command:id}和${config:section.key}。扩展不认识的变量会原样保留,因此拼写错误会以它所查找的路径的形式出现在发现日志中,而不是凭空消失。${command:id}会先运行该命令(仅在受信任的工作区中,最多五秒),因此${command:cmake.buildDirectory}会跟随所选的 CMake 工具包。 - URL 条目——
http(s)://条目会在每次加载时被抓取到扩展的存储中并从那里加载;Coverage 视图显示的是 URL。每次抓取都是条件式的(ETag / Last-Modified),因此未变化的工件不会下载任何内容;抓取失败时改为加载上次下载的副本,原因写在输出中。remote.headers添加Name: value形式的标头并展开其中的变量,remote.refreshMinutes按定时器重新抓取,而当 URL 本身不以文件名结尾时,#lcov.info片段用来指明格式。 - 忽略 glob——
fuciniCoverage.ignore在运行加载时把整个文件(供应商文件、生成的文件、第三方文件)从运行中丢弃,因此所有视图、门控和导出都在不含它们的情况下计数;加载结果说明剔除了多少个,输出列出它们的名字。模式匹配相对于工作区的路径和绝对路径,不含/的模式匹配任意深度的文件夹名或文件名,与.gitignore一样。 - 多根工作区——每个文件夹都按自己的条件进行发现:
defaultLocation、paths、pathRemap、ignore、watch.*和remote.*都按文件夹读取,因此文件夹自己的.vscode/settings.json或其.code-workspace条目会生效,条目中的${workspaceFolder}指该条目自己的文件夹。每个文件夹的跟踪文件合并为一次运行,Coverage 视图按文件夹对文件分组,向工作区添加或移除文件夹会自动重新加载覆盖率。 - 任务结束后重新加载——
watch.reloadAfterTasks可指定若干任务(tasks.json 中的label,或 Run Task 列表中所显示的source: name),它们结束时会重新加载覆盖率,无论watch.enabled是否开启——适用于监视器无法跟踪的构建。工作区之外的绝对paths条目也会被轮询,因此出现在那里的文件会被加载。 - 可取消的进度——加载十个或更多跟踪文件,或它们的总量达到 8 MB 或更多时,会在带有 Cancel 按钮的进度通知下运行,通知中显示正在读取的文件和所处阶段;较小的加载只转动状态栏。取消后上一次运行保持加载,并会如此说明。
自 1.2026.6 起,绝对路径的条目也可以含通配符:${workspaceFolder}/build/coverage-merged/*.lcov.info 会加载该文件夹中所有这样的文件,** 表示任意深度。第一个通配符之前的文件夹就是它查找的位置,而且这些文件夹会被监视,因此构建写到那里的报告会自动加载。Visual Studio 版本以同样的方式读取这类条目,而且自 1.2026.5 起,相对通配符在那里也会在任意深度搜索,就像这里一直以来那样。
未配置 paths 时,将加载在基础文件夹下找到的第一个常见报告文件名。首先是一直以来查找的四个——coverage.json、lcov.info、coverage.out、clover.xml——然后,自 0.2026.83 起,还有 jacoco.xml、coverage.xml、cobertura.xml、coverage.cobertura.xml、coverage.opencover.xml 和 coverage-final.json,接着是默认配置的输出位置:coverage/(nyc、Jest、vitest、PHPUnit、SimpleCov)、target/site/jacoco/jacoco.xml(Maven)、build/reports/jacoco/test/jacocoTestReport.xml(Gradle)以及 TestResults/*/coverage.cobertura.xml(dotnet test)。因此,Java 或 .NET 项目在首次打开时就会显示覆盖率,就像 lcov 项目一直以来那样。
自 1.2026.3 起,.NET 解决方案需要的更少了:众所周知的位置现在包括每个测试项目的 TestResults/<run>/ 文件夹,而不只是工作区文件夹自己的;Microsoft 收集器的 <user>_<machine>_<date>.cobertura.xml 也会在 Coverlet 的 coverage.cobertura.xml 旁被找到 —— 因此在 Visual Studio 的 Test Explorer 中运行过测试的解决方案在首次打开时就会显示其覆盖率,每个运行文件夹都会加载,合并时对每个文件以最新一次运行为准。而且默认开启的 fuciniCoverage.ignore.generated 会把设计器文件和生成的文件 —— *.Designer.cs、*.g.cs、*.generated.cs、*.razor.g.cs、AssemblyInfo、全局 using、Migrations/ 和 obj/ —— 在应用 ignore 之前就排除在每个总计、页面和导出之外,这样一张没人测试的生成表不会让解决方案过不了门槛。
在 Visual Studio 中:每次测试运行后都有覆盖率。像往常一样运行测试。Tools → Coverage Studio → Collect Coverage in Test Explorer… 会在解决方案旁放置一个带有 Microsoft Cobertura 收集器的 .runsettings。当 Tools → Options → Test → Auto detect runsettings files 开启时,Visual Studio 会自行拾取它;否则请在 Test → Configure Run Settings 下选择它一次。从此每次运行都会在测试项目下写出一份报告,对解决方案的监视会在它出现的那一刻加载它,并与设置所指定的其他报告合并 —— 无论来自 Test Explorer、终端还是脚本。没有任何东西挂钩 Test Explorer,因此 Visual Studio 2026 和 Microsoft.Testing.Platform 照常工作。Run Tests with Coverage 是 VS Code 的命令:它识别 dotnet test、vitest、jest、pytest、go、cargo、maven、gradle、phpunit 和 ctest,在 Output 窗口中开启覆盖率运行测试并加载它们写出的结果;没有 coverlet.collector 的测试项目用测试 SDK 自带的收集器运行,testRun.skipWhenTestsFail 会在运行以失败告终时把最后一次良好的覆盖率留在屏幕上。
一个需要令牌的 CI 工件,每一刻钟重新检查一次——令牌保留在环境中,绝不进入 settings.json:
{
"fuciniCoverage.coverageFile.paths": [
"https://ci.example.com/api/builds/latest/artifacts/coverage#lcov.info"
],
"fuciniCoverage.remote.headers": ["Authorization: Bearer ${env:CI_TOKEN}"],
"fuciniCoverage.remote.refreshMinutes": 15
}
Screenshot pending — add assets/img/remote-tracefile.png (see SCREENSHOTS.md).重新加载是刻意安静的——每次构建后弹出对话框,是一个只有一个答案的问题。不安静的是 Coverage 图标:当该视图不在你面前时,它会显示被重新读取的跟踪文件数量,提示信息则会列出它们的名字。打开视图,计数就会消失。
Screenshot pending — add assets/img/coverage-badge.gif (see SCREENSHOTS.md).无论跟踪文件怎么说都永远不该计入的文件:
{
"fuciniCoverage.ignore": ["vendor/**", "**/*.generated.ts", "build/**"]
}
在测试任务结束时重新加载,用于文件监视器无法跟踪的构建——替换符号链接输出树的运行、网络共享上的报告、对其文件做后处理的覆盖率步骤:
{
"fuciniCoverage.watch.reloadAfterTasks": ["npm: test", "coverage-report"]
}
6. 合并运行
每个已有的 paths 条目都会被加载并合并为一次运行。合并是确定性的——结果不取决于加载顺序——被多个跟踪文件测量的文件采用磁盘上 mtime 最新的那个的数值:模块单独的重跑会替换更广、更旧的运行对它的记录,而不是叠加。覆盖率视图会显示合并做了什么:每个文件行注明其数值来自哪个跟踪文件,数值覆盖了旧报告的地方徽章会带边框,树上方的每个跟踪文件行统计它有多少文件被覆盖或覆盖了其他文件,点击跟踪文件行会在树中高亮它的文件,直到再次点击(coverageView.mergedMarks 可关闭边框和高亮)。
合并后的运行可以包含比六项经典指标更多的内容,每一项都来自对其进行度量的报告:除了行、函数、区域、分支、MC/DC 和实例化之外,还有函数调用、目标代码和变异得分。Coverage 视图会为运行所包含的每项指标显示一列,而不包含的指标则不显示列;这些列按各指标在得分中的权重排序,最重的在前,任何宽度下都会显示,放不下的列横向滚动而不是消失;将视图拖到底部面板,它就会变成一张表格。状态栏卡片、HTML 报告以及 CSV 和 Markdown 导出都遵循同一规则:报告包含的内容会显示,不包含的内容则直接省略,而不是显示为零。
Screenshot pending — add assets/img/coverage-view-editor.png (see SCREENSHOTS.md).
Screenshot pending — add assets/img/coverage-view-columns.png (see SCREENSHOTS.md).
Screenshot pending — add assets/img/workspace-overview.gif (see SCREENSHOTS.md).
Screenshot pending — add assets/img/vs-workspace-overview.gif (see SCREENSHOTS.md).文件来自哪个报告
当多个已加载的报告测量同一个文件时,最新的那个会直接拿下该文件——当某个模块自己的重新运行取代了一次更早、范围更广的运行结果时,这是对的。但当这些报告有意测量同一份代码时,这就错了:编辑器会随着每次构建在它们之间来回切换,而每个报告都只讲述了一部分情况。coverageFile.sources 按文件说明应当改为如何处理。以下三种情况需要用到它:
- 某个报告更好。 llvm-cov 的
coverage.json带有 MC/DC,而同一构建的 GCCgcov.info只有行和分支:选择coverage.json,另一个则留给只有 GCC 构建的少数文件。 - 同一构建的多个测试套件。 单元测试、集成测试和硬件在环测试各自写出一份报告;每一份单独来看,都会漏掉其他套件运行过的行。合并,union:只要任一报告运行过某行,该行即为已覆盖,次数相加。
- 一套代码,两个编译器。 同样的源代码分别由 clang 和 GCC 构建,会在工具链特定的代码中走不同的路径——这里是一个内建函数,那里是一个可移植的回退实现——而每个报告都说另一半从未运行。合并,agree:只有当测量某行的每一个报告都运行过它时,该行才算已覆盖,各报告结果不一致的行以青绿色标记。这正是跨编译器检查要问的问题:我发布的每一个构建实际执行了什么?
您很少需要手写规则。在 Coverage 视图中右键单击一个文件并选择Choose Coverage Source...(或对打开的文件运行Choose Coverage Source for This File...):它会列出测量该文件的每一个报告,每个都附有各自的行、分支和 MC/DC,然后是Combine them — agree和Combine them — union;选择一项,并指明它适用于该文件、其文件夹还是某个模式。Clear Coverage Source for This File...会再次移除该规则。在设置中,每条规则由一个针对文件的 glob、一个针对报告的 glob(不含 / 的模式匹配报告的文件名,{a,b} 可列出多个)以及用于合并的 "combine": "agree" 或 "union" 组成。第一条文件匹配且其报告测量该文件的规则说了算;只有一个报告测量的文件永远不会被从该报告中拿走。合并后文件的分支、MC/DC 和区域来自在这些方面最丰富的报告。coverageFile.mergeStrategy: "order" 会按 coverageFile.paths 的顺序而非时间来决定没有任何规则涉及的文件。
{
"fuciniCoverage.coverageFile.paths": ["clang.json", "gcc/bits.gcov.json.gz"],
"fuciniCoverage.coverageFile.sources": [
{ "files": "src/**", "report": "{clang.json,bits.gcov.json.gz}", "combine": "agree" }
]
}
您会看到:Coverage 视图中该文件的行会注明其数值来自哪个报告;若为合并,则列出每个报告和所用模式,并以 ≠ n 表示存在分歧的行。装订线和带注释的源代码以青绿色标记这些行,并说明哪个报告运行了它们多少次。状态栏的悬停卡片会说明打开文件的来源,以及就整次运行而言生效的规则和其他文件是如何决定的。演示项目中的 samples/c-cross-check 就是双编译器的情形,可直接打开。Visual Studio 同样能做到这一切:在 Coverage 视图的某一行上使用 Choose Coverage Source...,以及在 Tools > Coverage Studio 下使用 Choose 和 Clear Coverage Source for This File...。
选择和合并来源属于付费套餐的功能。在 Community 中,最新的报告会拿下每个文件,与没有规则时一样;设置中的规则会被标明为未使用,而不是被悄悄忽略。

bits.c 执行Choose Coverage Source...——clang 报告、GCC 报告,或两者合并。


bits.c——该行显示 ≠ 4,只有一个构建运行过的四行为青绿色。
bits.c 的行覆盖率达到 100%——而构建之间存在分歧的行仍为青绿色。


7. 路径重映射
CI 通常会记录在您的工作站上不存在的绝对构建机器路径。使用有序规则重写这些路径:
{
"fuciniCoverage.pathRemap": [
{ "from": "/builds/ci/project", "to": "${workspaceFolder}" },
{ "from": "\\\\", "to": "/" }
]
}
规则按顺序应用;对于每个前缀,第一个匹配项生效。可以用它来消除 Windows/Unix 分隔符差异,或将容器路径映射到您的检出目录。
您很少需要自己编写规则。最常见的“加载了却什么都不亮”,是跟踪文件的路径来自容器或 CI 检出(/build/src/a.c、/home/runner/work/repo/repo/src/a.c、C:\agent\_work\1\s\src\a.c),而工作区里的是 src/a.c。当一次加载留下未匹配的文件时,扩展会在工作区中查找这些文件名,推断出能解释其中最多文件的前缀规则,以 [FUCINI_REMAP_002] 记录到日志,并在加载结果中提供 Apply Suggested Remap——点击一次即可把规则追加到 fuciniCoverage.pathRemap 并重新加载。Suggest Path Remap Rule 可从命令面板按需运行同样的分析。不点击就不会写入任何内容。
to 可使用 ${workspaceRoot},它代表跟踪文件所在的工作区文件夹;在多根工作区中,它会先尝试该文件夹,然后再尝试其他每个确实含有该文件的文件夹。规则本身按文件夹读取,因此多根工作区的每个根目录都可以有自己的规则。
8. 编辑器内覆盖率
- 装订线标记与标尺 —— 默认情况下,编辑器自身的装订线只显示发现项:MC/DC 条、自上次运行以来的三角形、已编辑行的圆点。
editorGutter.mode可添加仅未覆盖行的条或命中次数热力图(见下文);整个文件的彩色编码视图是 Annotated Source。 - 函数 CodeLens —— 每个函数上方显示覆盖率百分比;点击可跳转至其第一处未覆盖行。可通过
codeLens.enabled切换。 - 分支与 MC/DC 悬停 —— 悬停在判定行上可查看每个分支的已执行/未执行次数以及每个条件的向量表。可通过
mcdcGutter.enabled启用彩色边距条。 - 死代码警告 —— 具有可测量行且覆盖率为零的函数会在 Problems 面板中被标记(
deadCode.enabled)。 - Next / Previous Uncovered —— 在空白区域之间跳转。
- 状态栏 —— 一个覆盖整个运行的
$(dashboard) NN%项以及一个按文件显示的项,两者均根据综合评分进行着色;悬停可查看完整的指标表。 - 资源管理器徽章——按文件和按文件夹的覆盖率徽章,悬停显示指标细分。 默认关闭:用
explorerBadges.enabled打开。 - 自上次运行以来——上次运行执行过而本次未执行的每一行显示红色三角形,相反情况显示绿色三角形,附带概览标尺和悬停命中次数。
- 已编辑的缓冲区——所有标记、悬停和 CodeLens 随编辑与文本一起移动,并从重写的行上撤回,那里的灰点说明原因。
- 行内命中次数——
hitCounts.inline在每一条有语句开始的行后写上×12,也包括×0:这个数字一直都在悬停中显示,现在无需悬停。Toggle Inline Hit Counts 用于切换;状态栏菜单会视情况以“显示”或“隐藏”提供它。 - 逐行的 Problems 条目——
problems.perLine在已打开文档中每一段连续的从未执行的行上放一条 Information 诊断,并在编辑器中显示波浪线(一个四十行的死函数是一条,而不是四十条),设为uncoveredAndPartial时还会在每一条有分支臂从未走到的行上放一条。条目随您的编辑跟着行移动,文档关闭时消失;problems.enabled背后的逐文件条目保持不变。 - 行着色 —
lineHighlight按装订线标记的内容,在行本身背后加半透明底色(uncovered,或连已执行行一起的heatmap)。这是独立设置,因此可以让装订线保持干净而只给空白着色,或者反过来。 - 概览标尺 —
overviewRuler独立于装订线决定滚动条上的条纹:follow(跟随条形,与以往一致)、uncovered(始终显示,因此可以关闭装订线而滚动条仍然标出空白)或off。 - 哪个跟踪文件覆盖了某一行 — 合并了多个报告时,执行次数的悬停提示以覆盖来源:结尾,列出覆盖该行的每个报告(含 lcov
TN:中的套件名);点击 Coverage 视图中的跟踪文件行,会在每个打开的编辑器里点亮它贡献的那些行。
Screenshot pending — add assets/img/codelens-hover.png (see SCREENSHOTS.md).
Screenshot pending — add assets/img/codelens-crap.png (see SCREENSHOTS.md).
Screenshot pending — add assets/img/jump-between-gaps.gif (see SCREENSHOTS.md).
Screenshot pending — add assets/img/explorer-badges.png (see SCREENSHOTS.md).编辑器装订线模式
editorGutter.mode 决定编辑器自身的装订线在其始终显示的发现项之上,还显示哪些关于覆盖率的内容。off(默认)只有那些发现项。uncovered 在每一条从未执行的行上放一个条,在每一条有分支臂从未走到的行上放一个琥珀色条,已覆盖的行上什么都不放,这样一片绿墙就不会把那三条红线藏起来。coverage 再给每一条已覆盖的行加上绿色条,使整个文件以色阶的颜色呈现红、琥珀、绿。heatmap 则改为给每一条执行过的行加上暖色调,运行次数越多颜色越热——它显示的是一行运行了多少次,而不是是否被覆盖,因此一个 100% 的文件在这里呈黄色到棕色,从不是绿色。Choose Editor Gutter Mode... 可从命令面板或状态栏菜单中选择一个模式;状态栏右侧的模式按钮或 Alt+G 按一次即切换到下一个(statusBar.showModeToggle 可隐藏该按钮)。两者都会写入该设置。下面的示例同时开启未覆盖条和行内计数:
{
"fuciniCoverage.editorGutter.mode": "uncovered",
"fuciniCoverage.hitCounts.inline": true
}
Screenshot pending — add assets/img/gutters.png (see SCREENSHOTS.md).
coverage 模式——完整运行的行为绿色,分支臂从未走到之处为琥珀色,什么都没运行之处为红色。文件的状态栏项注明其数值来自哪个报告;旁边的模式按钮显示 Coverage。
Alt+G——heatmap、off、uncovered,然后回到 coverage。
Quote.cs 上的 Gutter: Coverage (Red, Amber, Green)。热力图是相对于文件而言的。某一行的色调是将其计数与同一文件中执行次数最多的行进行比较的结果,采用对数刻度,分为五级——因此,运行了一千次的循环体和只运行了两次的函数都清晰可见,不会一个淹没另一个;而一个所有内容都恰好运行一次的文件会呈现均匀的暖色,而不是均匀的浅色。在深色主题下,色阶从暗橄榄色过渡到鲜艳的橙色;在浅色主题下,则从浅黄色过渡到棕色:无论哪种情况,颜色越浓表示执行次数越多,而红色始终保留给从未运行过的行。lineHighlight: "heatmap" 会将同样的色调以淡色铺在代码背后,hitCounts.inline 则会在行尾写出确切的计数。
{
"fuciniCoverage.editorGutter.mode": "heatmap",
"fuciniCoverage.lineHighlight": "heatmap",
"fuciniCoverage.hitCounts.inline": true
}
Screenshot pending — add assets/img/heatmap-dark.png (see SCREENSHOTS.md).
Screenshot pending — add assets/img/heatmap-light.png (see SCREENSHOTS.md).
Screenshot pending — add assets/img/heatmap.gif (see SCREENSHOTS.md).颜色由您决定。colorScale.lowColor、midColor 和 highColor 绘制装订线、行底色以及每个百分比中的红、琥珀和绿;heatmap.colors 和 heatmap.colorsDark 保存热力图在浅色和深色主题下的五级色调,从运行次数最少的行到运行次数最多的行。设置页面在保存任何内容之前就会显示结果:在 Colour scale 下,预览会在各行滚动经过时始终保持可见——带两个枢轴点的渐变、以各自颜色显示的几个百分比,以及以 coverage 模式和热力图绘制的一个小型浅色编辑器和一个小型深色编辑器——并在选择颜色时重新绘制。
{
"fuciniCoverage.heatmap.colors": ["#e3f2fd", "#90caf9", "#42a5f5", "#1e88e5", "#0d47a1"]
}


一行中从未执行的部分
一行代码要么被覆盖,要么没有,而实际情况往往介于两者之间:return x > hi ? hi : x; 执行过,但它的 hi 分支从未执行。启用 lineHighlight 后,对于所有带有列范围的格式(llvm-cov、Istanbul、Go、dotnet-coverage),已执行行中从未执行的部分会在编辑器中着色显示——与带注释的源代码所标记的范围相同。仅适用于执行过的行:从未执行的行本来就已整行标记。对于 C++ 模板,只要任意一个实例化执行过某处,该处即计为已执行;各个实例化的具体情况由实例化指标报告。
Screenshot pending — add assets/img/partial-line.png (see SCREENSHOTS.md).哪些测试执行了这一行
有些报告不仅说明某一行执行过,还说明是哪些测试执行了它:在 .coveragerc 中设置 dynamic_context = test_function 并用 coverage json --show-contexts 输出的 coverage.py,使用 -coverbytest 采集的 OpenCover,以及每个测试写成一节(TN:)的 lcov tracefile,其各节被合并成一条记录,每行都带有测试。将鼠标悬停在行首显示命中次数的位置,即可看到测试名称 —— 缩短为类名和方法名,前五个列出名称,其余只给出数量;函数的 CodeLens 会说明有多少测试运行了它。Show Tests That Ran This Line —— Command Palette、编辑器的右键菜单、在 Visual Studio 中的 Tools → Coverage Studio 或 Ctrl+Shift+U, T —— 列出它们,最繁忙的在前,并在你选中的那个测试在工作区中的定义处打开它。一次 dotnet test 运行说不出哪个测试运行了什么;设置 testRun.perTest,Run Tests with Coverage 就会列出测试类并逐个运行,每份报告标上其类名,这样悬停、CodeLens 和该命令对 .NET 也能作答。该信息按文件保存,因此与一份对测试一无所知的报告合并后仍会保留。
{
"fuciniCoverage.testRun.perTest": true
}
# .coveragerc
[run]
dynamic_context = test_function
coverage run -m pytest && coverage json --show-contexts
Screenshot pending — add assets/img/tests-per-line.gif (see SCREENSHOTS.md).
Screenshot pending — add assets/img/tests-for-line.gif (see SCREENSHOTS.md).
Quote.cs 第 62 行执行 Show Tests That Ran This Line —— 运行过它的测试,以及打开该测试的 Go to Test。标记上的通过与失败
一行已覆盖的代码,无论它的测试通过与否,看起来都一样。当报告说明了哪些测试运行了某一行,并且运行器的结果文件说明了这些测试的结局时,失败的测试运行过的行会在行首得到一条红色条,并在概览标尺上得到一条条纹;它的悬停会列出失败的测试,Show Tests That Ran This Line 会在每个失败的测试旁写上 failed。测试变红时,该看的就是它走过的代码。结果在每次加载时从每份报告所在的文件夹读取 —— 任何 .trx,以及 JUnit 写入器使用的名称(TEST-*.xml、junit*.xml、test-results*.xml)—— 也从 testResults.paths 读取,其中填写相对于文件夹的路径或通配模式。Run Tests with Coverage 会让 dotnet test 在报告旁生成一个 test-results.trx,因此开启 testRun.perTest 后无需任何设置。testResults.enabled 可关闭读取。
{
"fuciniCoverage.testResults.paths": ["build/test-results/**/*.xml", "reports/junit.xml"]
}
Screenshot pending — add assets/img/pass-fail.gif (see SCREENSHOTS.md).
Screenshot pending — add assets/img/vs-pass-fail.gif (see SCREENSHOTS.md).改动涉及的测试
完整运行要很久才能回答的问题,Run Tests Affected by My Changes… 立刻就能回答:在所有测试中,哪些走过了我刚刚编辑的代码?它向 git 询问工作树相对于 testImpact.baseRef 的差异 —— 默认是 HEAD,即尚未提交的内容;设为 origin/main 则是该分支改动的全部内容 —— 然后列出运行过被改动行的测试,运行改动最多的排在最前。选中一个测试即可打开它。如果报告来自逐测试运行(testRun.perTest),第一个条目只运行这些测试,每个类写入各自的报告,因此其他各类的报告保持不变,总计仍是整个套件的总计。只运行了某处插入或删除下方的行的测试也会列出:否则它的报告指向的就是错误的行。
Screenshot pending — add assets/img/test-impact.gif (see SCREENSHOTS.md).
Screenshot pending — add assets/img/vs-test-impact.gif (see SCREENSHOTS.md).仍能运行每一行的最少测试
Show Minimal Test Set… 根据同一份逐测试的行数据回答两个问题:哪几个测试仍能运行套件所运行的每一行 —— 用于提交前的快速运行 —— 以及哪些测试运行的行全都已被别的测试运行过。被选中的测试排在前面,每个都附有它新增的行和只有它运行的行;然后是其余的测试,每个都标明没有新增任何行。选择采用贪心法 —— 先选运行行数最多的测试,再选在此基础上新增最多的测试 —— 因此结果接近最小集合,并且每次都相同。按行来看冗余,不等于按测试所检查的内容来看也冗余:一个没有新增任何行的测试,仍可能是唯一对这些行上的某个值做断言的测试。这份列表告诉你精简套件时该看哪里,而不是该删什么。
Screenshot pending — add assets/img/minimal-test-set.png (see SCREENSHOTS.md).
Screenshot pending — add assets/img/vs-minimal-test-set.png (see SCREENSHOTS.md).运行这个测试,带覆盖率
把光标放在一个测试里,运行 Run This Test with Coverage —— Command Palette、编辑器的右键菜单、在 Visual Studio 中的 Tools → Coverage Studio 或 Ctrl+Shift+U, R —— 只有那个测试会开启覆盖率运行:光标所在的测试方法,或当光标位于方法之间时的测试类或 describe 块,或当光标位于所有类之上时的整个文件。光标下是什么是从文本中读出来的,因此无论安装了哪种语言服务,它都适用于 C#、VB 和 F#([Fact]、[Test]、[TestMethod] 等)、JavaScript 和 TypeScript(it、test、describe)以及 Python(def test_…)。文件夹的配方被收窄到它 —— dotnet test --filter "FullyQualifiedName~…"、jest 或 vitest <file> -t "…"、一个 pytest 节点 id —— 它写出的内容像任何运行一样被加载;无法收窄的运行器会如实说明,Run Tests with Coverage 则运行全部测试。
Screenshot pending — add assets/img/run-this-test.gif (see SCREENSHOTS.md).
[Fact] 中执行 Run This Test with Coverage —— 这次运行出现在 Tracefiles 列表中,方法上方的行显示代码由一个测试运行。Go 和 Rust。在 Go 文件中,光标所在的 func TestXxx(或 Fuzz…、Example…)单独运行,在它自己的包中并按其确切名称 —— go test ./pkg/calc -run "^TestTotal$" —— 光标不在测试内时,则运行该文件所在的包。在 Rust 文件中,#[test] 函数按它在 crate 中的路径并带 --exact 运行;光标位于某个 mod 中而不在任何测试内时,运行该模块的测试;tests/ 下的集成测试在它自己的二进制文件中运行(--test api)。运行器只会收到它自己语言的文件:Go 文件夹里的 TypeScript 文件得到的是一句说明,而不是一次空运行。
每次保存后的覆盖率
开启 testRun.onSave 后,保存一个源文件会在片刻之后开启覆盖率运行覆盖它的测试,装订线随之更新。对 .NET 解决方案,会通过 .csproj 图找到被保存文件所在的项目,并只运行引用它的测试项目 —— 直接引用,或经由其他项目间接引用 —— 每个一次 dotnet test <project>;其他任何运行器运行其整个配方;不属于任何项目、也没有任何报告测量的文件则什么都不运行。连续多次保存算作一次运行,正在进行的运行不会被打断。其余部分与 Run Tests with Coverage 一样:终端、Output 窗格、加载的报告。默认关闭 —— 这是一个要自己选择的习惯,而不是一个等着被发现的默认值。
{
"fuciniCoverage.testRun.onSave": true
}
Screenshot pending — add assets/img/coverage-on-save.gif (see SCREENSHOTS.md).
testRun.onSave —— 保存 Quote.cs,测试自行运行,方法上方的那一行随之变化。应用本身,带覆盖率
测试运行说明的是测试到达了哪里。人到达了哪里 —— 发布前把各个界面点一遍,重现客户的操作步骤 —— 同样是覆盖率,Run App with Coverage… 负责收集它。文件夹中的 .NET 应用 —— Exe、WinExe、Web 项目或 Worker 项目,但绝不是测试项目;有多个时由你选择 —— 会在 Microsoft 的 dotnet-coverage 之下、在它自己的控制台中启动,这样控制台应用就可以接受输入,并一直运行到你关闭它为止。你走过的部分随后像任何运行一样被加载,在覆盖率会话中则叠加到测试运行过的内容之上。应用以何种方式结束,不影响这次运行。该工具需要安装一次:dotnet tool install --global dotnet-coverage。
Screenshot pending — add assets/img/run-app.gif (see SCREENSHOTS.md).
Screenshot pending — add assets/img/vs-run-app.gif (see SCREENSHOTS.md).变异体及其所在位置
变异测试工具——面向 Java 的 PIT,面向 JavaScript、C# 和 Scala 的 Stryker,面向 C 和 C++ 的 Mull——会对您的代码做出细小的改动,并针对每处改动运行测试。没有任何测试察觉到的改动就是一个存活的变异体:代码执行了,却没有任何东西检查它做了什么。变异分数说明有多少变异体被捕获;自 0.2026.83 起,编辑器会指出其余的变异体在哪里。含有存活变异体的行在行尾带有一条注记(⚑ 2 mutants survived);如果报告指明了被改动的文本(Stryker 和 Mull 会指明),该文本会加下划线;概览标尺上会有一条条纹;悬停时会显示改动了什么、有多少测试执行了它,以及对于已被捕获的变异体,是哪个测试捕获了它们。
Screenshot pending — add assets/img/mutant-hover.png (see SCREENSHOTS.md).存活表示测试执行了被改动的代码并且全部通过。未测试(报告中的 no coverage)表示根本没有测试执行该代码,因此不可能有任何东西察觉到。已被捕获的变异体只在悬停时计数,从不标记;而那些从来算不上公平测试的变异体——例如无法编译的变异体——不计入分数。带注释的源代码在每个此类行下方有一行说明,并提供 Next Mutant 按钮(U);HTML 报告中有一个 Surviving Mutants 部分,可链接到相应的行。无需开启任何设置,没有变异报告的项目也不会看到这些内容。
Screenshot pending — add assets/img/mutants.gif (see SCREENSHOTS.md).测试能察觉吗?在光标处运行变异测试
整个项目的变异测试要跑一小时,一个函数只要一分钟。Run Mutation Tests Here 跑的就是这一分钟:让文件夹的变异测试运行器去变异光标所在的函数 —— 或选中的行,或整个文件 —— 并把它的报告加载到屏幕上的覆盖率旁边,存活的变异体会在那里被标记。对 C# 使用 Stryker.NET(dotnet tool install --global dotnet-stryker),向它指定字符范围以及引用该文件所在项目的测试项目;对 JavaScript 和 TypeScript 使用 StrykerJS,向它指定行;对 Java 使用搭配 Maven 的 PIT,向它指定类。运行器以何种方式结束,不影响这次运行:分数低于阈值只是一个退出代码,报告照样会有。运行器什么都没写出时会有提示,并说明需要安装什么。
Screenshot pending — add assets/img/mutation-here.gif (see SCREENSHOTS.md).
Screenshot pending — add assets/img/vs-mutation-here.gif (see SCREENSHOTS.md).VS Code 自己的 Test Coverage 视图
在 testCoverage.publish 开启(默认)的情况下,每次加载的运行也会送往编辑器原生的 Test Coverage 视图——Test Explorer 的覆盖率面板、它放在 Explorer 中的逐文件百分比,以及编辑器自己的 Show Inline Coverage 开关——以行、分支臂和函数的形式,详情按需加载,无论它们来自哪个跟踪文件,也不涉及任何测试适配器。Show in Test Coverage View 会发布活动运行并打开该面板,不论设置如何;状态栏菜单通过 quickActions.showTestCoverage 提供它。关闭时,Test Explorer 中根本不会出现 Fucini Coverage 条目。当跟踪文件为其测试命名时(lcov 的 TN: 段),每个测试都会出现在 Test Explorer 的 Fucini Coverage 下,视图也可以按测试筛选文件的覆盖率。
Screenshot pending — add assets/img/test-coverage-view.png (see SCREENSHOTS.md).Problems 面板中的未覆盖行
默认关闭,因为它天生就嘈杂,而且只针对已打开的文档——两千个文件的运行不会淹没面板。独立于 problems.enabled,后者保持每个文件一条:
{
"fuciniCoverage.problems.perLine": "uncoveredAndPartial"
}
Screenshot pending — add assets/img/problems-panel.png (see SCREENSHOTS.md).9. 差异覆盖率
将装饰限制为相对于 git 基准引用发生更改的行 —— 非常适合精确审查某个 PR 覆盖了什么、遗漏了什么:
{
"fuciniCoverage.gitDiff.enabled": true,
"fuciniCoverage.gitDiff.baseRef": "origin/main"
}
Screenshot pending — add assets/img/diff-coverage.png (see SCREENSHOTS.md).在 Visual Studio 中。同样的两项设置,从同样的文件读取:扩展会为本次运行的每个文件自行执行 git diff <baseRef>,并把运行缩减到您的分支改动的行 —— 在边栏中、在 Coverage 窗口中以及在门槛中。当 git 什么也不返回时,Output 窗格会说明 git 到底回答了什么。
新代码的门槛
threshold.newCode 是一道独立的门槛,与整个运行的门槛并列:相对于 gitDiff.baseRef 改动的行中,可测量的那些至少要有多大百分比运行过。总体只有 62% 的项目,仍然可以要求今天写下的代码经过测试。它在每次加载时基于整个运行计算,而整个运行仍留在屏幕上 —— gitDiff.enabled 是另一回事:用改动的行取代整个运行。Check Coverage Thresholds 和 Problems 中的合规条目会用单独的一句话报告它未通过:New code 72.00% < 80% (18 of 25 line(s) changed against origin/main ran)。没有任何可测量行的改动 —— 注释、README —— 不会导致任何失败。
{
"fuciniCoverage.threshold.newCode": 80,
"fuciniCoverage.gitDiff.baseRef": "origin/main"
}
Screenshot pending — add assets/img/new-code-gate.png (see SCREENSHOTS.md).
Screenshot pending — add assets/img/vs-new-code-gate.png (see SCREENSHOTS.md).按标签、按作者、按日期界定新代码
默认情况下,新代码是工作树相对于 gitDiff.baseRef 改动的行 —— 它可以是分支、提交或标签:"v1.4.0" 就是自那次发布以来的全部内容。还有两项设置可以提出引用无法表达的问题。gitDiff.author 接受作者姓名的一部分,此时新代码就是 git 归于该作者的行;gitDiff.since 接受一个日期,此时新代码是最后一次改动发生在该日或之后的行,无论它们经由哪个分支进入;两者同时设置,则是该作者自该日以来的行。它们从 git blame 中读出,一次一个文件 —— 大型运行在每次加载时都会感受到这一点 —— 并在差异覆盖率和上述门槛中取代差异。尚未提交的行在两种情况下都计入。
{
"fuciniCoverage.gitDiff.author": "Ada",
"fuciniCoverage.gitDiff.since": "2026-09-01"
}
Screenshot pending — add assets/img/new-code-author.png (see SCREENSHOTS.md).间接变化:没人碰过却变了的地方
差异说明哪些行改了;两次运行说明哪些行不再运行或开始运行。值得一看的是两者不一致的那些行:没人编辑过却不再运行的代码,原因可能是某个测试被删除、上游的某个分支现在提前返回、某个夹具变了。审查差异时看不到它们 —— 按定义它们就在差异之外。Show Indirect Coverage Changes… 按文件列出它们,损失最多的文件排在最前,并在你选中的文件的第一处这样的行打开它。如果 baseline.report 指定了 CI 基线,运行就与它比对 —— 拉取请求正是以此为准来评判的 —— 否则与前一次运行比对;工作树相对于 gitDiff.baseRef 改动的行,以及该改动使文件行号移位之处以下的全部内容,都不计入。
Screenshot pending — add assets/img/indirect-changes.png (see SCREENSHOTS.md).
Screenshot pending — add assets/img/vs-indirect-changes.png (see SCREENSHOTS.md).10. 综合评分
该扩展不是报告六个独立数字,而是将它们汇总为单一的加权调和平均值(WHM)。对于百分比为 pᵢ、权重为 wᵢ 的各项指标:
WHM = ( Σ wᵢ ) / ( Σ wᵢ / pᵢ )
调和平均值由其中最小的项主导,因此薄弱指标无法被完美指标所掩盖 —— 这正是安全门控所需要的特性。默认权重:
| 指标 | 权重 | 理由 |
|---|---|---|
| MC/DC | 4 | IEC 61508 SIL 3–4 / ISO 26262 ASIL C–D / DO-178C DAL A–B. |
| 分支 | 2 | IEC 61508 SIL 2+ / ISO 26262 ASIL B+. |
| 条件 | 3 | 介于分支与 MC/DC 之间(DO-178C、ISO 26262)。 |
| 区域 | 1.5 | 语句内部路径(例如部分短路求值)。 |
| 行 | 1 | IEC 61508 SIL 1+ / ISO 26262 ASIL A+. |
| 函数 | 1 | 死代码检查。 |
| 实例化 | 0.5 | C++ 模板覆盖率;范围最窄。 |
| 函数调用 | 1 | 已执行的调用点(gcov、VectorCAST、Testwell CTC++、TRACE32)。 |
| 目标代码 | 0 | 除非为其指定权重,否则关闭:它已涵盖语句覆盖率。 |
| 变异得分 | 0 | 除非为其指定权重,否则关闭:它不是覆盖率。 |
将任意权重设置为 0 即可将该指标从评分中剔除。数据中不存在的指标(例如普通 lcov 运行中的 MC/DC)会被直接省略。
在您另行决定之前,目标代码和变异得分的权重均为 0(whmWeights.objectCode、whmWeights.mutation)。目标代码覆盖率已涵盖语句覆盖率,因此同时为两者加权会把同一份证据计算两次;变异得分则根本不是覆盖率——一个测试套件可以运行每一行代码,却什么也检测不出来。不过,两者仍各有自己的列和自己的阈值。
条件覆盖率
a && b 是一个判定——两个分支——同时也是两个条件、四种结果。一个测试套件可以走过两个分支,却从未见过 b 为假:此时分支覆盖率是完整的,而条件覆盖率是四分之三。在 DO-178C 和 ISO 26262 中,这一级别介于分支覆盖率与 MC/DC 之间;自 0.2026.83 起,它成为一项独立的指标,而不再并入分支:状态栏卡片中的一行,Coverage 视图、报告和导出中的一列,评分中的权重 3(whmWeights.conditions),以及一个门控(threshold.conditions;在 standard 配置文件下不设门控,在 safety 下为 100)。
数据读取自 BullseyeCoverage 的 condition 探针、TRACE32 的 cond 导出,以及使用 -fcondition-coverage 构建的 GCC 14。不区分条件与判定的报告没有这一行、这一列,也没有这项权重。如果您使用 Bullseye 或 TRACE32:条件过去被计为分支,因此您的分支百分比会随本次发布而变化,而新的权重也会使您的综合评分发生变化;若要得到与以前相同的评分,请将 whmWeights.conditions 设为 0。
GCC 14,逐行显示。-fcondition-coverage 让 gcov 说明哪个条件缺少哪种结果 —— 条件 1 从未为真,条件 0 真假都未出现 —— 每个条件都在其所在行上标记,并在悬停中列出,数据来自 gcov 的 JSON 及其文本清单(condition outcomes covered 3/6)。这项指标与其他指标出现在同样的地方:threshold.conditions 为它设门槛,运行没有这项数据时不作评判;Copy Coverage Summary 有 Conditions 列;badge.metric 接受 conditions;搜索框接受 conditions < 80。函数的分支数据不包含其条件的结果,文件和运行的分支数据也是如此。
Screenshot pending — add assets/img/gcc-conditions.png (see SCREENSHOTS.md).复杂度与 CRAP
有些报告包含每个函数的圈复杂度:OpenCover(即 Coverlet 输出的格式)、JaCoCo、Clover 和 Cobertura。在这种情况下,扩展会根据该复杂度以及它自己度量的覆盖率,为函数计算一个 CRAP 分数——Change Risk Anti-Patterns:
CRAP = complexity² × (1 − coverage)³ + complexity
一个简单且经过充分测试的函数,其分数就等于自身的复杂度,不会更高;而一个复杂且几乎未经测试的函数,其分数可达数百。在演示项目的 C# 示例中,Price() 的复杂度为 22,约 37% 的行被覆盖,得分为 142.2;而 Shipping() 的复杂度为 12 且被完全覆盖,得分为 12。惯用的界线是 30:达到或超过该值的函数会列在 HTML 报告的 Riskiest Methods 下,最差的排在最前。
复杂度和分数显示在函数的 CodeLens 上,以及带注释源代码中函数的旁边。Max CRAP 是文件中所有函数的最高分数——是最差的那个,而不是平均值——它是 Coverage 视图、HTML 报告以及 CSV 和 Markdown 导出中的一列。它只是参考信息:没有阈值,在综合评分中也没有权重。不包含复杂度的报告(lcov、gcov、Istanbul、coverage.py)没有 CRAP,也不会出现空列。
自 0.2026.83 起,C 和 C++ 也支持该指标。llvm-cov 不报告复杂度,因此复杂度是根据报告中已有的分支数据计算的:函数自身所在文件中写出的条件数再加一——这是 McCabe 的定义,也是面向 .NET 和 Java 的源代码级工具所统计的内容。由其他文件中的宏展开而来的条件不计入,例如每一个 assert。
Screenshot pending — add assets/img/riskiest-methods.png (see SCREENSHOTS.md).Risk hotspots,以及显示哪些文件
在 Coverage 视图的树下方,Risk hotspots 列出复杂代码与低覆盖率相遇的函数,最差的在前,每一项都会在该函数处打开其文件。函数依据 CRAP 分数入选 —— hotspots.crapThreshold,默认为 30,即该指标的提出者所建议的数值 —— 或仅依据圈复杂度入选,hotspots.complexityThreshold,默认关闭;只有携带复杂度的报告(OpenCover、Coverlet 的 Cobertura、JaCoCo、llvm-cov)才会有热点。Community 看到数量和最差分数;排序后的列表属于 Pro。排序旁边的第二个下拉框指定树显示哪些文件 —— 全部、有缺口的文件、未测试的文件,或自上次运行以来更改过的文件;文件夹跟随其文件并保留自己的数字。两者在 Visual Studio 的 Coverage 窗口中同样存在。
Screenshot pending — add assets/img/vs-hotspots.png (see SCREENSHOTS.md).命名空间、类和方法
对于写明其类名的 .NET 报告 —— Cobertura 和 Coverlet 的 coverage.cobertura.xml、OpenCover —— 第三个下拉框 Folders / Namespaces 把同一次运行画成命名空间、类和方法:命名空间的数字是其各类之和,类的数字是其各方法之和,正如文件夹的数字是其各文件之和,排序和筛选也相同。方法行在方法处打开文件;未声明在任何命名空间中的类位于 (no namespace) 下。选择写入 coverageView.perspective;不携带类名的运行没有下拉框,显示其文件夹。在 Visual Studio 中,Coverage 窗口有同样的下拉框。
Screenshot pending — add assets/img/namespace-perspective.png (see SCREENSHOTS.md).
搜索树
按钮和下拉框下方有一个搜索框。一个词保留名称或路径中含有它的行 —— 输入的每个词都必须出现;一个比较式保留数字通过它的行 —— < 80% 针对分数、lines >= 90、branches = 0、functions != 100、mcdc < 50 —— 而 invoice < 80 两者都要。命名空间视角的方法行和风险热点与文件一起被搜索,所以函数会在运行记录它的位置被找到。树随着你的输入在页面内筛选,无需重绘;含有匹配项的文件夹会为它展开,搜索框清空后再收起;Esc 清空搜索框。在 Visual Studio 中是同一个搜索框、同一套语法。
Screenshot pending — add assets/img/tree-search.gif (see SCREENSHOTS.md).lines < 80。树跟随编辑器
你正在查看的文件会在 Coverage 视图中高亮,它上方的文件夹会展开,它所在的行会滚动到可见位置 —— 不必再在树中寻找已经打开的文件。除此之外,树保持原有的滚动位置,也不会重绘任何内容。coverageView.followActiveEditor 默认开启;想让树留在自己放置的位置的人可以用它关闭此行为。
Screenshot pending — add assets/img/follow-editor.gif (see SCREENSHOTS.md).
Screenshot pending — add assets/img/vs-follow-editor.gif (see SCREENSHOTS.md).11. 合规性门控
COMPLIANT / NON-COMPLIANT 判定(显示在带注释的源代码视图、HTML 报告以及两处状态栏悬停提示中)由每个指标的阈值和一个模式共同决定:
Screenshot pending — add assets/img/statusbar.png (see SCREENSHOTS.md).| 模式 | COMPLIANT 的条件… |
|---|---|
strict (default) | 每个单独设限的指标都达到各自的阈值 —— 没有折衷。 |
whm | 单一加权综合评分达到 threshold.weighted。 |
both | 同时满足加权门槛与逐指标的严格门槛。 |
either | 满足加权门槛或逐指标严格门槛之一(最宽松)。 |
将阈值设为 0 即可关闭该门控,而报告未包含的指标永远不会被评判。您未设置的阈值代表什么,由下文的配置文件决定。运行 Fucini Coverage: Check Coverage Thresholds 可按需进行评估。
在 Visual Studio 中。Tools → Coverage Studio → Check Coverage Thresholds 依据同样的 threshold.* 键评判本次运行,并在状态栏和 Output 窗格中给出结论;有了 compliance.diagnostics、problems.enabled、problems.perLine 或 deadCode.enabled,这些发现会与编译器自己的条目一起出现在 Error List 中 —— 每个低于门槛的文件一条,每个未覆盖范围一条,每个从未运行的函数一条 —— 每条都会在对应行处打开其文件。
Screenshot pending — add assets/img/vs-error-list.png (see SCREENSHOTS.md).阈值配置文件
threshold.profile 决定您未设置的阈值代表什么。standard 自 0.2026.82 起成为默认值,它是对一个与安全无关的普通项目可以提出的要求。safety 则将每个门控都设为 100——这是安全论证所要求的,也正是 0.2026.81 及之前版本的出厂行为。
| 指标 | standard | safety |
|---|---|---|
| 行 | 80 | 100 |
| 函数 | 80 | 100 |
| 区域 | 75 | 100 |
| 分支 | 70 | 100 |
| 综合评分 | 75 | 100 |
| MC/DC、实例化、函数调用、目标代码 | 无门控 | 100 |
| 条件 | 无门控 | 100 |
| 变异得分 | 无门控 | 无门控 |
您自行设置的阈值优先于任一配置文件。该示例将分支的门控设为 90,其余全部设为 100;如果只想让部分指标保持 100,请继续使用 standard,并自行设置这些指标。由于设置的工作方式,有一点需要注意:与出厂默认值相等的值无法与未设置值区分开来,因此在 safety 下,一个恰好写成其标准数值的阈值——例如行的 80——仍会被视为 100。
{
"fuciniCoverage.threshold.profile": "safety",
"fuciniCoverage.threshold.branch": 90
}
Screenshot pending — add assets/img/threshold-profile.png (see SCREENSHOTS.md).
Screenshot pending — add assets/img/threshold-profiles.gif (see SCREENSHOTS.md).如果您依赖的是旧的默认值,那么在更新到 0.2026.82 之后、选择 safety 之前,您将受到更宽松的评判。此前已运行过的安装会收到一次相关提示,并附带一个 Keep 100% (safety) 按钮,用于将该配置文件写入您的用户设置;对于共享项目,请同时将其写入工作区设置,以便所有人都按同一标准评判。Configure Coverage Compliance & Thresholds... 在阈值网格上方提供了配置文件选项,其实时预览会按实际生效的门控进行评判。在每张卡片和每份报告中,门控已关闭的指标会以中性的短横线显示其数值,而不是对勾或叉号。
Screenshot pending — add assets/img/statusbar-metrics.png (see SCREENSHOTS.md).11a. 目标代码覆盖率
这里的其他每一项指标都是从源代码测得的:工具对代码插桩,插桩后的构建运行,计数器告诉你哪些语句、分支或条件被执行过。目标代码覆盖率测量的是另一回事——构建出的二进制文件中究竟有多少真正执行过,以指令字节计,靠观测指令流而非对源码插桩。这很重要,因为编译器会产出源码中任何一行都无法交代的代码:数组边界检查、switch 跳转表、寄存器溢出、栈探测,以及在缺少相应指令的内核上为除法或浮点调用运行时辅助例程。它也是这里唯一能回答这覆盖到汇编了吗?的指标——手写汇编从不被插桩,基于源码的工具够不到它。DO-178C 明确说明了原因:在 A 级,当编译器或链接器生成无法直接追溯到源语句的代码时,目标 6.4.4.2b 要求对这部分生成代码进行额外验证。
它包含语句覆盖率——每一条被执行的机器指令都来自某条语句,因此 100% 的目标代码覆盖率意味着 100% 的语句覆盖率。反过来则不成立,而这种不对称正是测量它的全部意义。由此有两点。它在综合得分中不占任何权重,因为把同一批执行的两种度量取平均,等于把同一份证据算了两遍;取而代之的是通过 fuciniCoverage.threshold.objectCode 设门槛——是要求而非权重,这正是标准的要求方式。并且差距本身才是交付物:行覆盖率 100%、目标代码覆盖率不足 100% 的一次运行,恰好定位出了源码无法交代的编译器生成代码,所以合规卡片会说312 字节从未执行,行覆盖率为 100%,而不是把它埋没成“94%”。反过来——源码未跑完却报告整个二进制都跑过了——是矛盾而非低分:几乎总是合并一侧的过期跟踪文件,或者从未与文件路径对上的模块名。这会作为一条发现报告出来。
目标代码覆盖率要求二进制文件在其指令可被观测的地方运行。有三条路径,成本差异极大。只有第三条能产生目标代码覆盖率;列出前两条,是因为它们解决的是一个相关问题,而那往往才是真正值得解决的问题。
- 在目标上运行的插桩构建。 GCC 交叉编译器接受
-fprofile-arcs -ftest-coverage,所以在工作站上可用的插桩在目标上同样可用——只是裸机系统没有文件系统来写.gcda计数器,往往也没有可供写出的exit()。计数器无论如何都在 RAM 中,所以办法是把那块内存转储出来,在目标之外重建这些文件;NASA JPL 的embedded-gcov(Apache-2.0)做的正是这件事,不需要文件系统、操作系统或标准 C 库。这样得到的是:由你的交叉编译器、为你的指令集、在你的硬件上生成的构建的语句、分支与函数覆盖率——不是目标代码覆盖率,而且被测二进制是插桩过的,比你实际发布的更大更慢。结果可作为 LCOV、gcovr JSON、gcov JSON 载入,或者在早于 GCC 9 的工具链上作为纯文本.gcov清单载入。 - 指令集模拟器。 让真正的目标二进制在模拟器中运行,并从执行过的地址推导覆盖率:不需要硬件、不需要插桩、不需要探针,而且因为它观测的是指令,能产出真正的目标代码覆盖率。限制在于保真度——模拟器对内核建模较好,对外设建模较差,因此除非它精确建模了你的芯片,否则对整机结果应持保留态度。
- 片上跟踪加调试器。 认证级的路径,也是唯一真正非侵入的一条:内核的跟踪单元把指令流报告给调试器,由调试器转换成覆盖率。什么都不插桩,代码尺寸不变,时序不受影响——正是这一点,使结果成为关于你真正发布的那个二进制的证据。列预算之前先确认芯片:在许多微控制器系列中,完整跟踪只在带大容量跟踪缓冲和高速跟踪端口的仿真版或开发版上才有,而量产件只提供缩水的跟踪单元,厂商为其准备的退路是一种软件跟踪——它会插桩代码并消耗 CPU 时间,恰恰抹掉了当初选择这条路径的理由。
许多嵌入式项目通过为开发机编译同一份源码并在那里运行单元测试来测量覆盖率。这很快,不需要硬件,对大多数团队而言也是正确的默认做法——但值得说清楚这份证据究竟描述了什么:与实际发布版不同的编译器、不同的优化器、不同的指令集、不同的二进制文件。对大多数软件来说,这是一个可接受且被充分理解的权衡。对于按 IEC 61508、ISO 26262 或 DO-178C 评估的工作,这正是那个标准的「主机对比目标」质疑,而上面的路径正是对它的标准回答——第一条路径为源码覆盖率解决了这个问题,第三条为目标代码解决了这个问题。
目前,为目标代码所读取的导出是 Lauterbach TRACE32 的 COVerage.EXPORT.ListModule(metric="object");像加载其他任何跟踪文件一样加载它。该指标仅在付费许可证下以及评估期内计分并设门槛。社区版安装不会为其计分——但当跟踪文件带有该数据时会得到告知,因为一份悄悄省略了文件中最有力证据的合规判定,会比一份明说自己省略了什么的判定更糟。
12. 排除非代码行
在 excludeComments 开启(默认)的情况下,完全落在纯注释行或空行上的语句会从统计总数中排除 —— 这同时也消除了一个已知的 llvm-cov/gcov 怪癖:即偶发的命中计数落在了注释上。标准标记同样会被遵循:
x = risky(); // LCOV_EXCL_LINE
// LCOV_EXCL_START
debug_only_block();
// LCOV_EXCL_STOP
在 C、C++、Objective-C、CUDA 和 C# 中,预处理指令同样不算作代码:以 # 开头的行——#define、#include、#if——以及由行尾反斜杠延续出的行。gcov 会把宏的每一次使用都计到该宏定义所在的行上,因此一个唯一被报告的行是 #define CHANNEL_COUNT (33U) 的头文件,会因为 .c 文件中的某个循环运行到了它而显示为 100% 覆盖,而一个其常量未被任何地方使用的头文件则显示为 0%。这样的头文件现在没有可测量的内容,也不会被列出。
将 excluded.includeInTotals 设置为 true,可让被排除的行仍计入统计。
从树中排除一个文件或文件夹
在 Coverage 视图中右键点击一个文件或文件夹并选择 Exclude from Coverage —— 它也在该行的路径操作中 —— 或者对打开的文件运行 Exclude This File from Coverage:文件的工作区相对路径,或文件夹的 /**,会被写进文件夹设置中的 fuciniCoverage.ignore,仅一次,重新加载后它就从每个总计和每个页面中消失;这个模式留在那里,随时可以再拿掉。在 Visual Studio 中,行的菜单和 Tools → Coverage Studio 有同样的两条命令,把同一个设置写进同一个文件。
Screenshot pending — add assets/img/exclude-from-tree.gif (see SCREENSHOTS.md).
fees.ts 执行 Exclude This File from Coverage —— 树中不再有它,模式写入 .vscode/settings.json。特性和命名空间,针对 .NET
两个设置从源代码中读出报告说不出的东西。ignore.attributes —— 默认为 ExcludeFromCodeCoverage —— 把位于它所列特性下方的类、方法或属性排除掉,如同 LCOV_EXCL_START/STOP 那样,无论报告是哪个工具写的:Coverlet 遵守该特性,OpenCover、dotCover 和转换而来的 lcov 不遵守,现在它们全都一致了。名称带或不带 Attribute 后缀、带或不带命名空间均可;可以加上 GeneratedCode、Obsolete 或你自己的。ignore.namespaces 排除命名空间声明匹配某个前缀的每个文件,* 为通配符 —— Acme.Generated、Acme.*.Migrations、*Tests —— Output 窗格会列出它排除的文件。两者都在读取注释标记的同一遍中读取,两个版本皆然。
{
"fuciniCoverage.ignore.attributes": ["ExcludeFromCodeCoverage", "GeneratedCode"],
"fuciniCoverage.ignore.namespaces": ["Acme.Generated", "*.Migrations"]
}
从编辑器中排除
选中几行,从灯泡、编辑器的右键菜单或 Command Palette 中选择 Exclude Selection from Coverage —— 在 Visual Studio 中是灯泡(Ctrl+.)、代码窗口的右键菜单、Tools → Coverage Studio 或 Ctrl+Shift+U, X —— 排除就会写在代码所在处:C#、VB 或 F# 的类、方法或属性上方的 [ExcludeFromCodeCoverage],文件已导入 System.Diagnostics.CodeAnalysis 时用短名称,未导入时完全限定,因而无需添加任何 using;单行代码末尾的 LCOV_EXCL_LINE;多行两侧、按第一行缩进的 LCOV_EXCL_START 和 LCOV_EXCL_STOP —— 用该语言自己的注释符号,//、#、'、-- 或 ;。一步即可撤销。在 Problems 条目标记为未覆盖的行上,灯泡也提供它。已经标记过的行,或已经带有该特性的成员,保持原样。
Screenshot pending — add assets/img/exclude-selection.gif (see SCREENSHOTS.md).
LCOV_EXCL 标记,然后是方法上方的 [ExcludeFromCodeCoverage]。仅分支,以及 gcovr 的标记
防御性的 else、没有任何值会到达的 default:、编译器为异常添加的分支臂:这一行会运行,也应当计入,只是它的分支无法走到。LCOV_EXCL_BR_LINE 和 LCOV_EXCL_BR_START … LCOV_EXCL_BR_STOP 把所在行上的分支从统计中去掉,而把行留在统计中。LCOV_EXCL_EXCEPTION_BR_LINE 及其区域去掉的更少 —— 只去掉异常才会到达的分支臂,前提是报告说明了哪些是这样的分支臂,gcov 的 JSON 就是如此。每个标记也都能以 gcovr 的前缀读取:GCOVR_EXCL_LINE、GCOVR_EXCL_START、GCOVR_EXCL_BR_LINE、GCOV_EXCL_STOP 等与对应的 LCOV_EXCL_ 标记含义相同,因此为 gcovr 编写的源代码在这里读出的结果相同,无论报告由哪个工具生成。不读取的是:GCOVR_EXCL_FUNCTION —— 那一行上没有任何内容说明函数在哪里结束。
if (handle == NULL) return E_FAIL; // LCOV_EXCL_BR_LINE: the allocator never fails on target
// LCOV_EXCL_BR_START generated state table
switch (state) { /* … */ }
// LCOV_EXCL_BR_STOP
Screenshot pending — add assets/img/branch-markers.png (see SCREENSHOTS.md).说明原因:有依据的排除
把代码从统计中去掉是一种主张 —— “这无法测试”、“这是给开发者用的辅助代码” —— 而在 DO-178C、ISO 26262 或 IEC 61508 之下,凡是会改变认证所依据的数字的主张,都必须有书面记录并可供评审。因此,排除可以在它写下的地方说明原因:LCOV_EXCL_LINE 或 LCOV_EXCL_START 之后的文本,或者 [ExcludeFromCodeCoverage] 的 Justification。
abort(); // LCOV_EXCL_LINE: defensive, unreachable by construction
// LCOV_EXCL_START debug dump, never built for the target
[ExcludeFromCodeCoverage(Justification = "a developer aid")]
public static void DebugDump(decimal amount) { /* … */ }
原因在每次加载时读取,日志会统计其数量。Export Coverage Report… 可写出这份清单 —— exclusions and their justifications,格式为 Markdown 或 CSV:文件、行、书写方式,以及理由或 none given。排除后已无可测量内容的文件仍在这份清单中。开启 exclusions.requireReason 后,没有给出原因的排除就是一个发现项:在其所在行给出 Problems 中的一条警告,并使 Check Coverage Thresholds 失败。此时 Exclude Selection from Coverage 会先询问原因,再把它与标记一起写入,或写成特性的 Justification;如果取消或留空,则不排除任何内容。
Screenshot pending — add assets/img/justified-exclusions.png (see SCREENSHOTS.md).
Screenshot pending — add assets/img/vs-justified-exclusions.png (see SCREENSHOTS.md).12a. 这份覆盖率还描述当前这个文件吗?
从跟踪文件读到的覆盖率,描述的是测试运行时的源码,而大多数格式并不说明那是哪一个版本。因此覆盖率面板中的每个文件都处于以下三种状态之一:
- ✓ 已验证 —— 跟踪文件记录了每个被测行的内容,并且仍与该文件一致。
- 没有标记 —— 未被推翻。既没有东西否定这份覆盖率,也没有东西证实它。这是常态。
- ⚠ 已被推翻 —— 文件本身否定了它的覆盖率。该行会变灰,提示会说明发现了什么。
只有两种格式能拿到这个对勾,因为只有这两种记录了所测源码的任何内容:用 --checksum 写出的 lcov(geninfo 中 默认关闭;其文档中的用途正是防止把同一文件不同版本的覆盖率合并到一起),以及内嵌每个被测行文本的 GNATcoverage XML。把 lcov 的这个选项打开是值得的。
该警告两者都不需要,且适用于所有格式:记录在文件末尾之外的覆盖、不再位于报告所指位置的具名函数,或在文件中为空行的已执行行。仅凭时间戳永远不够 — git checkout 会改变所有时间戳却不改变一个字节 — 因此时间戳变化至多只是再看一眼的理由。检查在加载覆盖率时运行,并在文件每次保存或在磁盘上更改时再次运行,所以标记跟随文件:与运行结果矛盾的编辑会设置它,恢复测量版本的检出会清除它。提示会精确说明证据。将 staleWarning.enabled 设为 false 可关闭全部功能。
12b. 未测量与未覆盖
运行从未提及的文件是未测量的:没有任何测试加载过它,因此没有工具为它写过一行。这与未覆盖——已测量,但命中为零——并不相同,而扩展也从未把一个不存在的文件计为 0%。它直到 0.2026.69 之前没有做的,是把这些不存在的文件显示出来:一个完全没有测试的模块从不出现在树、总数或阈值检查中,读者看到的是一个一半文件从未被执行过的项目的“92%”。
现在,在加载了运行的情况下,对于属于运行自身种类、而运行中没有的源文件,逐文件的状态栏项会显示 not measured——“属于运行的种类”是指它具有运行所测量的文件扩展名之一,因此 C 项目的头文件和源文件是候选,而它的 Python 脚本不是。除此之外的一切都通过 fuciniCoverage.unmeasured.mode 可选开启:
off(默认)——不再有其他变化。list——Coverage 视图在底部得到一个 Unmeasured(未测量)组,列出每个这样的文件,每一行都可打开它;加载结果对它们计数;徽章在 Explorer 中把它们标为?;而fuciniCoverage.threshold.unmeasuredFiles可在数量超过您设定的值时让 Check Coverage Thresholds 失败。任何百分比都不会变动。count——list所做的一切,并且每个未测量的文件还会按其自身代码行数作为一个未覆盖文件计入运行,因此状态栏、阈值、徽章、历史记录、每种导出和 Test Coverage 视图都显示更低的数字。该文件在出现的每个地方都被绘制为未测量,其装订线条说的是“未测量”而不是“从未执行”,不匹配检查也不去碰它——没有任何测量结果可供其内容去抵触。
{
"fuciniCoverage.unmeasured.mode": "list",
"fuciniCoverage.threshold.unmeasuredFiles": 0
}
头文件是 C 中已知的误报:它们具有运行的扩展名家族,而没有任何跟踪文件会提到它们,因为头文件本身不会编译出任何东西。因此 fuciniCoverage.unmeasured.exclude 默认排除 **/*.h、**/*.hpp、**/*.hh、**/*.inl 和 **/*.d.ts,采用与 fuciniCoverage.ignore 相同的 glob 语法;加入一个不在此处构建的平台移植,或生成的桩代码,它们就会从列表、计数和状态栏状态中消失。ignore 提到的文件也永远不会被称为未测量——把它排除在外本来就是目的。threshold.unmeasuredFiles 默认为 -1,即不检查;0 在运行所属种类的任何文件从未被测试加载时立即失败,并且它只在 unmeasured.mode 为 list 或 count 时生效。
13. HTML 报告
- Generate Full HTML Report —— 按目录自行生成的多页报告。输出至
htmlReport.outputDir,若未设置则输出到扩展的全局存储目录。 - Open Annotated Source —— 单个文件的带注释视图。将
externalHtmlReport.dir指向预先构建好的llvm-cov show --format=html目录,即可使用这些页面而非内置渲染器。 - Export Fucini JSON —— 供您自己工具使用的完整规范化模型。
- Export Coverage Report... — 将运行导出为 CSV(按文件或按函数)、Markdown 摘要、LCOV 跟踪文件、Cobertura XML、SARIF,或供 README 使用的自包含 SVG 徽章。也可从 Covered Files 视图中 Generate Full HTML Report 旁边的按钮打开。除 CSV 外一切都是有损的——MC/DC、区域和实例化在 LCOV、Cobertura 或 SARIF 中无处安放——因此承载全部指标的仍是 Fucini JSON 和 CSV。
- Copy Uncovered Lines —— 每个覆盖缺口的 Markdown 摘要,可直接粘贴到 PR 中。
在 Visual Studio 中。Tools → Coverage Studio 的 Reports 组:Show Annotated Source 把当前文档作为一个页面在文档区中打开 —— 每一行都带着自己的颜色,发现列在旁边 —— 并与编辑器双向同步滚动;Open HTML Report 把单页报告写入 htmlReport.outputDir(或 %LOCALAPPDATA%\Fucini\CoverageStudio\html-report)并打开浏览器;Generate Full HTML Report 在那里写出每个文件夹的索引和每个文件的页面;Export Coverage Report… 提供同样的八种格式并询问保存位置;Generate Coverage Badge 把徽章写到 badge.outputPath 指定的位置,有了 badge.autoUpdate 则每次加载后都写。带注释的页面和 Coverage 窗口一样需要 WebView2;磁盘上的报告则不需要。
Screenshot pending — add assets/img/vs-annotated-source.png (see SCREENSHOTS.md).
Screenshot pending — add assets/img/report.png (see SCREENSHOTS.md).
Screenshot pending — add assets/img/report-directory-summary.png (see SCREENSHOTS.md).
Screenshot pending — add assets/img/report-file-summary.png (see SCREENSHOTS.md).
Screenshot pending — add assets/img/report-coverage-gaps.png (see SCREENSHOTS.md).
Screenshot pending — add assets/img/report-mcdc-detail.png (see SCREENSHOTS.md).
Screenshot pending — add assets/img/export-picker.png (see SCREENSHOTS.md).
Screenshot pending — add assets/img/annotated-source.png (see SCREENSHOTS.md).带注释的源代码与普通源代码
Open Annotated Source 会在同一列中,将当前活动标签页替换为该文件的彩色覆盖率网页视图;Open Annotated Source to the Side 效果相同,但会以拆分方式显示,使源代码与带注释视图并排展示。若想改为原地来回切换——在分屏会让每个面板过窄的小屏幕上很实用——请使用 Toggle Annotated Source:它会在两者之间切换当前活动标签页,并记住您当前正在查看的是哪一个。在带注释视图内部,Show Source 可直接跳回该文件的普通编辑器。
您实际看到的是哪种 HTML 取决于 externalHtmlReport.dir:当该设置已配置,且该目录中已存在与当前活动文件匹配的页面时(llvm-cov 按文件生成页面的方式为 <仓库名>/<相对路径>.html,因此报告自身的仓库名文件夹永远不需要配置),系统会显示该工具链生成的页面,而不是内置渲染器——其本地链接/样式表会被重写以便在网页视图中正常工作,并且它包含内置视图无法呈现的细节(MC/DC 展开视图、区域高亮、目录汇总)。如果该文件没有匹配的页面,或该设置留空呢?那么将改用内置渲染器。扩展只会读取该目录——绝不会写入其中。
供 README 使用的覆盖率徽章
Generate Coverage Badge 为活动运行向工作区写出一个 shields 风格的 SVG——badge.outputPath,默认 coverage-badge.svg,相对于工作区文件夹或绝对路径,变量与其他每个路径设置相同——并提议打开它。它是您仓库中的一个普通文件,因此 README 嵌入它时背后没有任何服务,没有 shields.io 的往返,在隔离网络的仓库中同样可用:

badge.metric 选择徽章显示的内容:lines(标签为 coverage)、branches、mcdc 或 weighted——即状态栏显示的同一综合评分,标签为 score。所选指标没有数据的运行会得到灰色的 n/a,而不是红色的 0%。颜色遵循您自己的 colorScale.* 档位:低于 lowThreshold 用 lowColor,直到 highThreshold 用 midColor,从那里起用 highColor。开启 badge.autoUpdate 后,徽章会在每次成功加载后静默重写,输出通道把每次写入记录为 [FUCINI_BADGE]——默认关闭,因为它会在每次加载时向工作区写入。把 SVG 与代码一起提交,仓库页面上的图片就会跟随编辑器中的数字。
{
"fuciniCoverage.badge.outputPath": "docs/coverage-badge.svg",
"fuciniCoverage.badge.metric": "weighted",
"fuciniCoverage.badge.autoUpdate": true
}
14. 历史记录与趋势
启用 history.enabled 可在本地持久化保存运行记录(保留 history.retention 条)。之后您将在状态栏中获得趋势箭头(▲/▼)、Coverage 视图中的趋势图,以及两个比较命令:Compare with Last Run 和 Compare Two History Runs,每个都会生成按文件划分的回归/改进差异。
在 Visual Studio 中。每次加载都会与上一次比较:每一行开始运行或停止运行的地方在边栏中有一个三角形(sinceLastRun.enabled),状态栏的分数旁有趋势,数字变动的文件则有 Compare with Last Run。有了 history.enabled,运行会保存在 %LOCALAPPDATA%\Fucini\CoverageStudio\history 下,保留 history.retention 次,因此比较在重启之后依然存在,趋势图有东西可画,Compare Two History Runs… 可以从中任选两次。
Screenshot pending — add assets/img/history-compare.png (see SCREENSHOTS.md).
Screenshot pending — add assets/img/since-last-run.png (see SCREENSHOTS.md).趋势图
在 Coverage 视图的工具栏下方,当保存了两次或更多运行时,历史的行覆盖率被画成一条折线,从最早到最新,取最新的三十次:每次运行一个点,悬停时显示其日期和数字,旁边是最后一次的数字和一个表示自第一次以来走向的箭头。纵轴的刻度是这些运行自身的范围,所以一个处在 92–94% 的团队看到的是变化,而不是 100 之下的一条平线。两个版本都从同一份历史画出它。
Screenshot pending — add assets/img/trend-chart.png (see SCREENSHOTS.md).
覆盖率会话
一次测试运行很少能覆盖一处改动触及的全部:单元测试在这里跑,集成套件在那里跑,开着覆盖率在 UI 里走一遍又在别处。Start Coverage Session 把随后的各次加载合成一个总和 —— 每次运行逐行以并集方式叠加到前一次之上,任何一次执行过的行即为已覆盖,计数相加 —— 于是装订线、树和分数显示的是它们合起来到达的范围。屏幕上的运行(如果有)是会话的第一次。树上方的一条横条说明自何时起有多少次运行,并可结束会话;End Coverage Session 也是如此,之后下一次加载又独立成篇。在 Visual Studio 中,Tools → Coverage Studio 下的一个条目开始会话,勾选状态下再选它则结束会话。
Screenshot pending — add assets/img/coverage-session.gif (see SCREENSHOTS.md).
CI 基线:我相对于 main 处于何处?
Compare with Last Run 回答的是你上一次改动做了什么;而在提交拉取请求之前,问题是你相对于 main 处于何处,答案在构建服务器上。baseline.report 指定主分支的构建所发布的报告,每次加载都与它比对:状态栏的卡片带有一行差值 —— vs CI baseline: 84.20% → 85.10% (+0.90%), 3 file(s) moved —— Compare with CI Baseline 则逐文件列出变动的文件,措辞与 Compare with Last Run 相同。在 Visual Studio 中,该命令位于 Tools → Coverage Studio 下,在你要求时获取或读取报告。在 .vscode/settings.json 中指定它的三种方式:
- CI 提供的 URL。 GitLab 把分支最近一次作业的工件放在一个稳定的地址上;令牌通过
remote.headers中的${env:…}留在设置文件之外。Jenkins:https://jenkins.example.com/job/app/job/main/lastSuccessfulBuild/artifact/coverage/lcov.info。TeamCity:https://tc.example.com/repository/download/App_Main/.lastSuccessful/coverage/lcov.info,加上Authorization: Bearer ${env:TC_TOKEN}。该 URL 的获取和缓存方式与coverageFile.paths的 URL 完全相同(ETag、remote.refreshMinutes)。 - 流水线复制到共享位置的文件 —— 一个绝对路径,例如
\\build\drops\app\main\coverage.cobertura.xml。 - 已下载的工件,放在文件夹下,比如
ci/main.lcov.info(把该文件夹加进.gitignore)—— 一个相对于文件夹的路径。
{
"fuciniCoverage.baseline.report": "https://gitlab.example.com/api/v4/projects/42/jobs/artifacts/main/raw/coverage/lcov.info?job=test",
"fuciniCoverage.remote.headers": ["PRIVATE-TOKEN: ${env:GITLAB_TOKEN}"]
}
阅读器认识的任何格式都可以。CI 相对于其检出目录写下的路径 —— 例如 Coverlet 在运行器上写出的 src/Billing/Invoice.cs —— 会在比对之前被放到这个文件夹之下,从而与本地运行解析出的文件对上;来自另一台机器的绝对路径什么都匹配不上,所以请让流水线写出相对路径。无法获取或读取的基线只是 Output 窗格中的一行,绝不会导致加载失败。
Screenshot pending — add assets/img/ci-baseline.gif (see SCREENSHOTS.md).main 变动的文件。
工件是一个 zip,这也能读取。无论里面放了什么,GitHub 和 GitLab 都把工件作为压缩包交付。在 # 之后指明其中的报告:成员按其路径或路径的结尾查找;没有 # 时,取压缩包中唯一的文件,或其中唯一看起来像报告的文件,如果有多个,Output 窗格会列出它们的名称。你自己下载的 zip(ci/artifact.zip#lcov.info)同样如此,在 VS Code 中,coverageFile.paths 里的 URL 也是如此。令牌只发送给为之设置它的服务器,不会再往外传:跳转到存放压缩包的存储时不带令牌。
{
"fuciniCoverage.baseline.report": "https://api.github.com/repos/acme/app/actions/artifacts/1234567/zip#coverage/lcov.info",
"fuciniCoverage.remote.headers": ["Authorization: Bearer ${env:GITHUB_TOKEN}"]
}
14a. 面向 AI 智能体:屏幕上的覆盖率
编写测试的智能体可以运行测试并读到一个百分比。它自己做不到的,是询问编辑器所知道的内容:这个函数的哪些行从未运行、哪个分支的哪一侧没有走到、哪些测试到达了某一行、它刚写的差异是否已被覆盖。六个工具回答的正是这些,用的是智能体可以据以行动的措辞,依据的是已加载的运行 —— 同样的排除项、同样的重映射、与屏幕上同样的数值。因此,受命补上某个函数覆盖缺口的智能体,和看着装订线的审阅者,谈论的是同一批行。
| 工具 | 回答什么 |
|---|---|
coverage_summary | 运行的各项指标总计,以及离完全覆盖最远的文件。应当最先询问的内容。 |
coverage_gaps | 文件、函数或行范围中从未运行的内容:未覆盖的行、未走到的分支臂、缺少配对的 MC/DC 条件、存活的变异体。 |
tests_for_line | 哪些测试运行了某一行,前提是报告有此信息 —— 即逐测试运行。 |
patch_coverage | 一份差异的覆盖率 —— 工作树相对于某个引用的差异,或交给它的补丁 —— 以及其中从未运行的行。 |
coverage_delta | 自前一次运行以来的变动:总计,以及上升或下降的文件。 |
test_run_command | 开启覆盖率运行此工作区测试的命令。由智能体运行它;编辑器会自行重新加载报告。 |
VS Code 自己的智能体无需任何设置即可把它们当作工具使用。其他所有智能体 —— 任何使用 MCP(Model Context Protocol)的客户端 —— 则通过一个服务器访问它们,mcp.enabled 开启时由编辑器运行该服务器,在 VS Code 和 Visual Studio 中都一样。Copy MCP Server Configuration 把客户端的条目放到剪贴板上,格式与 VS Code 和 Visual Studio 在 mcp.json 中读取的一致;外层键名为 mcpServers 的客户端,把同一条目放在该名称下即可。
Screenshot pending — add assets/img/mcp-config.png (see SCREENSHOTS.md).
Screenshot pending — add assets/img/vs-mcp-config.png (see SCREENSHOTS.md).{
"servers": {
"fucini-coverage": {
"type": "http",
"url": "http://127.0.0.1:39217/mcp",
"headers": { "Authorization": "Bearer …" }
}
}
}
- 除非你开启,否则它是关闭的,并且只在
127.0.0.1上监听:本机之外的任何东西都访问不到它。 - 调用必须带上你的令牌,令牌只生成一次并为你保存;没有它,得到的回应是 401。复制出的配置包含令牌:不要让它进入代码仓库。
- 由网页发出的调用 —— 它在自己的
Origin中表明了这一点 —— 无论带着什么都会被拒绝。 - 它读取已加载的运行,不写入任何内容。测试运行只给出命令,不会启动:由智能体在自己的终端中运行该命令。
端口是 mcp.port,未设置时为 39217。如果它已被占用 —— 第二个窗口,或与本版本并排运行的另一个版本 —— 就使用下一个空闲端口,复制出的配置会给出实际可用的那个。Output 窗格会说明服务器在哪里监听([FUCINI_MCP])以及为何无法监听([FUCINI_MCP_001])。方案不包含的内容 —— 测试名称、MC/DC 详情 —— 这些工具只计数而不列出,与悬停的做法相同。
15. 命令参考
在 Visual Studio 中。所有命令都在 Tools → Coverage Studio 下,名称与表中相同但没有 Fucini Coverage: 前缀;Coverage 窗口的工具栏和行的右键菜单包含各自应有的命令,代码窗口的右键菜单则有作用于编辑器的三条:Run This Test with Coverage、Show Tests That Ran This Line 和 Exclude Selection from Coverage。带快捷键的命令是 Ctrl+Shift+U 之后的两键和弦:R 重新加载,N 和 P 转到下一个和上一个未覆盖的行,B 转到下一个部分测试的判定,T 列出运行了该行的测试,X 排除选区 —— 不是 VS Code 的 Alt+N,因为在 Visual Studio 中 Alt 加字母会打开菜单。可在 Tools → Options → Keyboard 中更改,它们列为 Coverage Studio: …。此版本没有的:Test Coverage 面板是 VS Code API —— 本次运行改为显示在 Coverage 窗口中;Solution Explorer 无法显示徽章。只有此版本才有的:Run Native Project with Coverage,用于 C++ 启动项目。
在 Visual Studio 中:各项功能的位置。 Coverage 窗口位于 View → Other Windows → Coverage,也是 Tools → Coverage Studio 的第一项;此版本的每条命令都在那里,分组排列:加载与重新加载、监视文件、两条复制命令和卡片;Run Tests with Coverage、Run Native Project with Coverage 和 Collect Coverage in Test Explorer…;Next 与 Previous Uncovered 和部分测试的判定;着色、执行计数和装订线模式;文件的覆盖率来源、运行了某一行的测试、带覆盖率的单个测试、两种排除方式;Compare with Last Run、Compare Two History Runs…、Compare with CI Baseline、覆盖率会话和 Check Coverage Thresholds;Reports 组 —— Show Annotated Source、Open HTML Report、Generate Full HTML Report、Export Coverage Report… 和 Generate Coverage Badge;然后是 Getting Started、合规阈值以及 Licence…。窗口自己的工具栏有最常用的五项:Load Coverage、Reload、Toggle Coverage Painting、Copy Coverage Summary 和合规阈值;其下是视角、筛选器和排序,以及搜索框;再往下是趋势图。把指针停在树中的某一行并按下它的 ⋯,即可打开该行的菜单 —— 打开文件、打开所在文件夹、复制路径(绝对或相对,两种斜杠皆可)、把它排除在覆盖率之外。状态栏右端显示本次运行的分数和当前文件的分数,以及自上次运行以来的趋势;Show Coverage Card 和 Show Coverage Card for This File 打开其背后的卡片,与 VS Code 在指针停在其状态栏项上时显示的卡片相同。







Screenshot pending — add assets/img/statusbar-and-toolbar.gif (see SCREENSHOTS.md).
Screenshot pending — add assets/img/statusbar-menu.png (see SCREENSHOTS.md).
Screenshot pending — add assets/img/worst-functions.png (see SCREENSHOTS.md).| 命令 | 作用 |
|---|---|
| Load Coverage | 发现并加载工作区的覆盖率。默认 Alt+Shift+R(macOS 上为 Ctrl+Alt+R)。 |
| Pick Coverage File | 选择跟踪文件——一个、多个,或每次再加一个;所选文件会累加、合并并写入 coverageFile.paths,此后自动加载。 |
| Show Loaded Coverage Files | 列出当前运行中的跟踪文件。“覆盖率”视图也会列出它们,每个都带有卸载按钮,旁边还有选择按钮。 |
| Toggle Tracefile Watch | 监视跟踪文件的更改并自动重新加载。 |
| Run Tests with Coverage | 识别测试运行器,在终端任务中开启覆盖率运行,加载其写出的内容,并提议将路径添加到设置。 |
| Run This Test with Coverage | 光标所在的测试 —— 方法、类或 describe 块,或整个文件 —— 用收窄到它的配方运行,然后像任何运行一样加载。也在编辑器的右键菜单中。 |
| Show Tests That Ran This Line | 在报告说明了哪些测试运行了什么的情况下,列出运行了光标所在行的测试,最繁忙的在前,并在你选中的那个测试的定义处打开它。 |
| Run Tests Affected by My Changes… | 列出运行过工作树相对于 testImpact.baseRef 所改动行的测试,打开你选中的那个;如果报告来自逐测试运行,也可以只运行这些测试。 |
| Show Minimal Test Set… | 仍能运行套件所运行的每一行的最少测试,随后是没有新增任何行的测试。 |
| Run App with Coverage… | 在 dotnet-coverage 之下启动文件夹中的 .NET 应用,并在它关闭时加载你走过的部分。 |
| Run Mutation Tests Here | 用 Stryker.NET、StrykerJS 或 PIT 变异光标所在的函数 —— 或选区,或整个文件 —— 并把报告加载到屏幕上的覆盖率旁边。 |
| Exclude This File from Coverage / Exclude from Coverage | 把打开的文件 —— 或者,在 Coverage 视图的某一行上,该文件或该文件夹的 /** —— 写进文件夹设置中的 fuciniCoverage.ignore,仅一次。 |
| Exclude Selection from Coverage | 把排除写在代码所在处:.NET 成员上方的 [ExcludeFromCodeCoverage],单行的 LCOV_EXCL_LINE,多行两侧的 LCOV_EXCL_START/STOP,用该文件自己的注释符号。也在灯泡和编辑器的右键菜单中。 |
| Start Coverage Session / End Coverage Session | 会话开启期间,每次加载都以并集方式叠加到前一次之上;树上方的一条横条统计运行次数,并可结束会话。 |
| Next / Previous Uncovered | 在未覆盖区域之间跳转。默认 Alt+N / Alt+P(macOS 上为 Ctrl+Alt+N / Ctrl+Alt+P)。 |
| Choose Editor Gutter Mode... | 在编辑器装订线中关闭、仅显示未覆盖条、显示红琥珀绿条,或显示命中次数热力图。 |
| Cycle Editor Gutter Mode | 在 off、uncovered、coverage 和 heatmap 之间前进一步——按 Alt+G,或点击状态栏中的模式按钮。 |
| Choose Coverage Source for This File... | 当打开的文件被多个已加载的报告测量时,为该文件、其文件夹或某个模式选择其数值来自哪一个——写入一条 coverageFile.sources 规则。 |
| Clear Coverage Source for This File... | 移除适用于打开文件的 coverageFile.sources 规则。 |
| Toggle Inline Hit Counts | 显示或隐藏每行之后的 ×N 执行次数——切换 hitCounts.inline。 |
| Show in Test Coverage View | 将活动运行发布到 VS Code 自己的 Test Coverage 面板(即使 testCoverage.publish 已关闭)并打开它。 |
| Suggest Path Remap Rule | 推断能把活动运行中未匹配的文件对应到工作区的 pathRemap 规则,并提议添加它。 |
| What Community Includes | 免费层级的每项上限和被保留的功能汇总为一份清单,并显示各上限的已用量。 |
| Open HTML Report | 打开已生成的静态报告。 |
| Generate Full HTML Report | 构建多页报告。 |
| Open Annotated Source / …to the Side | 文件的带注释覆盖率视图。 |
| Toggle Annotated Source | 在源码与带注释视图之间切换。默认 Alt+A(macOS 上为 Ctrl+Alt+A)。 |
| Show Worst-Covered Files / Folders / Functions | 按从低到高排序的 Quick Pick。 |
| Copy Uncovered Lines | 所有未覆盖范围的 Markdown 摘要。 |
| Compare Two History Runs / with Last Run | 按文件的回归差异 —— 历史中保存的两次运行,或屏幕上的运行与它所替代的那一次。 |
| Compare with CI Baseline | 已加载的运行与 baseline.report 指定的报告 —— 来自 CI 的主分支报告,一个 URL 或一个已下载的工件 —— 逐文件比对。 |
| Show Indirect Coverage Changes… | 没人编辑过、覆盖率却变了的行,相对于 CI 基线或前一次运行,按文件列出。 |
| Check Coverage Thresholds | 评估合规门槛。 |
| Export Fucini JSON | 将当前运行导出为 Fucini JSON。 |
| Export Coverage Report... | 选择一种格式——CSV、Markdown、LCOV、Cobertura XML、SARIF 或 SVG 徽章——以及保存位置。 |
| Generate Coverage Badge | 将活动运行的 SVG 徽章写入 badge.outputPath,显示 badge.metric,并提议打开它。 |
| Copy MCP Server Configuration | 把 MCP 客户端询问屏幕上的覆盖率所需的内容 —— 服务器地址和你的令牌 —— 放到剪贴板上;服务器关闭时,提议开启它。 |
| Show Status Bar Menu | 打开快捷操作菜单。 |
| Browse for External HTML Report / Full Report Output Dir… | 通过选择器设置报告目录。 |
16. 设置参考
完整、分类的设置表格 —— 包含每个选项及其默认值 —— 位于功能与设置页面。所有键均以 fuciniCoverage. 为前缀。
值得一提:fuciniCoverage.language 会将此扩展自身运行时界面(状态栏、通知、工具提示、Web 视图)固定为特定语言,与 VS Code 的显示语言无关——默认值为 auto,并随附全部 18 种受支持语言的语言包。它不会更改命令面板条目或设置界面的标签,这些仍由 VS Code 根据自身显示语言解析。
在 Visual Studio 中。同样的 fuciniCoverage.* 键,从最多四个文件读取,后者优先:如果机器上有 VS Code,则读取 VS Code 自己的用户设置 —— 只读不写,这样您的颜色和阈值一并带过来;%APPDATA%\Fucini\CoverageStudio\settings.json,本版本的用户设置;文件夹中或解决方案旁的 .vscode\settings.json,因此为 VS Code 配置的仓库对 Visual Studio 同样有效;以及解决方案根目录下的 fucini-coverage.json,供不愿在 .NET 仓库中保留 .vscode 文件夹的团队使用 —— VS Code 版本同样读取它。Coverage 窗口工具栏上的齿轮打开一个设置窗口,按其 Write changes to 选择器写入用户文件或文件夹中已存在的那个文件,并保留所编辑文件中的注释。fuciniCoverage.language 在那里同样有效;菜单遵循 Visual Studio 自身的语言。
17. 错误代码
诊断信息会输出到 Fucini Coverage 输出通道(View → Output → Fucini Coverage),每条都带有稳定的 FUCINI_* 代码。
在 Visual Studio 中。同样的代码会进入输出窗口的 Fucini Coverage 窗格(View → Output),并保存在 %LOCALAPPDATA%\Fucini\CoverageStudio\coverage-studio.log 中,以便回答 IDE 关闭之后才提出的问题。常见的三种:窗格说文件已加载但什么也没标记 —— 装订线模式为 off,请选择 Show Coverage in Editor;FUCINI_REMAP_001 和一个信息栏 —— 报告中的路径在这里不存在,窗格会建议 pathRemap 规则;Coverage 窗口说它需要 WebView2 —— 安装 Microsoft Edge WebView2 Runtime,编辑器中的标记不需要它。
| 代码 | 含义 |
|---|---|
FUCINI_PARSE_001 | 不受支持或无法识别的覆盖率格式 —— 没有解析器匹配该文件。 |
FUCINI_PARSE_010 | lcov 文件中没有 SF 记录。 |
FUCINI_PARSE_020 | llvm-cov JSON 无效、缺少 data[] 或不包含文件。 |
FUCINI_PARSE_030 | 缺少 Cobertura 根元素 <coverage> 或 XML 无效。 |
FUCINI_PARSE_040 | 缺少 JaCoCo 根元素 <report> 或没有源文件。 |
FUCINI_PARSE_050 | Istanbul/SimpleCov JSON 无效或不包含文件。 |
FUCINI_PARSE_060 | SimpleCov JSON 无效或缺少顶层 files 数组。 |
FUCINI_PARSE_070 | Go 配置文件缺少 mode: 头,或其中没有覆盖率块。 |
FUCINI_PARSE_080 | Clover 缺少根元素 <coverage> 或 <project>,或其中没有文件。 |
FUCINI_PARSE_090 | OpenCover 缺少根元素 <CoverageSession>,或其中没有文件。 |
FUCINI_PARSE_100 | coverage.py 的 JSON 无效,或缺少顶层 files 对象。 |
FUCINI_PARSE_110 | gcov/gcovr 的 JSON 无效,或缺少顶层 files 数组。 |
FUCINI_PARSE_120 | 缺少 <source> 元素的 GNATcoverage XML,或 xi:include 从未被解析的 index.xml —— 请改为加载每个源文件对应的 *.xml。 |
FUCINI_PARSE_130 | 缺少根元素 <BullseyeCoverage>,或其中没有 <src> 元素。 |
FUCINI_PARSE_140 | 缺少根元素 <CoverageDSPriv>、缺少用于解析行记录的 <SourceFileNames> 表,或其中没有行数据。 |
FUCINI_PARSE_150 | Xcode 覆盖率 JSON 无效,或缺少顶层 targets 数组。 |
FUCINI_PARSE_160 | 缺少 SonarQube 的根元素 <coverage>,或其中没有 <file> 元素。 |
FUCINI_PARSE_170 | Coveralls JSON 无效,或缺少顶层 source_files 数组。 |
FUCINI_LOAD | 一次加载已从其进度通知中取消;上一次运行(如果有)保持加载。 |
FUCINI_REMOTE_001 | 某个 coverageFile.paths URL 无法抓取,附带原因;改为加载上次下载的副本。普通的 [FUCINI_REMOTE] 行记录每次下载和每次未变化(304)的检查。 |
FUCINI_REMOTE_002 | 某个 coverageFile.paths URL 在 30 秒内没有给出完整应答——多半是接受了连接却不再回应的代理或 VPN;改为加载上次下载的副本。 |
FUCINI_REMOTE_003 | 某个 coverageFile.paths URL 返回的是 HTML 页面而不是跟踪文件——很可能是登录页面。该页面不会被缓存,改为加载上次下载的副本;请检查 remote.headers 中的令牌。 |
FUCINI_REMAP_002 | 针对一次加载留下的未匹配路径,建议了一条 pathRemap 规则,并说明它能解释其中多少条——或者无法推断出任何规则,或者推断出的规则已在设置中却未能解决它们。 |
FUCINI_REMAP_003 | 建议的规则已追加到 fuciniCoverage.pathRemap(由 Apply Suggested Remap 完成),覆盖率正在重新加载。 |
FUCINI_GITDIFF_001 | Git 扩展不可用,因此跳过差异覆盖率并显示整个运行。 |
FUCINI_GITDIFF_002 | gitDiff.baseRef 以 - 开头,而任何 git 引用都不能这样开头(git 会把它当作选项);跳过差异覆盖率并显示整个运行。 |
FUCINI_IGNORE | fuciniCoverage.ignore 从运行中剔除了多少个文件,以及是哪些。 |
FUCINI_UNMEASURED_001 | 运行所属种类中没有任何测试加载过的文件,每行一个。 |
FUCINI_UNMEASURED_002 | 超过 5,000 个工作区文件匹配了运行的某个扩展名;只有前 5,000 个被纳入 Unmeasured 列表的考虑范围。 |
FUCINI_UNMEASURED_003 | 在 count 模式下,部分未测量文件被列出但不计数:已达到读取上限(每次加载 2,000 个文件或 50 MB)。 |
FUCINI_UNMEASURED_004 | 某个未测量的文件无法读取;它会被列出但不计数。 |
FUCINI_TESTCOVERAGE | 有多少文件被发布到了 VS Code 的 Test Coverage 视图。 |
FUCINI_BADGE | 徽章已写出,附带其路径、标签和值。 |
FUCINI_BADGE_001 | 徽章无法写出,附带原因。 |
FUCINI_WORKSPACE | 工作区中添加或移除了一个文件夹;重新进行覆盖率发现。 |
FUCINI_VARIABLES | 路径设置中每个 ${command:…} 返回了什么,或者为什么保持原样:命令失败、没有返回可用的值,或者工作区不受信任。 |
FUCINI_WATCH | 监视器正在做什么:正在监视的路径、某个已配置文件的出现、watch.reloadAfterTasks 中指定的某个任务结束(附其退出码)并触发了重新加载,或某个位于 files.watcherExclude 下而改为轮询的路径。 |
18. 隐私
遥测由 window 作用域的 telemetry.enabled 设置控制。默认关闭。每日摘要和移除通知还需要 VS Code 全局 telemetry.telemetryLevel 允许遥测;安装通知和少量小事件则无论如何都会发送,处于试用期或免费 Community 层级的安装在使用台账中的条目也是如此。在 Community 层级,Unlock Full Features 以此遥测换取全部 Pro 功能(含 MC/DC),为期 30 天,每台机器一次 — 再次关闭则适用标准的 Community 功能集;付费许可证在两种情况下均不受影响。发送的内容请参阅隐私政策。
有两件事值得知道,因为您可以据此采取行动。第一件是您的使用台账 ID:在试用期和 Community 层级,扩展会在 Show License / Activation 中以及 What Community Includes 的末尾显示它,选中那一行即可复制该 ID 并打开政策页面。台账只凭一个哈希识别您的机器,除此之外别无线索,因此这个 ID 是您询问台账为您保存了什么、要求删除它或对它提出反对的唯一途径——您报出它,我们就能找到您那一行;没有它,我们找不到。第二件是安装记录:某个安装首次报告时写入的一行,它会一直保留,而不是在 90 天后删除;其中包含安装标识符、日期、其地址解析到的地点、各版本号和操作系统——不含地址本身,也不含任何与您代码有关的内容。正是它让我们能说出这些年来一共有过多少次安装。两者都在隐私政策中有完整说明,只要您提出要求,两者都会被删除。

19. 评估与激活
Fucini Coverage Studio 以评估预发布版本的形式发布。首次运行时会请您同意进行评估,随后试用开始 — 在 14 天试用期内所有功能均可使用,试用期自您在该安装上首次使用时开始计算。此后没有任何固定的停止日期。
自 2.2026.2 版起,新安装的试用在扩展成功连接许可证服务器一次后开始;在此之前它以 Community 层级运行,并会如实说明。在开发容器或 Codespace 中不开始试用——订阅代码、密钥或 7 天通行证可在那里解锁全部功能。
试用期结束时不会锁定任何内容:扩展会转入免费的 Community 层级。若要恢复完整功能集,请进行激活 — 激活会连接许可证服务器并自动应用返回的密钥,无需邮件,也无需手动复制密钥:
Screenshot pending — add assets/img/licence-dialog.png (see SCREENSHOTS.md).- 运行
Fucini Coverage: Show License / Activation(试用期或密钥过期后也可从状态栏进入),然后点击Request License…(若此前已激活过,则为Request Renewed Key…)。 - 确认申请。密钥会自动签发,且会重复使用您此前申请中的联系方式,而不会再次询问。
- 就这样。激活密钥会生成并自动写入
fuciniCoverage.license.key。状态栏随即由试用切换为已激活许可证。
手动路径,一图说清:打开许可证面板,选择 Enter Key,粘贴邮件里的密钥,横幅就会变成“Activated”。
Screenshot pending — add assets/img/activate-key.gif (see SCREENSHOTS.md).如果自动请求失败(无网络连接、服务器问题):运行 Fucini Coverage: Copy Activation Request Code,通过联系页面或 info@fucini.consulting 发送给我们,然后用 Fucini Coverage: Enter Activation Key 粘贴您收到的 FCK1.… 密钥(或直接写入 settings.json):
{ "fuciniCoverage.license.key": "FCK1.…粘贴完整密钥…" }该设置名为 fuciniCoverage.license.key(用户/全局作用域)。密钥与您的设备及操作系统用户绑定,可能带有有效期,并可启用或限制单个功能,以及限制每天的覆盖率加载次数。如果显示“invalid for this machine”,请从本次安装申请新的密钥——为其他设备签发的密钥在这里永远无法通过验证。
在 Visual Studio 中。该版本没有免费层级:没有订阅就不读取任何内容。有订阅时,方案在那里决定的内容与在 VS Code 中相同 —— 方案不包含的功能不予提供,其命令会说明这一点而不是运行。许可证服务器仍支持的最低版本在那里同样有效:低于它的构建每天收到一次提示,并附上停止工作的日期,在那之前继续工作。离线永远不会让构建停止工作。
免费的 Community 层级:每周额度
Community 永远不会停止显示覆盖率。行号槽标记、概览标尺、悬停提示、CodeLens、Coverage 视图、监视模式、差异覆盖率和历史记录完全不计数,通过命令加载 tracefile 也只有每天 25 次的上限。计量的是输出:打开 HTML 报告、导出、比较两次运行、检查阈值以及就地查看带注释的源代码,每项每周五次。
一周以您为准,而不是以日历为准:从第一次使用开始,七天后结束。当某项额度用完时,提示消息会指明是哪项操作以及额度恢复的日期;在这一条消息之后,只有状态栏会再提及它。Fucini Coverage: What Community Includes 会列出每项额度、已使用的数量以及重置时间,让您在触及上限之前就心中有数。
Screenshot pending — add assets/img/weekly-allowance.png (see SCREENSHOTS.md).
Screenshot pending — add assets/img/community-limit-prompt.png (see SCREENSHOTS.md).
Screenshot pending — add assets/img/community-includes-weekly.png (see SCREENSHOTS.md).有三项细节属于付费方案,而相应的发现本身仍然免费:Community 会标记每一行存在存活变异体的代码并告知数量,但不显示每个变异体改动了什么;它会告知有多少个测试执行了某一行,但不显示测试名称;它会显示一次运行的 Max CRAP,但不显示排序后的 Riskiest Methods 列表。
Screenshot pending — add assets/img/mutant-hover-community.png (see SCREENSHOTS.md).如果在每周额度推出时(0.2026.83)您已经在使用 Community,您原先的上限将保留 30 天;我们只会通知您一次,并附上日期。计数器保存在您的计算机上——位于 VS Code 自身的存储以及 ~/.fucini-coverage/ 中,因此新建的 VS Code 配置文件会延续同一周——并且绝不会发送到任何地方。
7 天通行证
还没准备好订阅?7 天通行证让您在一周内使用 Pro 的全部功能,一次付费:不续订,也无需取消。请在价格页面购买(触及 Community 上限时的提示消息中也有相应按钮),激活码将通过电子邮件发送给您。
Screenshot pending — add assets/img/pricing-week-pass.png (see SCREENSHOTS.md).- 运行
Fucini Coverage: Enter Subscription Code——Community 版和 Pro 版中都有这条命令。 - 选择 Pro,然后粘贴邮件中的
FCB1-…代码。 - 就这么简单。密钥会自动为您签发并应用,且可离线使用。这一周从付款时开始计算,因此请在购买后立即激活。到期后,扩展会自行回到 Community,并继续显示覆盖率。
Screenshot pending — add assets/img/pass-step-plan.png (see SCREENSHOTS.md).
Screenshot pending — add assets/img/pass-step-code.png (see SCREENSHOTS.md).在 Visual Studio 中。 同一个代码即可激活 Fucini Coverage Studio for Visual Studio(2022 和 2026):打开 Tools → Coverage Studio → Licence…,粘贴代码,选择该代码对应的方案,然后按 Activate。Coverage 窗口随即在树的上方显示 Activated;用箭头展开后,该行会显示密钥的有效截止日期和一个通往您账户的按钮。一台机器即一个席位:激活 VS Code 或 Visual Studio 之一,同一台机器上的另一个编辑器会自动跟随——Visual Studio 读取 VS Code 的密钥,VS Code 读取 Visual Studio 的密钥——因此无需重复输入,也不会占用第二个席位。两者以相同方式维持密钥:Pro Plus、Business 和 Enterprise 每天签到两次,Pro 密钥在到期前一周内续期。


迁移到新电脑,以及管理席位
密钥绑定到一台机器,因此新电脑需要新的密钥——而订阅只为它所标明的机器数量付费。请自行释放旧的那台:登录管理你的订阅,找到不再使用的席位,选择 Free up,然后用同一个代码激活新机器。购买 Pro 不会给你发送密码——账户是存在的,只是还没有密码,所以第一次请用订阅时使用的邮箱地址设置或重置密码。
在 Business 和 Enterprise 中,你可以从同一页面通过邮件邀请每位同事。每个人都会收到只属于自己的激活码,一旦使用,他的邮箱地址就会出现在对应席位旁——有人离职时,你能看出哪个席位是他的,并把它释放给下一个人。邀请从发出的那一刻起占用一个席位,若 14 天未使用则过期;撤回邀请可立即收回该席位。
试用预发布版本
新功能会先到达 fucini.software,然后才进入 Marketplace,您可以申请获得推送。打开 Coverage 视图,展开许可证面板,点击 试用预发布版本…,或设置 fuciniCoverage.update.preRelease。此后扩展会在常规源之外,另行检查本站的预发布源,并提供两者中较新的一个。
Screenshot pending — add assets/img/licence-prerelease.png (see SCREENSHOTS.md).预发布经过测试,但却是我们最没有把握的构建——您是在自愿帮我们找出疏漏。加入后我们会知道此安装位于该通道,从而了解有多少人在测试,并告知您发生了哪些变化;退出后,下一个稳定版本会让您回到常规路径。并非总有预发布版本,在没有时加入也不会有任何损失:扩展会发现一个空的源,对此不作任何提示,并继续提供稳定版本,直到有预发布发布为止。
Screenshot pending — add assets/img/licence-prerelease-joined.png (see SCREENSHOTS.md).20. 联系方式与许可证
如有疑问、错误报告或替代许可条款 —— 请查看联系页面,或发送邮件至 info@fucini.consulting。基于附带发行说明署名条件的 MIT 许可证获得许可;另请参阅Notice。
21. 在命令行上
读取器也能在没有编辑器时运行:npm install -g fucini-coverage,免费,在 Windows、Linux、WSL 和 macOS 上。fucini-coverage check coverage/lcov.info --lines 80 是让流水线失败的门禁;summary、export、report、badge 和 compare 做的事与编辑器中同名命令相同,mcp 则在服务器上向智能体提供上一节的六个工具。它读取 fucini-coverage.json,也就是 Visual Studio 版所读的设置文件,因此阈值只需设置一次。fucini-coverage help <command> 解释每个命令;完整页面见 npm 上的 fucini-coverage。
Screenshot pending — add assets/img/cli-demo.gif (see SCREENSHOTS.md).