覆盖率收集工具:GCOV/LCOV 工具链介绍、安装与配置、生成覆盖率报告
说到覆盖率分析,GCOV 和 LCOV 这对组合,是我这些年用得最顺手的工具。说白了,GCOV 是 GCC 自带的代码覆盖率工具,而 LCOV 是它的图形化增强版。我刚开始做覆盖率分析时,对着原始 GCOV 生成的 .gcov 文件看得头晕,后来发现 LCOV 能把数据转成 HTML 报告,这才算真正入了门。
GCOV 是什么?
GCOV 是 GCC 编译器套件的一部分。你编译时加上特定选项,它就会在代码里插入探针。运行时,这些探针会记录每行代码的执行次数。嗯,这里要注意:GCOV 不会修改你的源代码,它只是在编译后的目标文件中加入计数信息。
我个人习惯把 GCOV 看作「代码的记分员」。它告诉你:哪行代码跑了多少次,哪个分支走了哪条路。我在项目中遇到过一个问题:某个函数明明被调用了,但内部某个 if 分支始终没进去。GCOV 一眼就揪出来了——那个分支的计数永远是 0。
LCOV 又是什么?
LCOV 是 GCOV 的前端工具。它把 GCOV 生成的原始数据,转成可视化的 HTML 报告。你想想看,原始 GCOV 输出是这样的:
-: 0:Source:main.c
-: 1:/* 注释 */
1: 2:int main() {
-: 3: int x = 0;
#####: 4: if (x > 0) {
-: 5: printf("positive\n");
-: 6: }
看到那个 ##### 了吗?表示第 4 行从未执行。但说实话,这种文本格式看多了眼睛疼。LCOV 把它变成彩色的 HTML,绿色表示已覆盖,红色表示未覆盖,一目了然。
安装与配置
安装其实很简单。在 Ubuntu/Debian 上:
sudo apt-get install gcov lcov
在 CentOS/RHEL 上:
sudo yum install gcc-c++ lcov
macOS 用户可以用 Homebrew:
brew install lcov
注意:GCOV 是 GCC 的一部分,装了 GCC 就有了。LCOV 需要单独装。
配置方面,我建议你创建一个 lcovrc 配置文件。我个人习惯这样配:
# ~/.lcovrc
geninfo_auto_base = 1
genhtml_branch_coverage = 1
genhtml_function_coverage = 1
这样生成的报告会包含分支覆盖率和函数覆盖率,不只是行覆盖率。
生成覆盖率报告:完整流程
好,我们走一遍完整流程。假设你有一个 C 项目,结构如下:
my_project/
├── src/
│ ├── main.c
│ └── utils.c
└── test/
└── test_main.c
第一步:编译时加选项
gcc -fprofile-arcs -ftest-coverage -o test_program src/main.c src/utils.c test/test_main.c
-fprofile-arcs 和 -ftest-coverage 是核心。前者生成分支计数代码,后者生成覆盖率数据文件。我刚开始时老忘加 -fprofile-arcs,结果跑完发现 .gcda 文件没生成,白跑一趟。
第二步:运行测试
./test_program
运行结束后,每个源文件旁边会生成一个 .gcda 文件。比如 main.gcda、utils.gcda。这些是二进制格式的覆盖率数据。
第三步:生成 LCOV 数据文件
lcov --capture --directory . --output-file coverage.info
--capture 表示收集数据,--directory . 指定搜索 .gcda 文件的目录。我建议加上 --rc lcov_branch_coverage=1 来启用分支覆盖率:
lcov --capture --directory . --rc lcov_branch_coverage=1 --output-file coverage.info
第四步:过滤不需要的文件
你肯定不想把系统头文件、测试代码本身也算进去。我一般这样过滤:
lcov --remove coverage.info '/usr/*' '*/test/*' --output-file coverage_filtered.info
第五步:生成 HTML 报告
genhtml coverage_filtered.info --output-directory coverage_report
打开 coverage_report/index.html,就能看到漂亮的覆盖率报告了。
核心知识体系
下面这张 SVG 图,帮你理清 GCOV/LCOV 工具链的工作流程:
实用技巧与避坑指南
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fprofile-arcs -ftest-coverage")
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -fprofile-arcs")
这样整个项目都能自动插桩,不用每个文件单独加。
我曾经踩过一个坑:跑完测试发现覆盖率报告里全是 0%。查了半天,原来是测试程序没正常退出(被 kill -9 了),.gcda 文件没来得及写盘。记住,程序必须正常退出(调用 exit 或从 main 返回),GCOV 才会把缓冲区数据刷到 .gcda 文件里。
解读覆盖率报告
LCOV 生成的 HTML 报告,主要看这几个指标:
| 指标 | 含义 | 我一般看什么 |
|---|---|---|
| 行覆盖率 | 被执行的代码行数 / 总行数 | 低于 80% 说明测试不充分 |
| 分支覆盖率 | 被走到的分支路径 / 全部分支 | 重点关注 if-else、switch 的遗漏分支 |
| 函数覆盖率 | 被调用的函数数 / 总函数数 | 未调用的函数可能是死代码 |
你想想看,行覆盖率 100% 不代表分支覆盖率 100%。比如 if (a && b) 这种条件,行覆盖可能只测了 a=true, b=true 的情况,但 a=false 或 b=false 的分支根本没测到。所以我建议你开启 LCOV 的分支覆盖率选项,别只看行覆盖率。
- GCOV 是 GCC 自带的覆盖率工具,LCOV 是它的图形化前端
- 编译时加
-fprofile-arcs -ftest-coverage,运行时生成 .gcda 文件 - 用
lcov --capture收集数据,genhtml生成 HTML 报告 - 记得过滤系统头文件和测试代码,否则覆盖率数据会虚高
- 分支覆盖率比行覆盖率更有价值,建议重点关注
好了,GCOV/LCOV 工具链的基本用法就这些。实际项目中,我还会结合 CI/CD 流水线,每次提交代码自动跑覆盖率,低于阈值就告警。这个后面有机会再细聊。