14、CTest覆盖率分析:CTest与gcov/lcov集成、设置COVERAGE标志、生成覆盖率报告
覆盖率分析这件事,说实话,我早年做C++项目时经常忽略它。
那时候觉得「功能跑通了就行」,结果有一次线上崩溃,查了半天才发现是一段从未被测试执行到的异常处理代码出了bug。嗯,从那以后,我再也不敢不看覆盖率报告了。
这一章,我们就来聊聊怎么在CMake + CTest的体系里,把gcov和lcov用起来,真正让覆盖率数据为你服务。
14.1 为什么需要覆盖率分析?
你想想看,测试用例全绿通过,就代表代码质量没问题吗?
不一定。如果有一半的代码根本没被执行到,那测试通过的意义就大打折扣。覆盖率分析,说白了就是帮你回答三个问题:
- 哪些代码被测试执行了?
- 哪些分支逻辑没有被覆盖?
- 我的测试用例是不是漏掉了关键路径?
我个人习惯把覆盖率当作「测试的体检报告」。它不直接证明代码正确,但能告诉你测试的「盲区」在哪。
14.2 gcov与lcov:经典组合
在Linux环境下,gcov是GCC自带的覆盖率工具,lcov则是它的可视化增强版。两者配合,可以生成漂亮的HTML报告。
它们的分工很清晰:
| 工具 | 作用 |
|---|---|
| gcov | 编译时插桩,运行时记录每行代码的执行次数 |
| lcov | 解析gcov数据,生成带图形界面的HTML报告 |
我在项目中遇到过一种情况:gcov生成了大量.gcda文件,但手工分析太痛苦。后来用lcov一键生成报告,效率提升了一个量级。
14.3 在CMake中设置COVERAGE标志
要让gcov生效,编译和链接时都需要加上特定的编译选项。CMake里最干净的做法,是用一个COVERAGE标志来控制。
来看一个典型的CMakeLists.txt配置:
cmake_minimum_required(VERSION 3.16)
project(CoverageDemo LANGUAGES CXX)
# 设置C++标准
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
# 可选:通过-DCOVERAGE=ON开启覆盖率
option(COVERAGE "Enable coverage instrumentation" OFF)
if(COVERAGE)
message(STATUS "Coverage instrumentation is ON")
# 编译选项:-fprofile-arcs -ftest-coverage
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fprofile-arcs -ftest-coverage -g")
# 链接选项:-lgcov
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -lgcov")
set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -lgcov")
endif()
# 添加可执行文件
add_executable(my_app src/main.cpp src/utils.cpp)
# 添加测试
enable_testing()
add_test(NAME test_basic COMMAND my_app --test)
-g调试符号。没有调试信息,lcov生成的报告里行号会乱掉。我吃过这个亏,排查了半天才发现是忘了加-g。
14.4 构建并运行覆盖率测试
配置好之后,构建和运行就很简单了:
# 配置,开启覆盖率
cmake -B build -DCOVERAGE=ON
# 构建
cmake --build build
# 运行测试(会生成.gcda文件)
cd build && ctest --output-on-failure
运行完ctest后,你会在build目录下看到一堆.gcda和.gcno文件。前者是运行时数据,后者是编译时信息。
find . -name "*.gcda" -delete。
14.5 用lcov生成覆盖率报告
数据有了,接下来用lcov把它变成可视化的HTML报告。
基本流程分三步:
- 收集原始数据:lcov从.gcda文件提取信息
- 过滤不需要的文件:比如第三方库、系统头文件
- 生成HTML:genhtml输出漂亮的报告
一个完整的脚本示例:
#!/bin/bash
# 进入构建目录
cd build
# 1. 收集覆盖率数据
lcov --capture --directory . --output-file coverage.info
# 2. 过滤掉外部依赖(比如/usr/include下的系统头文件)
lcov --remove coverage.info '/usr/*' '*/third_party/*' --output-file coverage_filtered.info
# 3. 生成HTML报告
genhtml coverage_filtered.info --output-directory coverage_report
echo "报告已生成:build/coverage_report/index.html"
打开coverage_report/index.html,你会看到每个文件的覆盖率百分比,还能点进去查看具体哪些行被覆盖、哪些行没被执行。
--remove参数非常关键。不过滤的话,报告里会塞满系统库的覆盖率数据,根本看不清楚自己的代码。
14.6 将覆盖率集成到CTest中
手动跑脚本虽然可行,但不够自动化。我建议把覆盖率生成也做成一个CTest测试用例,这样一键就能完成。
在CMakeLists.txt里添加:
# 添加覆盖率报告生成测试
if(COVERAGE)
find_program(LCOV lcov REQUIRED)
find_program(GENHTML genhtml REQUIRED)
add_test(NAME coverage_report
COMMAND ${CMAKE_SOURCE_DIR}/scripts/generate_coverage.sh
WORKING_DIRECTORY ${CMAKE_BINARY_DIR}
)
set_tests_properties(coverage_report PROPERTIES
FIXTURES_REQUIRED "test_basic"
)
endif()
这样,运行ctest -R coverage_report就能自动生成报告。而且通过FIXTURES_REQUIRED,它会先确保基础测试已经跑过。
我曾经在CI流水线里这么配置,每次提交代码后自动生成覆盖率报告,团队里谁代码没测到位,一目了然。
14.7 知识体系总览
下面这张图,帮你把整个流程串起来:
14.8 常见问题与避坑
- 报告里全是0%? 检查一下是否真的运行了测试。gcda文件只有在程序正常退出时才会生成。如果测试进程被kill,数据就丢了。
- 覆盖率偏低? 别急着怀疑代码。先看看是不是忘了过滤第三方库。我见过有人把STL的覆盖率也算进去,结果自己代码只有30%。
- lcov命令找不到? 记得先安装:
sudo apt install lcov。macOS上用brew install lcov。
好了,这一章的内容就到这里。把gcov和lcov集成到CMake + CTest里,其实不复杂。关键是养成习惯,每次提交代码前看一眼覆盖率报告。你会发现,很多隐藏的bug在写代码阶段就被揪出来了。
公众号:蓝海资料掘金营,微信deep3321