测试程序执行前准备与执行后文件收集
本文记录执行 GoogleTest 测试程序前应确认的准备项,以及测试程序执行后应收集的文件。它适合用于本地调试、CI 任务、覆盖率统计、问题复现和测试结果归档。
测试程序不是只要能运行就算完成。真正可追踪的测试执行,应当在运行前明确环境、工具链、路径和输出位置,在运行后收集测试结果、日志、覆盖率原始数据和最终报告。
执行检查与收集流程
准备环境
-> 清理旧产物
-> 执行测试程序
-> 收集测试结果
-> 收集覆盖率数据
-> 生成并归档报告
执行前准备清单
| 准备项 | 检查内容 | 目的 |
|---|---|---|
| 构建类型 | 使用 Debug 或覆盖率专用构建目录 |
避免优化影响行覆盖率和调试信息 |
| 覆盖率编译选项 | GCC/Clang 使用 --coverage -O0 -g,或等价 CMake 选项 |
生成 .gcno 和运行时计数逻辑 |
| 测试可执行文件 | 确认测试目标已经成功编译 | 避免执行旧二进制或缺失目标 |
| 运行目录 | 固定工作目录,例如工程根目录或构建目录 | 避免相对路径读取资源失败 |
| 输出目录 | 预先创建日志、报告、覆盖率数据目录 | 防止程序运行后无法写入文件 |
| 目录权限 | 确认测试进程对输出目录有写权限 | 覆盖率和日志文件通常在进程退出时写入 |
| 环境变量 | 设置动态库路径、资源路径、覆盖率路径等变量 | 保证运行时能找到依赖和产物位置 |
| 旧数据清理 | 清理历史 .gcda、旧报告和旧日志 |
避免本次结果被历史数据污染 |
| 工具链版本 | gcov、lcov、gcovr 与编译器版本匹配 |
避免覆盖率格式不兼容 |
| 随机与时间 | 固定随机种子、时区和测试时间基准 | 减少不稳定测试 |
| 外部依赖 | 准备测试数据库、临时目录、假服务或配置文件 | 防止测试访问真实生产环境 |
覆盖率相关环境变量
覆盖率测试最容易出问题的是 .gcda 文件没有生成,或生成到了不容易收集的位置。GCC 的 gcov 运行时支持使用环境变量重定向 .gcda 输出路径。
| 变量 | 说明 |
|---|---|
GCOV_PREFIX |
指定 .gcda 文件输出的根目录 |
GCOV_PREFIX_STRIP |
从原始目标文件路径前面剥离的目录层级数 |
GCOV_ERROR_FILE |
指定 gcov 运行时错误输出文件,便于排查无法写入 .gcda 的原因 |
GCOV_EXIT_AT_ERROR |
设置为非零值时,gcov 运行时遇到错误会退出 |
GCOV_PREFIX 和 GCOV_PREFIX_STRIP 常用于以下场景:
- 测试程序在容器、目标机或沙箱中执行,但构建目录不可写。
- 测试程序被部署到其他目录执行,原始构建路径不存在。
- 需要把不同测试任务的
.gcda输出隔离到不同目录。 - 交叉编译后在目标板执行测试,再把覆盖率数据拷回主机生成报告。
GCOV_PREFIX 示例
假设编译时目标文件路径类似:
/home/build/project/build-coverage/CMakeFiles/core_tests.dir/src/foo.cpp.gcda
测试程序实际运行在 /opt/test-run,并希望把 .gcda 写入 /tmp/gcov-data。如果不设置环境变量,程序退出时会尝试写回原始构建路径。一旦该路径不存在或没有写权限,就可能导致 .gcda 生成失败。
可以在执行前设置:
export GCOV_PREFIX=/tmp/gcov-data
export GCOV_PREFIX_STRIP=3
export GCOV_ERROR_FILE=/tmp/gcov-data/gcov-error.log
mkdir -p "${GCOV_PREFIX}"
ctest --test-dir build-coverage --output-on-failure
剥离 3 层路径后,.gcda 会写入类似:
/tmp/gcov-data/project/build-coverage/CMakeFiles/core_tests.dir/src/foo.cpp.gcda
GCOV_PREFIX_STRIP 的值需要根据构建机上的绝对路径层级调整。可以先运行一次测试,再用 find 检查 .gcda 是否落在预期目录。
find /tmp/gcov-data -name "*.gcda" -print
如果没有 .gcda,应优先检查:
- 测试程序是否正常退出。异常终止、
SIGKILL、强制杀进程可能来不及写.gcda。 GCOV_PREFIX目录是否存在且可写。GCOV_PREFIX_STRIP是否剥离过多或过少。- 编译和链接阶段是否都启用了覆盖率选项。
GCOV_ERROR_FILE中是否有写入失败、路径不存在或权限错误。
Windows 和 MinGW 示例
在 Windows 的 MinGW 或 MSYS2 环境中,路径建议尽量使用当前工具链能识别的形式。Bat 示例:
set "GCOV_PREFIX=D:\coverage\gcov-data"
set "GCOV_PREFIX_STRIP=3"
set "GCOV_ERROR_FILE=D:\coverage\gcov-data\gcov-error.log"
if not exist "%GCOV_PREFIX%" mkdir "%GCOV_PREFIX%"
ctest --test-dir build-coverage --output-on-failure
MSYS2 Shell 示例:
export GCOV_PREFIX=/d/coverage/gcov-data
export GCOV_PREFIX_STRIP=3
export GCOV_ERROR_FILE=/d/coverage/gcov-data/gcov-error.log
mkdir -p "${GCOV_PREFIX}"
ctest --test-dir build-coverage --output-on-failure
如果后续使用 gcovr 或 lcov 在 Windows 主机上收集数据,应确认工具能同时访问 .gcno 所在构建目录和 .gcda 所在输出目录。必要时把 .gcda 拷回原始构建目录的对应层级。
执行前建议清理的文件
| 文件或目录 | 是否建议清理 | 说明 |
|---|---|---|
*.gcda |
是 | 历史计数会污染本次覆盖率 |
旧 coverage.info |
是 | 避免误上传旧 tracefile |
| 旧 HTML 报告目录 | 是 | 避免新旧页面混在一起 |
| 旧 JUnit XML | 是 | 避免 CI 解析到上一次测试结果 |
| 测试临时目录 | 视情况 | 如果测试依赖干净状态,应清理 |
*.gcno |
否 | .gcno 是编译产物,通常不应在执行前删除 |
常用清理命令:
find build-coverage -name "*.gcda" -delete
rm -rf coverage-html coverage-gcovr
rm -f coverage.info coverage.xml test-result.xml test.log
如果使用 lcov,也可以用:
lcov --directory build-coverage --zerocounters
执行测试程序
直接运行测试程序时,可以输出 GoogleTest XML:
./build-coverage/tests/core_tests \
--gtest_output=xml:test-result.xml
使用 CTest 时,建议开启失败详情:
ctest --test-dir build-coverage --output-on-failure
如果需要筛选用例:
./build-coverage/tests/core_tests \
--gtest_filter="StringUtilTest.*" \
--gtest_output=xml:test-result.xml
如果需要重复运行排查不稳定测试:
./build-coverage/tests/core_tests \
--gtest_repeat=20 \
--gtest_break_on_failure \
--gtest_output=xml:test-result.xml
执行后必须收集的文件
| 类型 | 常见文件 | 用途 |
|---|---|---|
| 测试结果 | test-result.xml、Testing/**/*.xml |
CI 展示通过、失败、跳过和耗时 |
| 终端日志 | test.log、CI job log |
排查断言失败、崩溃和环境问题 |
| 覆盖率原始数据 | .gcno、.gcda |
生成覆盖率报告的基础数据 |
| 覆盖率中间文件 | coverage.info、coverage.raw.info、coverage.xml、coverage.json |
供 lcov、genhtml、gcovr 或 CI 平台解析 |
| HTML 报告 | coverage-html/index.html、coverage-gcovr/coverage.html |
本地查看源码行覆盖情况 |
| 崩溃产物 | core dump、minidump、崩溃日志 | 定位段错误、非法访问、abort |
| Sanitizer 日志 | asan.log.*、ubsan.log.*、tsan.log.* |
定位内存、未定义行为和线程问题 |
| 测试配置 | 测试配置文件、资源文件、环境变量快照 | 复现测试执行环境 |
覆盖率归档时不要只保存 HTML。HTML 适合阅读,但 .info、XML 或 JSON 更适合 CI 趋势分析和后续二次处理。
覆盖率文件收集建议
.gcno 在编译时生成,通常位于构建目录;.gcda 在测试程序运行并正常退出时生成。收集覆盖率时应确保二者能够按路径对应起来。
build-coverage/
CMakeFiles/
core_tests.dir/
src/
foo.cpp.gcno
foo.cpp.gcda
如果 .gcda 被 GCOV_PREFIX 重定向到其他目录,建议归档时保留目录层级:
gcov-data/
project/
build-coverage/
CMakeFiles/
core_tests.dir/
src/
foo.cpp.gcda
生成报告前应确认:
.gcno来自本次构建,而不是旧构建。.gcda来自本次测试执行,而不是历史残留。gcov工具版本与编译器版本一致。- 源码路径、构建路径和重定向路径都能被收集工具访问。
推荐归档目录结构
test-artifacts/
logs/
test.log
gcov-error.log
junit/
test-result.xml
coverage/
raw/
gcno/
gcda/
lcov/
coverage.raw.info
coverage.info
gcovr/
coverage.xml
coverage.json
coverage.html
html/
index.html
diagnostics/
core/
sanitizer/
environment/
env.txt
versions.txt
这个结构把“可读报告”和“可二次处理数据”分开,后续排查会更方便。
环境信息记录
建议在执行前或执行后记录关键版本:
{
date
uname -a
cmake --version
ctest --version
gcc --version
gcov --version
lcov --version
gcovr --version
env | sort
} > test-artifacts/environment/versions.txt
如果环境变量中包含 token、密码、私有仓库地址等敏感信息,不应直接上传完整 env。可以只记录测试相关变量,例如:
{
echo "GCOV_PREFIX=${GCOV_PREFIX:-}"
echo "GCOV_PREFIX_STRIP=${GCOV_PREFIX_STRIP:-}"
echo "GTEST_FILTER=${GTEST_FILTER:-}"
echo "CTEST_PARALLEL_LEVEL=${CTEST_PARALLEL_LEVEL:-}"
} > test-artifacts/environment/env.txt
CI 中的最小示例
set -euo pipefail
PROJECT_ROOT="$(pwd)"
BUILD_DIR="${PROJECT_ROOT}/build-coverage"
ARTIFACT_DIR="${PROJECT_ROOT}/test-artifacts"
GCOV_DATA_DIR="${ARTIFACT_DIR}/coverage/raw/gcda"
rm -rf "${ARTIFACT_DIR}"
mkdir -p \
"${ARTIFACT_DIR}/logs" \
"${ARTIFACT_DIR}/junit" \
"${ARTIFACT_DIR}/coverage/gcovr" \
"${GCOV_DATA_DIR}" \
"${ARTIFACT_DIR}/environment"
cmake -S "${PROJECT_ROOT}" -B "${BUILD_DIR}" \
-DCMAKE_BUILD_TYPE=Debug \
-DENABLE_COVERAGE=ON
cmake --build "${BUILD_DIR}" --parallel
find "${BUILD_DIR}" -name "*.gcda" -delete
export GCOV_PREFIX="${GCOV_DATA_DIR}"
export GCOV_PREFIX_STRIP=3
export GCOV_ERROR_FILE="${ARTIFACT_DIR}/logs/gcov-error.log"
ctest --test-dir "${BUILD_DIR}" --output-on-failure 2>&1 \
| tee "${ARTIFACT_DIR}/logs/test.log"
gcovr \
--root "${PROJECT_ROOT}" \
"${BUILD_DIR}" \
"${GCOV_DATA_DIR}" \
--gcov-executable gcov \
--xml-pretty \
--output "${ARTIFACT_DIR}/coverage/gcovr/coverage.xml" \
--print-summary
gcovr \
--root "${PROJECT_ROOT}" \
"${BUILD_DIR}" \
"${GCOV_DATA_DIR}" \
--gcov-executable gcov \
--html --html-details \
--output "${ARTIFACT_DIR}/coverage/gcovr/coverage.html" \
--print-summary
如果项目不需要重定向 .gcda,可以去掉 GCOV_PREFIX 和 GCOV_PREFIX_STRIP,只让 .gcda 生成在构建目录中。
常见问题
| 问题 | 可能原因 | 处理 |
|---|---|---|
没有 .gcda |
测试程序没有正常退出、目录不可写、没有覆盖率编译 | 检查退出码、权限、--coverage 编译和链接选项 |
.gcda 生成到奇怪目录 |
GCOV_PREFIX_STRIP 层级不正确 |
调整剥离层级,并用 find 验证 |
| 覆盖率为 0 | .gcno 和 .gcda 不匹配,或工具链版本不匹配 |
清理后重新构建,指定正确 gcov |
| CI 没有展示测试失败详情 | 未收集 JUnit XML 或日志 | 使用 --gtest_output=xml,归档 test.log |
| 报告包含测试代码和第三方代码 | 没有过滤路径 | 在 lcov --remove 或 gcovr --exclude 中排除 |
| 多次运行结果越来越高 | 没有清理旧 .gcda |
执行前删除 .gcda 或使用 lcov --zerocounters |
| 只保存了 HTML,无法做趋势分析 | 缺少 XML、JSON 或 .info |
同时归档机器可读报告 |
总结
测试执行前应确认构建、路径、权限、环境变量和旧数据状态;测试执行后应收集结果 XML、日志、.gcno、.gcda、覆盖率中间文件、HTML 报告和必要的诊断文件。尤其在覆盖率场景中,GCOV_PREFIX 和 GCOV_PREFIX_STRIP 应在运行测试前明确设置和验证,否则 .gcda 很可能无法生成或无法被报告工具正确收集。