覆盖率收集工具: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 工具链的工作流程:

GCOV / LCOV 工具链工作流程 源代码 main.c, utils.c 编译 GCC 编译 -fprofile-arcs -ftest-coverage 生成 可执行文件 test_program 运行 运行测试用例 ./test_program 生成 .gcda 数据文件 main.gcda, utils.gcda LCOV 处理 HTML 覆盖率报告 lcov → genhtml 关键文件说明 .gcno - 编译时生成 .gcda - 运行时生成 .info - LCOV 中间文件 .html - 最终报告 (.gcno 在编译时生成, .gcda 在运行时生成)

实用技巧与避坑指南

💡 小技巧: 如果你用 CMake,可以在 CMakeLists.txt 里这样配置:
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fprofile-arcs -ftest-coverage")
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -fprofile-arcs")
这样整个项目都能自动插桩,不用每个文件单独加。
⚠️ 注意: 生产环境千万别用带覆盖率编译选项的二进制!我见过有人把测试版直接部署到线上,结果性能下降 30%,还多了一堆 .gcda 文件占磁盘空间。覆盖率编译选项只应在测试阶段使用。

我曾经踩过一个坑:跑完测试发现覆盖率报告里全是 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 流水线,每次提交代码自动跑覆盖率,低于阈值就告警。这个后面有机会再细聊。

可可精品资料库