Skip to content

测试程序执行前准备与执行后文件收集

本文记录执行 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 很可能无法生成或无法被报告工具正确收集。