如何测量测试覆盖率?
解读
在国内 Rust 岗位面试中,面试官问“如何测量测试覆盖率”并不是想听你背命令,而是考察三点:
- 是否真正在 CI/本地跑过覆盖率,而不是“听说过”;
- 能否把覆盖率工具链与 Cargo 生态打通,给出可落地的工程方案;
- 是否知道 Rust 编译器基于 LLVM 的覆盖率映射机制,并能解释
-C instrument-coverage与grcov/llvm-cov的关系。
回答时要体现“编译参数 → 数据收集 → 报告生成 → CI 集成 → 质量门禁”完整闭环,并指出国内常见坑:Windows 路径、源码映射、profraw 合并、CI 缓存过大等。
知识点
- LLVM 覆盖率映射(Coverage Mapping):Rust 1.60+ 官方方案,编译期插入探针,开销 < 5%。
- 编译开关:
-C instrument-coverage、-C link-dead-code、-C codegen-units=1保证行号精确。 - 数据格式:
.profraw→llvm-profdata merge→.profdata;grcov可直接解析.profraw生成 lcov/json/cobertura。 - 工具链:
– grcov(Mozilla 开源,跨平台,支持 Cobertura,国内 GitLab/Azure DevOps 友好);
– cargo-llvm-cov(社区维护,一行命令出报告,支持 doctest、doc 覆盖);
– tarpaulin(仅 Linux x86_64,基于 ptrace,对 async/await 偶有漏报,国内镜像源拉取慢)。 - 报告指标:行覆盖、区域覆盖(Region)、分支覆盖(MC/DC 国内轨交、车载代码强制要求)。
- CI 集成:GitHub Actions / Gitee Go 里缓存
~/.cargo/bin与target/debug/deps/*.profraw,加阈值门禁grcov --threshold 80。 - 常见坑:
– 路径映射:Docker 内编译需加--remap-path-prefix;
– 并行测试:多进程写同一.profraw需LLVM_PROFILE_FILE="foo-%p-%m.profraw";
– 二进制改名:cargo nextest 需--profile cov保证测试二进制带探针。
答案
工程上我采用官方 LLVM 方案,三步落地:
- 编译:
RUSTFLAGS="-C instrument-coverage -C link-dead-code -C codegen-units=1" cargo build --profile test
这样所有测试二进制都带探针,且死代码也被链接,避免漏统计。 - 运行:
LLVM_PROFILE_FILE="target/cov/raw-%p-%m.profraw" cargo test --profile test
每个测试进程写独立 profraw,防止并发写冲突。 - 收集与报告:
使用 grcov(国内源已做 crates.io 镜像,下载 30 秒以内):
grcov target/cov --binary-path ./target/debug/deps -t lcov --branch --ignore-not-existing -o target/cov/lcov.info
再把 lcov.info 推送到 GitLab CI,配合coverage: '/lines\.*: (\d+\.\d+)%/'即可在 Merge Request 里看到行覆盖 87.3%,分支覆盖 81.2%。
若客户要求 HTML 自托管,执行
genhtml -o target/cov/html target/cov/lcov.info
即可在 Nginx 静态目录浏览,红色行一目了然。
对于doctest 覆盖,用cargo llvm-cov --doctests --open,可把文档测试纳入统计,这是 tarpaulin 做不到的。
最后在ci.yml里加门禁:
- grcov --threshold 80 --fail-under-lines 80
低于 80% 直接拒绝合并,保证交付质量。
拓展思考
- 国内车载软件要求MC/DC 100%,而 LLVM 区域覆盖只能近似分支覆盖,如何进一步插桩布尔短路表达式?可写 proc-macro 在
&&/||处插入#[coverage(off/on)]手动标记,再二次解析.profdata生成 MC/DC 报告。 - 对于no_std 嵌入式环境,没有文件系统,如何把探针数据吐出来?自定义
__llvm_profile_write_buffer重定向到串口,上位机收集后拼接成.profraw,再回主机用llvm-profdata合并,实现裸机覆盖率。 - 超大单仓库(百万行)CI 耗时 30 min,主要卡在
grcov解析。可改用增量覆盖:只统计 Merge Request diff 涉及的文件,用git diff --name-only过滤,再把增量报告上传到 CodeCov 的carryforwardflag,既满足门禁又节省 70% 时间。