C++如何进行代码覆盖率测试?(gcov与lcov使用)

1次阅读

必须同时加-fprofile-arcs和-ftest-coverage,否则gcov无覆盖率数据;前者插桩计数,后者生成.gcno;仅对gcc/g++编译目标生效,需在cmake中显式配置。

C++如何进行代码覆盖率测试?(gcov与lcov使用)

gcc编译时必须加-fprofile-arcs -ftest-coverage

不加这两个flag,gcov压根看不到任何覆盖率数据——它不是运行时自动采集的,而是靠编译器在插桩(instrument)时埋点。漏掉任一flag都会导致生成的二进制里没覆盖率逻辑,后续所有步骤都白忙。

常见错误现象:gcov报告全是#####(表示无计数),或者lcov生成的HTML里所有行标灰、覆盖率为0。

  • -fprofile-arcs:让gcc在分支跳转处插入计数代码
  • -ftest-coverage:生成.gcno文件(含源码结构信息),供gcov解析
  • 必须同时启用,且仅对用gcc/g++编译的目标文件生效;CMake项目需在CMAKE_CXX_FLAGStarget_compile_options里显式追加
  • 注意:链接时不用额外参数,但运行测试前得确保环境变量GCOV_PREFIX(如需重定向输出路径)已设好

gcov只处理单个.o.gcno文件,不能直接扫整个工程

gcov本质是按目标文件粒度工作的——给它一个.gcno和对应的.gcda,它才输出那个源文件的覆盖率。很多人误以为跑一次gcov *.gcno就能汇总,结果要么报错,要么只刷出部分文件。

使用场景:调试单个模块时快速验证;配合脚本批量处理多个.gcda生成原始.gcov文件。

立即学习C++免费学习笔记(深入)”;

  • 典型命令:gcov path/to/file.gcno(自动找同目录file.gcda
  • .gcda不在源码目录,用-o指定路径:gcov -o build/objs/ src/foo.cpp.gcno
  • 输出的foo.cpp.gcov是带标记的源码副本,-行未执行,#####行无计数,数字行是执行次数
  • 别手动删.gcda:它记录运行时计数,每次测试后要保留才能被lcov合并

lcov生成HTML前必须先lcov --capture收集数据

直接拿一.gcda扔给lcov -a会失败——lcov需要先通过--capture把当前工作目录下所有.gcda读进来,转换成中间格式(.info),再做过滤、合并、生成HTML。

性能影响:大工程里--capture可能卡几秒,因要遍历所有.gcda并解析对应.gcno;若.gcno路径不对(比如编译路径和当前路径不一致),会跳过该文件且静默失败。

  • 基础流程:lcov --capture --Directory build/ --output-file coverage.base.info
  • 跑完测试后,再捕获一次:lcov --capture --directory build/ --output-file coverage.test.info
  • --diff--remove过滤系统头、第三方库:lcov --remove coverage.test.info '/usr/*' '*/test/*' --output-file coverage.filtered.info
  • 最后genhtml coverage.filtered.info --output-directory report/

c++模板、内联函数、异常路径容易漏覆盖

gcov对模板实例化体的覆盖率统计很脆弱——如果模板定义在头文件里且没被显式实例化,对应.gcda可能根本不存在;inline函数若被编译器内联掉,也不会生成独立计数;catch块若没触发异常,gcov就当它不存在。

容易踩的坑:看到HTML里某行标灰,第一反应是“代码没跑”,但更可能是编译器优化掉了该路径,或模板没实例化。

  • 模板问题:在测试文件末尾加template class MyTemplate<int>;</int>强制实例化,确保生成.gcno/.gcda
  • 禁用内联:编译时加-fno-inline -fno-inline-small-functions(仅调试期用,别上生产)
  • 异常路径:写专门触发throw的单元测试,或用__attribute__((noinline))标记catch块里的关键函数
  • 注意-O0不是万能解——某些路径在-O0下反而因缺少优化而无法触发(比如NRVO失效导致额外拷贝)

事情说清了就结束。

text=ZqhQzanResources