1. Введение
Fucini Coverage Studio превращает трассировочные файлы покрытия, которые ваша сборка уже создаёт, в полноценное покрытие в редакторе — в VS Code и в Visual Studio. Он читает двадцать пять форматов, нормализует их в одну каноническую модель Fucini, детерминированно объединяет несколько прогонов и показывает всё в самом редакторе — поля, CodeLens, подсказки и окно Covered Files — так что нет отдельного просмотрщика, ради которого надо покидать редактор. Обе редакции делят один читатель: один и тот же отчёт даёт одни и те же числа, до цифры, в обеих. Это руководство написано с точки зрения VS Code; там, где Visual Studio отличается, врезка In Visual Studio говорит как.
Он создан с учётом процессов функциональной безопасности: покрытие MC/DC, покрытие регионов и инстанциаций C++, взвешенная по безопасности комплексная оценка и настраиваемый вердикт COMPLIANT / NON-COMPLIANT, согласованный с подходами IEC 61508, ISO 26262 и DO-178C.
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 или из файла .vsix через Extensions → ⋯ → Install from VSIX…. Расширение активируется при запуске и добавляет значок Coverage на панель активности.
В Visual Studio. Тот же продукт выходит как второе расширение, Fucini Coverage Studio для 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 КБ — крошечный C-проект с готовым lcov.info):
- Распакуйте его в любое место, затем откройте
coverage-studio-demo.code-workspaceв VS Code (File → Open Workspace from File…). - Установите Fucini Coverage Studio, если ещё не сделали этого (см. §2 выше).
- Собственный
.vscode/settings.jsonрабочей области уже указывает на встроенныйcoverage/lcov.info, поэтому полосы покрытия и представление Covered Files появляются сразу после активации расширения — шагLoad Coverageне требуется. - Хотите перегенерировать файл трассировки самостоятельно (например, другим компилятором)? Запустите
scripts/gen-coverage.sh(или.ps1в Windows) — доступные цели компиляторов смотрите в собственномREADME.mdрабочей области.
В Visual Studio. Откройте решение или папку (File → Open → Folder — так открывают проект CMake) — демо выше открывается как папка, и его .vscode/settings.json читается и там. Отчёт в месте, которое называют настройки, загружается сам, а панель Fucini Coverage окна Output говорит, какие файлы; иначе — Tools → Coverage Studio → Load Coverage File…, один раз — это запоминается. Откройте исходный файл и выберите Show Coverage in Editor: красное — строка, которая никогда не выполнялась, янтарное — решение, принятое только в одну сторону, а Ctrl+Shift+U, N переходит к следующей непокрытой строке. View → Other Windows → Coverage — это дерево с числами каждого файла и одним баллом по всем, который строка состояния показывает для прогона и для файла, в котором вы находитесь. Tools → Coverage Studio → Getting Started открывает эти шаги как страницу с кнопкой для каждого; в первый раз она открывается сама.
- Создайте файл трассировки покрытия с помощью обычного тестового процесса.
- Command Palette (
Ctrl+Shift+P) →Fucini Coverage: Load Coverage. При включённомwatch.enabled(по умолчанию) подходящий файл загружается автоматически. - Откройте покрытый исходный файл: строка состояния показывает его процент, заполняется представление «Покрытие», наведение на строку показывает число её попаданий, а прогон появляется в нативной панели 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 | оценка мутаций — без покрытия |
Нативный проект C++, измеренный из Visual Studio
В Visual Studio. Собственное покрытие проекта C++ в Visual Studio было только в Enterprise, а бесплатный плагин OpenCppCoverage, закрывавший этот пробел, снят с поддержки. Tools → Coverage Studio → Run Native Project with Coverage берёт стартовый проект — .vcxproj — спрашивает у MSBuild, куда он собирается и на каком наборе инструментов, и измеряет его одним из двух способов. На наборе инструментов ClangCL, с llvm-profdata и llvm-cov из LLVM на машине (их приносит компонент Visual Studio C++ Clang tools for Windows), он собирает проект заново в coverage\native\ с -fprofile-instr-generate -fcoverage-mapping, через props-файл, который MSBuild велено импортировать первым — в проекте ничего не меняется, — запускает программу с заданным LLVM_PROFILE_FILE, объединяет профиль и экспортирует 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)://скачивается в хранилище расширения при каждой загрузке и загружается оттуда; представление «Покрытие» называет URL. Каждое скачивание условное (ETag / Last-Modified), так что неизменившийся артефакт ничего не передаёт, а когда скачивание не удаётся, вместо него загружается последняя скачанная копия, с причиной в выводе.remote.headersдобавляет заголовкиName: valueс раскрытием переменных,remote.refreshMinutesскачивает заново по таймеру, а фрагмент#lcov.infoназывает формат, когда сам URL не заканчивается именем файла. - Glob-шаблоны исключения —
fuciniCoverage.ignoreотбрасывает целые файлы (вендорные, сгенерированные, сторонние) из прогона при его загрузке, так что каждое представление, каждый барьер и каждый экспорт считают без них; результат загрузки говорит, сколько исключено, а вывод их называет. Шаблоны сопоставляются с путём относительно рабочей области и с абсолютным путём, а шаблон без/соответствует имени папки или файла на любой глубине, как в.gitignore. - Многокорневые рабочие области — каждая папка обнаруживается на собственных условиях:
defaultLocation,paths,pathRemap,ignore,watch.*иremote.*читаются для каждой папки отдельно, так что действует собственный.vscode/settings.jsonпапки или её запись в.code-workspace, а${workspaceFolder}в записи означает папку самой записи. Файлы трассировки всех папок сливаются в один прогон, представление «Покрытие» группирует файлы по папкам, а папка, добавленная в рабочую область или удалённая из неё, сама перезагружает покрытие. - Перезагрузка после задачи —
watch.reloadAfterTasksназывает задачи (labelиз tasks.json илиsource: name, как показывает список Run Task), завершение которых перезагружает покрытие, независимо от того, включён лиwatch.enabled, — для сборок, за которыми наблюдателю не уследить. Абсолютная записьpathsвне рабочей области тоже опрашивается, так что появившийся там файл загружается. - Ход выполнения с возможностью отмены — загрузка десяти и более файлов трассировки, или 8 МБ и более, идёт под уведомлением о ходе выполнения с кнопкой «Отмена», которое называет читаемый файл и фазу; меньшая лишь крутит индикатор в строке состояния. Отмена оставляет предыдущий прогон загруженным и сообщает об этом.
Начиная с 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>/ каждого тестового проекта входит в число известных мест, а не только папка самой рабочей области, и <user>_<machine>_<date>.cobertura.xml сборщика Microsoft находится рядом с coverage.cobertura.xml от Coverlet — так что решение, тесты которого были запущены в Test Explorer Visual Studio, показывает своё покрытие при первом открытии, каждая папка запуска загружается, а слияние оставляет за самым новым запуском последнее слово по каждому файлу. А 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… кладёт рядом с решением один файл .runsettings со сборщиком Cobertura от Microsoft. Visual Studio подхватывает его, когда включён параметр Tools → Options → Test → Auto detect runsettings files; иначе выберите его один раз в 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 загружается и сливается в один запуск. Слияние детерминировано — результат не зависит от порядка загрузки — а файл, измеренный более чем одним файлом трассировки, берёт свои числа из того, у которого самое новое время изменения на диске: собственный повторный запуск модуля заменяет то, что о нём говорил более широкий, старый запуск, а не добавляется к нему. Представление Покрытие показывает, что это сделало: каждая строка файла называет файл трассировки, из которого взяты её числа, значок обведён там, где эти числа перекрыли более старый отчёт, каждая строка файла трассировки над деревом считает, сколько её файлов перекрыто или перекрыло другие, а щелчок по строке файла трассировки подсвечивает её файлы в дереве, пока по ней не щёлкнут снова (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 говорит, что делать вместо этого, для каждого файла отдельно. Это нужно в трёх ситуациях:
- Один отчёт лучше.
coverage.jsonот llvm-cov содержит MC/DC, аgcov.infoот GCC для той же сборки — только строки и ветви: выберитеcoverage.json, а другой оставьте для тех немногих файлов, которые собирает только GCC. - Несколько наборов тестов одной сборки. Модульные тесты, интеграционные тесты и hardware-in-the-loop пишут каждый свой отчёт; каждый по отдельности упускает строки, которые выполнили другие. Объединить, union: строка покрыта там, где её выполнил любой отчёт, а счётчики складываются.
- Одна кодовая база, два компилятора. Одни и те же исходники, собранные clang и GCC, идут разными путями через код, зависящий от инструментария, — здесь intrinsic, там переносимый запасной вариант, — и каждый отчёт говорит, что другая половина ни разу не выполнялась. Объединить, agree: строка покрыта, только если её выполнил каждый отчёт, который её измеряет, а строки, в которых отчёты расходятся, отмечены бирюзовым. Именно этот вопрос задаёт проверка между компиляторами: что на самом деле проверила каждая сборка, которую я поставляю?
Правила редко приходится писать вручную. Щёлкните файл правой кнопкой в представлении Покрытие и выберите 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" }
]
}
Что вы видите: строка файла в представлении Покрытие называет отчёт, из которого взяты её числа, или, при объединении, каждый отчёт и режим с ≠ n для спорных строк. Поле редактора и Annotated Source отмечают эти строки бирюзовым и сообщают, какой отчёт сколько раз их выполнил. Всплывающая подсказка строки состояния называет источник открытого файла, а для всего прогона — действующие правила и то, как решены остальные файлы. samples/c-cross-check из демо — это случай двух компиляторов, готовый к открытию. Visual Studio тоже всё это умеет: Choose Coverage Source... на строке представления Покрытие, а также Choose и Clear Coverage Source for This File... в Tools > Coverage Studio.
Выбор и объединение источников входят в платные планы. В Community самый новый отчёт побеждает в каждом файле, как и без правила, а о правиле в настройках сообщается, что оно не используется, вместо того чтобы молча его игнорировать.

bits.c из представления Покрытие — отчёт clang, отчёт GCC или оба вместе.


bits.c, clang и GCC, объединённые по agree, — строка показывает ≠ 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. - Какой файл трассировки покрыл строку — когда объединено несколько отчётов, подсказка со счётчиком выполнений заканчивается словом Покрыто: и каждым отчётом, покрывшим эту строку, включая имя набора из
TN:в lcov; щелчок по строке файла трассировки в представлении 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.Тепловая карта строится относительно файла. Оттенок строки сравнивает её счётчик с самой горячей строкой того же файла по логарифмической шкале в пять ступеней — поэтому тело цикла, выполненное тысячу раз, и функция, выполненная дважды, видны оба, и одно не заглушает другое, а файл, в котором всё выполнилось ровно один раз, выглядит равномерно тёплым, а не равномерно бледным. В тёмной теме шкала идёт от тусклого оливкового к яркому оранжевому, в светлой — от бледно-жёлтого к коричневому: в обоих случаях больше цвета означает больше выполнений, а красный остаётся зарезервированным для строк, которые не выполнялись ни разу. 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); это те же участки, которые отмечает Annotated Source. Только в строках, которые выполнялись: строка, которая не выполнялась ни разу, и так отмечена целиком. Шаблон C++ считается выполненным там, где его выполнила хотя бы одна инстанциация; что сделала каждая инстанциация, показывает метрика инстанциаций.
Screenshot pending — add assets/img/partial-line.png (see SCREENSHOTS.md).Какие тесты выполнили эту строку
Некоторые отчёты сообщают не только о том, что строка выполнялась, но и о том, какие тесты её выполнили: coverage.py, записанный командой coverage json --show-contexts при dynamic_context = test_function в .coveragerc, OpenCover, собранный с -coverbytest, и файл трассировки lcov, записанный по одной секции на тест (TN:), секции которого складываются в одну запись с тестами у каждой строки. Наведите указатель на начало строки, где стоит число попаданий, и тесты будут названы — сокращённо до класса и метода, первые пять по имени, остальные числом; 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 перечислит тестовые классы и запустит их по одному, помечая каждый отчёт его классом, чтобы подсказка, линза и команда могли ответить и для .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 — тест, который её выполнил, и 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 запрашивается diff рабочего дерева относительно 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, — а то, что он записал, загружается как любой прогон; раннер, который нельзя сузить, сообщает об этом, и Run Tests with Coverage запускает все.
Screenshot pending — add assets/img/run-this-test.gif (see SCREENSHOTS.md).
[Fact] — прогон в списке Tracefiles, строки над методами говорят, что код выполнил один тест.Go и Rust. В файле Go функция func TestXxx (или Fuzz…, Example…) под курсором запускается одна, в своём пакете и по точному имени — go test ./pkg/calc -run "^TestTotal$", — а вне теста запускается пакет файла. В файле Rust функция с #[test] запускается по своему пути в крейте с --exact; когда курсор стоит в mod, но ни в одном тесте, запускаются тесты этого модуля; интеграционный тест в tests/ запускается в собственном двоичном файле (--test api). Раннеру передаются только файлы его языка: для файла TypeScript в папке Go выводится сообщение, а не пустой прогон.
Покрытие после каждого сохранения
При включённом 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, веб-проект или worker-проект, но никогда не тестовый проект; если их несколько, предлагается выбор — запускается под dotnet-coverage от Microsoft в отдельной консоли, чтобы в консольное приложение можно было вводить текст, и работает, пока вы его не закроете. То, через что вы прошли, затем загружается как любой прогон, а в сессии покрытия добавляется к тому, что выполнили тесты. То, как завершилось приложение, на прогон не влияет. Инструмент нужно установить один раз: 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).Мутанты и где они находятся
Инструмент мутационного тестирования — PIT для Java, Stryker для JavaScript, C# и Scala, Mull для C и C++ — вносит в код небольшие изменения и прогоняет тесты для каждого из них. Изменение, которого не заметил ни один тест, — это выживший мутант: код выполнился, но никто не проверил, что он сделал. Мутационная оценка говорит, сколько мутантов было поймано; начиная с 0.2026.83 редактор показывает, где находятся остальные. После строки с таким мутантом стоит пометка (⚑ 2 mutants survived), изменённый текст подчёркнут, если отчёт указывает, какой именно это был текст (так делают Stryker и Mull), на обзорной линейке появляется полоса, а при наведении видно, что было изменено, сколько тестов выполнили этот код, и для пойманных мутантов — какой тест их поймал.
Screenshot pending — add assets/img/mutant-hover.png (see SCREENSHOTS.md).Выжил означает, что тесты выполнили изменённый код и все прошли. Не проверен (в отчёте — no coverage) означает, что этот код не выполняет ни один тест, так что заметить изменение было некому. Пойманные мутанты подсчитываются в подсказке при наведении и никогда не отмечаются, а мутанты, которые не были честной проверкой, — например, тот, что не скомпилировался бы, — в оценку не входят. В Annotated Source под каждой такой строкой есть отдельная строка и кнопка 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 — PIT с Maven, которому сообщают класс. То, как завершился раннер, на прогон не влияет: оценка ниже порога — это код возврата, а отчёт всё равно есть. О раннере, который ничего не записал, сообщается — вместе с тем, что для него нужно установить.
Screenshot pending — add assets/img/mutation-here.gif (see SCREENSHOTS.md).
Screenshot pending — add assets/img/vs-mutation-here.gif (see SCREENSHOTS.md).Собственное представление Test Coverage VS Code
При включённом testCoverage.publish (по умолчанию) каждый загруженный прогон попадает и в нативное представление Test Coverage редактора — панель покрытия в Test Explorer, проценты по файлам, которые она ставит в Проводнике, и собственный переключатель редактора Show Inline Coverage — как строки, ветви и функции, с подробностями, подгружаемыми по запросу, из какого бы файла трассировки они ни пришли и без участия какого-либо адаптера тестов. Show in Test Coverage View публикует активный прогон и открывает панель независимо от настройки; меню строки состояния предлагает это за quickActions.showTestCoverage. При выключенной настройке запись Fucini Coverage в Test Explorer не появляется вовсе. Когда файл трассировки называет свои тесты (секции TN: в lcov), каждый тест появляется под Fucini Coverage в Test Explorer, и представление может фильтровать покрытие файла по тесту.
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 и в порогах. Панель 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, файл за файлом, — что большой прогон почувствует при каждой загрузке, — и занимают место diff для дифференциального покрытия и для порога выше. Ещё не закоммиченная строка считается в любом случае.
{
"fuciniCoverage.gitDiff.author": "Ada",
"fuciniCoverage.gitDiff.since": "2026-09-01"
}
Screenshot pending — add assets/img/new-code-author.png (see SCREENSHOTS.md).Косвенные изменения: что сдвинулось, хотя никто этого не трогал
Diff говорит, какие строки изменились; два прогона говорят, какие строки перестали или начали выполняться. Посмотреть стоит на те строки, где одно расходится с другим: код, который никто не правил и который больше не выполняется, потому что удалили тест, ветвь выше по ходу выполнения теперь делает ранний возврат, изменилась фикстура. Ревью diff их увидеть не может — они по определению вне его. Show Indirect Coverage Changes… перечисляет их по файлам, первым — файл, потерявший больше всего, и открывает выбранный на первой такой строке. Прогон сравнивается с базовой линией CI там, где baseline.report её называет, — по ней и судят pull request, — а иначе с предыдущим прогоном; строки, которые рабочее дерево меняет относительно 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, чтобы исключить эту метрику из оценки. Метрики, отсутствующие в данных (например, MC/DC в обычном lcov-прогоне), просто опускаются.
Объектный код и мутационная оценка имеют вес 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).
Считывается из проб condition BullseyeCoverage, экспорта cond TRACE32 и GCC 14 при сборке с -fcondition-coverage. В отчёте, который не отличает условия от решений, нет ни такой строки, ни столбца, ни веса. Если вы используете Bullseye или TRACE32: раньше условия считались ветвями, поэтому с этим выпуском ваш процент ветвей изменится, а с новым весом — и составная оценка; чтобы получить оценку в прежнем виде, задайте для whmWeights.conditions значение 0.
GCC 14, построчно. С -fcondition-coverage gcov говорит, какого исхода какого условия не хватает — условие 1 ни разу не было истинным, условие 0 не было ни истинным, ни ложным, — и каждое условие отмечается на своей строке и называется в подсказке при наведении, по JSON gcov и по его текстовому листингу (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: функция с оценкой на этом уровне или выше попадает в список Riskiest Methods в HTML-отчёте, начиная с худшей.
Сложность и оценка отображаются в CodeLens функции и рядом с функцией в аннотированном исходном коде. Max CRAP — это наивысшая оценка среди всех функций файла — худшая, а не средняя, — и она является столбцом в представлении Coverage, в HTML-отчёте и в экспорте в CSV и Markdown. Это справочная информация: у неё нет порога и нет веса в комплексной оценке. У отчётов, не содержащих сложности (lcov, gcov, Istanbul, coverage.py), нет CRAP — и пустого столбца тоже нет.
Начиная с 0.2026.83 он есть и у C, и у C++. llvm-cov не сообщает сложность, поэтому она подсчитывается по данным о ветвях, которые уже есть в отчёте: условия, записанные в собственном файле функции, плюс один — это определение Маккейба и то же, что считают инструменты уровня исходного кода для .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, Cobertura от Coverlet, JaCoCo, llvm-cov). Community видит количество и худший показатель; ранжированный список — в Pro. Рядом с сортировкой второй выпадающий список говорит, какие файлы показывает дерево — все, файлы с пробелами, непротестированные файлы или файлы, изменившиеся с последнего запуска; папки следуют за своими файлами и сохраняют собственные числа. Обе возможности есть и в Visual Studio, в окне Coverage.
Screenshot pending — add assets/img/vs-hotspots.png (see SCREENSHOTS.md).Пространства имён, классы и методы
Для отчёта .NET, называющего свои классы — Cobertura и coverage.cobertura.xml от Coverlet, 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(), в момент которого их записать. Счётчики всё равно лежат в оперативной памяти, поэтому решение — выгрузить эту память и восстановить файлы вне цели;embedded-gcovот NASA JPL (Apache-2.0) делает именно это, без файловой системы, операционной системы и стандартной библиотеки C. Это даёт покрытие операторов, ветвей и функций сборки, сделанной вашим кросс-компилятором, для вашей системы команд, на вашем оборудовании — но не покрытие объектного кода, а измеряемый двоичный файл инструментирован, то есть больше и медленнее поставляемого. Результат загружайте как LCOV, gcovr JSON, gcov JSON или, на инструментарии старше GCC 9, как текстовый листинг.gcov. - Симулятор системы команд. Запустите настоящий целевой двоичный файл в симуляторе и выведите покрытие из выполненных адресов: без оборудования, без инструментации, без зонда — и, поскольку наблюдаются инструкции, он может дать настоящее покрытие объектного кода. Ограничение — точность: симулятор хорошо моделирует ядро и хуже периферию, поэтому к результатам уровня всей системы относитесь с осторожностью, если он не моделирует ваш кристалл детально.
- Трассировка на кристалле и отладчик. Путь сертификационного уровня и единственный по-настоящему невмешивающийся: блок трассировки ядра передаёт поток инструкций отладчику, который превращает его в покрытие. Ничего не инструментируется, размер кода не меняется, тайминги не затронуты — именно это и делает результат доказательством о том двоичном файле, который вы действительно поставляете. Проверьте кристалл, прежде чем закладывать это в бюджет: во многих семействах микроконтроллеров полная трассировка доступна только на эмуляционном или отладочном варианте с большим буфером трассировки и скоростным портом, тогда как серийные кристаллы несут урезанный блок трассировки, а запасной вариант производителя для них — программная трассировка, которая инструментирует код и потребляет процессорное время, то есть уничтожает то самое свойство, ради которого этот путь и выбирали.
Многие встраиваемые проекты измеряют покрытие, компилируя те же исходники для машины разработки и запуская модульные тесты там. Это быстро, не требует оборудования и для большинства команд является правильным выбором по умолчанию — но стоит чётко понимать, что именно описывает это свидетельство: другой компилятор, другой оптимизатор, другую систему команд и другой двоичный файл, нежели поставляемый. Для большей части ПО это приемлемый и хорошо понятный компромисс. Для работ, оцениваемых по IEC 61508, ISO 26262 или DO-178C, это стандартное возражение «хост против цели», и описанные выше пути — стандартные ответы на него: первый закрывает вопрос для покрытия исходников, третий — для объектного кода.
Сегодня для объектного кода читается экспорт COVerage.EXPORT.ListModule Lauterbach TRACE32 с metric="object"; загружайте его как любой другой файл трассировки. Эта метрика оценивается и контролируется только по платной лицензии и в период оценки. Установка Community не оценивает её — но получает уведомление, когда файл трассировки её содержит, потому что вердикт о соответствии, молча опускающий самое сильное доказательство в файле, был бы хуже вердикта, который говорит, что именно он опустил.
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), выглядел покрытым на 100%, потому что цикл в файле .c доходил до неё, а тот, чью константу ничто не использовало, — покрытым на 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 — дерево без него, шаблон в .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"]
}
Исключение из редактора
Выделите строки и возьмите Exclude Selection from Coverage из лампочки, контекстного меню редактора или Command Palette — в Visual Studio из лампочки (Ctrl+.), меню правой кнопки в окне кода, Tools → Coverage Studio или Ctrl+Shift+U, X, — и исключение записывается там, где код: [ExcludeFromCodeCoverage] над классом, методом или свойством C#, VB или F#, коротким именем, если файл уже импортирует 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 и его область убирают ещё меньше — только ветви, в которые попадают при выбросе исключения, там, где отчёт говорит, какие это ветви, как это делает JSON gcov. Каждый маркер читается и с префиксами 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 либо Justification у [ExcludeFromCodeCoverage].
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. Описывает ли это покрытие всё ещё этот файл?
Покрытие, прочитанное из файла трассировки, описывает код таким, каким он был при запуске тестов, и большинство форматов не сообщает, какая это была версия. Поэтому каждый файл на панели «Покрытие» находится в одном из трёх состояний:
- ✓ Проверено — файл трассировки хранит содержимое каждой измеренной строки, и оно по-прежнему совпадает с файлом.
- Без пометки — не опровергнуто. Ничто не противоречит покрытию и ничто его не подтверждает. Обычное состояние.
- ⚠ Опровергнуто — сам файл опровергает своё покрытие. Строка приглушена, а подсказка сообщает, что именно найдено.
Заслужить галочку могут только два формата, потому что лишь два сохраняют хоть что-то об измеренном коде: lcov, записанный с --checksum (в geninfo по умолчанию отключено; задокументированная цель — как раз не дать объединить покрытие двух версий файла) и GNATcoverage XML, который встраивает текст каждой измеренной строки. Включить эту опцию lcov стоит.
Предупреждению не нужно ни то, ни другое, и оно работает с любым форматом: покрытие, записанное за концом файла, именованная функция, которой больше нет там, куда её ставит отчёт, или выполненные строки, пустые в файле. Отметок времени самих по себе никогда не достаточно — git checkout меняет каждую, не меняя ни байта —, так что изменённая отметка времени — самое большее повод посмотреть ещё раз. Проверка выполняется при загрузке покрытия и снова всякий раз, когда файл сохраняется или меняется на диске, так что отметка следует за файлом: правка, противоречащая прогону, её ставит, checkout, возвращающий измеренную версию, её снимает. Подсказка говорит точно, что это доказало. Установите staleWarning.enabled в false, чтобы отключить всё это.
12b. Неизмеренное против непокрытого
Файл, который прогон нигде не упоминает, — неизмеренный: его не загружал ни один тест, поэтому ни один инструмент не записал о нём ни строки. Это не то же самое, что непокрытый — измеренный, с нулём попаданий, — и расширение никогда не оценивало отсутствующий файл как 0%. Чего оно не делало до 0.2026.69 — так это вообще не показывало отсутствующие: модуль без единого теста никогда не появлялся ни в дереве, ни в итогах, ни в проверке порогов, и читатель видел «92%» у проекта, половина файлов которого никогда не выполнялась.
Теперь при загруженном прогоне элемент строки состояния для файла говорит не измерено об исходном файле того же вида, что и прогон, которого в прогоне нет, — «того же вида» значит, что у него одно из расширений файлов, которые прогон измерял, так что заголовки и исходники C-проекта — кандидаты, а его скрипты на Python — нет. Всё сверх этого включается по желанию через fuciniCoverage.unmeasured.mode:
off(по умолчанию) — больше ничего не меняется.list— представление «Покрытие» получает внизу группу Unmeasured, называющую каждый такой файл, и каждая строка его открывает; результат загрузки их считает; значки помечают их?в Проводнике; а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, в том же glob-синтаксисе, что и fuciniCoverage.ignore; добавьте порт под платформу, который здесь не собирается, или сгенерированные заглушки — и они выпадут из списка, из счёта и из состояния строки состояния. Файл, названный в 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, XML Cobertura, SARIF или автономный значок SVG для вашего README. Доступно и с кнопки рядом с Generate Full HTML Report в представлении Covered Files. Всё, кроме CSV, теряет данные — MC/DC, регионам и инстанцированиям негде разместиться в LCOV, Cobertura или SARIF — поэтому Fucini JSON и CSV остаются форматами, несущими каждую метрику.
- Copy Uncovered Lines — сводка в формате Markdown по каждому пропуску, готовая для вставки в PR.
В Visual Studio. Группа Reports в Tools → Coverage Studio: 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. Аннотированной странице нужен WebView2, как и окну Coverage; отчётам на диске — нет.
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 записывает в рабочую область SVG в стиле shields для активного прогона — badge.outputPath, по умолчанию coverage-badge.svg, относительно папки рабочей области или абсолютно, с теми же переменными, что и в любой другой настройке путей, — и предлагает его открыть. Это обычный файл в вашем репозитории, так что README встраивает его без какого-либо сервиса за ним, без обращения к shields.io, и он работает и в изолированном от сети репозитории:

badge.metric выбирает, что показывает значок: lines (с подписью coverage), branches, mcdc или weighted — та же комплексная оценка, что и в строке состояния, с подписью score. Прогон без данных для выбранной метрики получает серое n/a, а не красные 0%. Цвет следует вашим собственным уровням colorScale.*: lowColor ниже lowThreshold, midColor до highThreshold, 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).
Сессии покрытия
Один тестовый прогон редко покрывает всё, чего коснулось изменение: модульные тесты прогнали здесь, интеграционный набор там, проход по интерфейсу с включённым покрытием где-то ещё. 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 отвечает, что сделало ваше последнее изменение; перед pull request вопрос в том, где вы относительно main, и ответ — на сборочном сервере. baseline.report называет отчёт, который публикует сборка ветки main, и каждая загрузка сверяется с ним: карточка в строке состояния несёт однострочную дельту — 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:
- URL, который отдаёт CI. GitLab держит последние артефакты задания ветки по стабильному адресу; токен остаётся вне файла настроек благодаря
${env:…}вremote.headers. 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 запрашивается и кэшируется точно так же, как URL изcoverageFile.paths(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 относительно своего checkout — src/Billing/Invoice.cs, как его пишет Coverlet на раннере, — перед сравнением помещается под эту папку, чтобы встретиться с файлом, который разрешил локальный прогон; абсолютный путь с другой машины не совпадает ни с чем, так что пусть конвейер пишет относительные пути. Базовая линия, которую нельзя запросить или прочитать, — это строка в панели 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, к URL в coverageFile.paths. Токен уходит на сервер, для которого он задан, и не дальше: переход по перенаправлению на хранилище, где лежит архив, выполняется без него.
{
"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. Для ИИ-агентов: покрытие на экране
Агент, который пишет тесты, может запустить их и прочитать процент. Чего он не может сам — спросить о том, что знает редактор: какие строки этой функции ни разу не выполнялись, какая сторона какой ветви, какие тесты доходят до строки, покрыт ли diff, который он только что написал. Шесть инструментов отвечают именно на это — словами, по которым агент может действовать, по загруженному прогону: те же исключения, то же переотображение, те же числа, что на экране. Поэтому агент, которому велели закрыть пробелы в покрытии функции, и рецензент, глядящий на поле редактора, говорят об одних и тех же строках.
| Инструмент | На что отвечает |
|---|---|
coverage_summary | Итоги прогона по каждой метрике и файлы, которым дальше всего до полного покрытия. Первое, о чём стоит спросить. |
coverage_gaps | Что ни разу не выполнялось в файле, функции или диапазоне строк: непокрытые строки, непройденные ветви, условия MC/DC без своей пары, выжившие мутанты. |
tests_for_line | Какие тесты выполнили строку — там, где отчёт это говорит, то есть при прогоне с разбивкой по тестам. |
patch_coverage | Покрытие diff — рабочего дерева относительно ссылки или переданного патча — и его строки, которые ни разу не выполнялись. |
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 исключает выделение; это не Alt+N из VS Code, потому что в Visual Studio Alt с буквой открывает меню. Измените их в Tools → Options → Keyboard, где они перечислены как Coverage Studio: …. Нет в этой редакции: панель Test Coverage — это API VS Code, вместо неё прогон показан в окне 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 (Ctrl+Alt+R на macOS). |
| 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… | Запустить приложение .NET из папки под dotnet-coverage и, когда оно закроется, загрузить то, через что вы прошли. |
| Run Mutation Tests Here | Мутировать функцию, в которой стоит курсор, — или выделение, или файл — с помощью Stryker.NET, StrykerJS или PIT и загрузить отчёт рядом с покрытием на экране. |
| Exclude This File from Coverage / Exclude from Coverage | Поместить открытый файл — или, на строке представления Coverage, файл либо /** папки — в fuciniCoverage.ignore в настройках папки, один раз. |
| Exclude Selection from Coverage | Записать исключение там, где код: [ExcludeFromCodeCoverage] над членом .NET, LCOV_EXCL_LINE в одной строке, LCOV_EXCL_START/STOP вокруг нескольких, знаком комментария самого файла. Также лампочка и контекстное меню редактора. |
| Start Coverage Session / End Coverage Session | Пока идёт сессия, каждая загрузка добавляется к предыдущей как объединение; полоса над деревом считает прогоны и завершает её. |
| Next / Previous Uncovered | Переходить между непокрытыми регионами. По умолчанию Alt+N / Alt+P (Ctrl+Alt+N / Ctrl+Alt+P на macOS). |
| 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 | Опубликовать активный прогон в собственную панель Test Coverage VS Code (даже при выключенном 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 (Ctrl+Alt+A на macOS). |
| 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 — отчёта ветки main из CI, URL или скачанного артефакта — файл за файлом. |
| Show Indirect Coverage Changes… | Строки, покрытие которых изменилось, хотя никто их не правил, — относительно базовой линии CI или предыдущего прогона, по файлам. |
| Check Coverage Thresholds | Оценить проверки соответствия. |
| Export Fucini JSON | Экспортировать текущий запуск как Fucini JSON. |
| Export Coverage Report... | Выберите формат — CSV, Markdown, LCOV, XML Cobertura, 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 закрепляет собственный интерфейс расширения во время работы (строка состояния, уведомления, подсказки, веб-представления) за определённым языком, независимо от языка интерфейса 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 в корне решения — для команды, которая предпочла бы не держать папку .vscode в репозитории .NET; редакция для VS Code читает его тоже. Шестерёнка на панели инструментов окна Coverage открывает окно настроек, которое записывает пользовательский файл или тот файл папки, что существует, как говорит его переключатель Write changes to, и сохраняет комментарии файла, который редактирует. fuciniCoverage.language там работает так же; меню следуют языку самой Visual Studio.
17. Коды ошибок
Диагностика выводится в канал вывода Fucini Coverage (View → Output → Fucini Coverage), каждая со стабильным кодом FUCINI_*.
В Visual Studio. Те же коды идут в панель Fucini Coverage окна Output (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 | JSON llvm-cov недействителен, отсутствует data[] или нет файлов. |
FUCINI_PARSE_030 | Корневой элемент Cobertura <coverage> отсутствует или XML недействителен. |
FUCINI_PARSE_040 | Корневой элемент JaCoCo <report> отсутствует или нет исходных файлов. |
FUCINI_PARSE_050 | JSON Istanbul/SimpleCov недействителен или не содержит файлов. |
FUCINI_PARSE_060 | JSON SimpleCov недействителен или отсутствует массив files верхнего уровня. |
FUCINI_PARSE_070 | Профиль Go без заголовка mode: либо без блоков покрытия. |
FUCINI_PARSE_080 | Отсутствует корневой элемент Clover <coverage> или <project>, либо в нём нет файлов. |
FUCINI_PARSE_090 | Отсутствует корневой элемент OpenCover <CoverageSession> либо в нём нет файлов. |
FUCINI_PARSE_100 | JSON coverage.py недействителен либо в нём нет объекта files верхнего уровня. |
FUCINI_PARSE_110 | JSON gcov/gcovr недействителен либо в нём нет массива files верхнего уровня. |
FUCINI_PARSE_120 | GNATcoverage XML без элемента <source> или index.xml, чьи xi:include так и не были раскрыты — загрузите вместо него файлы *.xml для каждого исходника. |
FUCINI_PARSE_130 | Отсутствует корневой элемент <BullseyeCoverage> или в нём нет элементов <src>. |
FUCINI_PARSE_140 | Отсутствует корневой элемент <CoverageDSPriv>, нет таблицы <SourceFileNames> для сопоставления строк или нет данных о строках. |
FUCINI_PARSE_150 | Некорректный JSON покрытия Xcode или отсутствует массив targets верхнего уровня. |
FUCINI_PARSE_160 | Отсутствует корневой элемент <coverage> SonarQube или в нём нет элементов <file>. |
FUCINI_PARSE_170 | Некорректный JSON Coveralls или отсутствует массив source_files верхнего уровня. |
FUCINI_LOAD | Загрузка отменена из своего уведомления о ходе выполнения; предыдущий прогон, если он был, остаётся загруженным. |
FUCINI_REMOTE_001 | URL из coverageFile.paths не удалось скачать, с указанием причины; вместо него загружается последняя скачанная копия. Обычные строки [FUCINI_REMOTE] фиксируют каждое скачивание и каждую проверку без изменений (304). |
FUCINI_REMOTE_002 | URL из coverageFile.paths не дал полного ответа за 30 секунд — чаще всего это прокси или VPN, который принимает соединение и затем молчит; вместо него загружается последняя скачанная копия. |
FUCINI_REMOTE_003 | URL из coverageFile.paths ответил HTML-страницей вместо файла трассировки — скорее всего, страницей входа. Страница не кэшируется, вместо неё загружается последняя скачанная копия; проверьте токен в remote.headers. |
FUCINI_REMAP_002 | Для путей, оставшихся после загрузки несопоставленными, предложено правило pathRemap, с числом путей, которые оно объясняет, — либо вывести ничего не удалось, либо выведенное правило уже есть в настройке и их не разрешило. |
FUCINI_REMAP_003 | Предложенное правило добавлено в fuciniCoverage.pathRemap командой Apply Suggested Remap, и покрытие перезагружается. |
FUCINI_GITDIFF_001 | Расширение Git недоступно; покрытие diff пропускается и показывается весь прогон. |
FUCINI_GITDIFF_002 | gitDiff.baseRef начинается с -, с чего ни одна ссылка git начинаться не может (git прочитал бы его как опцию); покрытие diff пропускается и показывается весь прогон. |
FUCINI_IGNORE | Сколько файлов fuciniCoverage.ignore исключил из прогона, и какие именно. |
FUCINI_UNMEASURED_001 | Файлы того же вида, что и прогон, которые не загружал ни один тест, по одному на строку. |
FUCINI_UNMEASURED_002 | Более 5 000 файлов рабочей области подошли под одно из расширений прогона; для списка Unmeasured рассмотрены только первые 5 000. |
FUCINI_UNMEASURED_003 | В режиме count некоторые неизмеренные файлы перечислены, но не засчитаны: достигнут предел чтения (2 000 файлов или 50 МБ за загрузку). |
FUCINI_UNMEASURED_004 | Неизмеренный файл не удалось прочитать; он перечислен, но не засчитан. |
FUCINI_TESTCOVERAGE | Сколько файлов опубликовано в представление Test Coverage VS Code. |
FUCINI_BADGE | Значок записан, с его путём, подписью и значением. |
FUCINI_BADGE_001 | Значок не удалось записать, с указанием причины. |
FUCINI_WORKSPACE | Папка добавлена в рабочую область или удалена из неё; покрытие обнаруживается заново. |
FUCINI_VARIABLES | Что ответила каждая ${command:…} в настройках путей или почему её оставили как есть: команда не сработала, вернула что-то непригодное или рабочая область не доверенная. |
FUCINI_WATCH | Что делает наблюдатель: отслеживаемые пути, появившийся настроенный файл, завершившаяся задача из watch.reloadAfterTasks (с её кодом выхода), вызвавшая перезагрузку, или путь под files.watcherExclude, который вместо этого опрашивается. |
18. Конфиденциальность
Телеметрией управляет параметр telemetry.enabled с областью window. По умолчанию он выключен. Для ежедневной сводки и уведомления об удалении также нужно, чтобы глобальный telemetry.telemetryLevel VS Code разрешал телеметрию; сигнал об установке и несколько небольших событий отправляются независимо от него, как и запись в журнале использования для установки в пробном периоде или на бесплатном уровне Community. На Community команда Unlock Full Features обменивает эту телеметрию на все возможности Pro, включая MC/DC, на 30 дней, один раз на машину — выключите его снова, и действует стандартный набор Community; на платную лицензию это не влияет ни в том, ни в другом случае. Что отправляется, см. в политике конфиденциальности.
Знать стоит о двух вещах, потому что с ними вы можете что-то сделать. Первая — ваш идентификатор журнала использования: в пробном периоде и на уровне Community расширение показывает его в Show License / Activation и внизу раздела What Community Includes, где выбор этой строки копирует идентификатор и открывает политику. Журнал знает вашу машину по хешу и больше никак, поэтому этот идентификатор — единственный способ спросить, что журнал хранит о вас, потребовать удаления или заявить возражение: укажите его — и мы найдём вашу строку; без него не найдём. Вторая — запись об установке: одна строка, создаваемая при первом обращении установки и сохраняемая, а не удаляемая через 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, а полученный в ответ ключ FCK1.… вставьте командой Fucini Coverage: Enter Activation Key (или напрямую в settings.json):
{ "fuciniCoverage.license.key": "FCK1.…вставьте ключ целиком…" }Название настройки — fuciniCoverage.license.key (область пользователь/глобально). Ключ привязан к вашей машине и пользователю ОС, может иметь срок действия и может включать или ограничивать отдельные функции и ограничивать число загрузок покрытия в день. Если отображается «invalid for this machine», запросите новый ключ из этой установки — ключ, выданный для другой машины, здесь никогда не пройдёт проверку.
В Visual Studio. В этой редакции нет бесплатного уровня: без подписки ничего не читается. С подпиской план решает там то же, что и в VS Code: функция, которую план не включает, недоступна, а её команда сообщает об этом вместо того, чтобы выполняться. Самая старая версия, которую ещё поддерживает сервер лицензий, действует и там: сборка ниже неё получает сообщение раз в день, с датой, когда она перестанет работать, и до тех пор продолжает работать. Отсутствие сети никогда не останавливает сборку.
Бесплатный уровень Community: недельный лимит
Community никогда не перестаёт показывать покрытие. Поля редактора, обзорная линейка, всплывающие подсказки, CodeLens, представление Coverage, режим наблюдения, покрытие изменений (diff) и история не учитываются вообще, а загрузка трасс-файла командой ограничена лишь 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).Если вы уже пользовались Community, когда появился недельный лимит (0.2026.83), ваши прежние ограничения сохраняются ещё 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 для 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> объясняет каждую; полная страница — fucini-coverage на npm.
Screenshot pending — add assets/img/cli-demo.gif (see SCREENSHOTS.md).