使用 CMake 和 Gcovr 进行代码覆盖率分析
1. Gcovr 的原理
1.1 编译时插桩 (Instrumentation)
使用 -fprofile-arcs -ftest-coverage
选项时,GCC 会插入 探针 (probes) 以记录代码执行情况。
编译后,会生成:
.gcno
文件:代码块(Basic Block)信息。- 可执行文件(带有探针的代码)。
1.2 运行时记录 (Profiling Data)
运行可执行文件后,GCC 记录运行数据并生成:
.gcda
文件:存储实际执行路径。
1.3 解析覆盖率数据
gcovr
解析.gcno
和.gcda
文件,提取覆盖率数据。- 格式化输出 HTML、XML、JSON 等报告。
1.4 过滤 & 报告生成
常见 gcovr
参数:
--root <path> # 设置项目根目录
--exclude <pattern> # 排除测试代码
--html # 生成 HTML 报告
--xml # 生成 XML 报告 (CI/CD 用)
--json # 生成 JSON 报告
1.5 调试 & 解决问题
如果覆盖率显示 0%
,可以检查:
- 确保
-fprofile-arcs -ftest-coverage
选项已启用。 - 确认
.gcda
文件是否生成:find build -name "*.gcda"
- 手动运行
gcov
解析:gcov src/math_lib.cpp -o build/CMakeFiles/math_lib.dir/src
- 确保
gcovr --root
指定了正确的路径。
2. 环境准备
2.1 安装必要的软件包
在 Linux (Ubuntu) 下,可以使用以下命令安装相关工具:
sudo apt update
sudo apt install cmake g++ lcov gcovr libgtest-dev
3. 项目结构
project/
│── CMakeLists.txt
│── src/
│ ├── math_lib.cpp
│ ├── math_lib.h
│ ├── main.cpp
│── tests/
│ ├── test_math_lib.cpp
│── build/ (编译目录)
│── reports/ (存放覆盖率报告)
4. 代码示例
4.1 头文件 math_lib.h
#ifndef MATH_LIB_H
#define MATH_LIB_H
int add(int a, int b);
bool isEven(int n);
int if_add(int a, int b, bool added);
#endif
4.2 源文件 math_lib.cpp
#include "math_lib.h"
int add(int a, int b) {
return a + b;
}
bool isEven(int n) {
return n % 2 == 0;
}
int if_add(int a, int b, bool added) {
if (added) {
return a + b;
}
return a - b;
}
4.3 测试代码 test_math_lib.cpp
#include <gtest/gtest.h>
#include "../src/math_lib.h"
TEST(MathTest, AddFunction) {
EXPECT_EQ(add(2, 3), 5);
EXPECT_EQ(add(-1, 1), 0);
}
TEST(MathTest, IsEvenFunction) {
EXPECT_TRUE(isEven(4));
EXPECT_FALSE(isEven(7));
}
int main(int argc, char **argv) {
::testing::InitGoogleTest(&argc, argv);
return RUN_ALL_TESTS();
}
5. CMake 配置文件 CMakeLists.txt
#
cmake_minimum_required(VERSION 3.20)
project(CodeCoverageExample)
# 启用 C++ 标准
set(CMAKE_CXX_STANDARD 17)
# 启用代码覆盖率编译选项
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fprofile-arcs -ftest-coverage")
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} --coverage")
# 添加 Google Test
find_package(GTest REQUIRED)
include_directories(${GTEST_INCLUDE_DIRS})
# 添加源文件
add_library(math_lib src/math_lib.cpp)
# 添加测试可执行文件
add_executable(test_math_lib tests/test_math_lib.cpp)
target_link_libraries(test_math_lib PRIVATE math_lib GTest::GTest GTest::Main pthread)
# 添加测试
enable_testing()
add_test(NAME RunTests COMMAND test_math_lib)
# 添加代码覆盖率目标
find_program(GCOVR gcovr REQUIRED)
set(GCOVR_COMMAND ${GCOVR_EXECUTABLE})
# 创建 reports 文件夹
file(MAKE_DIRECTORY ${CMAKE_SOURCE_DIR}/reports)
if (GCOVR)
execute_process(
COMMAND ${CMAKE_COMMAND} -E echo "Cleaning *.gcno/*.gcda files"
COMMAND find ${CMAKE_CURRENT_BINARY_DIR} -name '*.gcda' -delete
COMMAND find ${CMAKE_CURRENT_BINARY_DIR} -name '*.gcno' -delete
)
message(STATUS "${GCOVR}")
message(STATUS "${CMAKE_SOURCE_DIR}")
add_custom_target(coverage
COMMAND set -x
COMMAND ${GCOVR} --root ${CMAKE_SOURCE_DIR}
--html-details --html=${CMAKE_SOURCE_DIR}/reports/coverage.html
--xml-pretty --xml=${CMAKE_SOURCE_DIR}/reports/coverage.xml
--json -o ${CMAKE_SOURCE_DIR}/reports/coverage.json
WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}
COMMENT "Generating code coverage report..."
)
endif()
6. 运行步骤
6.1 编译
# 生成构建目录并配置项目
cmake -S . -B build
# 构建项目
cmake --build build
6.2 运行测试
# 运行测试
ctest --test-dir build
6.3 生成覆盖率报告
cmake --build build --target coverage
生成的覆盖率报告coverage.html
7. 总结
完整代码覆盖率流程
- 编译:使用
-fprofile-arcs -ftest-coverage
选项,生成.gcno
文件。 - 运行:运行测试,生成
.gcda
文件。 - 分析:使用
gcov
解析.gcda
和.gcno
文件。 - 生成报告:
gcovr
读取gcov
输出,并生成 HTML、XML、JSON 报告。
通过这套方法,我们可以高效地分析 C++ 代码的测试覆盖率,并在 CI/CD 中集成代码质量检查。