CMake CTest 测试
CTest 是 CMake 自带的测试工具,它与 CMake 构建系统紧密集成,可以方便地管理、执行和报告测试结果。本章将详细介绍如何使用 CTest 进行自动化测试。
CTest 简介
什么是 CTest?
CTest 是 CMake 套件的一部分,专门用于:
- 测试管理:定义和组织测试用例
- 测试执行:运行测试并收集结果
- 结果报告:生成测试报告和统计信息
- 持续集成:与 CDash 等仪表板系统集成
为什么使用 CTest?
- 原生集成:与 CMake 无缝配合
- 跨平台:支持 Windows、Linux、macOS
- 灵活性:支持多种测试框架
- 并行执行:自动并行运行测试
- 丰富功能:标签、依赖、超时、重试等
基本使用
启用测试
在 CMakeLists.txt 中启用测试功能:
cmake_minimum_required(VERSION 3.15)
project(MyProject)
# 启用测试
enable_testing()
# 或者使用更现代的方式
include(CTest)
两种方式的区别:
| 命令 | 效果 |
|---|---|
enable_testing() | 仅启用 add_test() 命令 |
include(CTest) | 启用测试并添加更多功能(如内存检查) |
添加简单测试
使用 add_test() 添加测试:
# 创建可执行文件
add_executable(myapp src/main.cpp)
add_executable(test_myapp tests/test_main.cpp)
# 添加测试
add_test(NAME MyTest COMMAND test_myapp)
运行测试
# 进入构建目录
cd build
# 运行所有测试
ctest
# 详细输出
ctest -V
# 更详细的输出
ctest -VV
测试定义详解
add_test 语法
add_test(NAME <name> COMMAND <command> [<arg>...]
[CONFIGURATIONS <config>...]
[WORKING_DIRECTORY <dir>]
[COMMAND_EXPAND_LISTS])
参数说明
NAME
测试的名称,用于识别和筛选:
add_test(NAME Unit.Math COMMAND math_tests)
add_test(NAME Unit.String COMMAND string_tests)
add_test(NAME Integration.API COMMAND api_tests)
COMMAND
要执行的命令,可以是可执行文件或内置命令:
# 执行可执行文件
add_test(NAME MyTest COMMAND my_test_exe)
# 执行 Python 脚本
add_test(NAME PyTest COMMAND python3 ${CMAKE_SOURCE_DIR}/tests/test.py)
# 使用生成器表达式
add_test(NAME MyTest COMMAND $<TARGET_FILE:my_test_exe>)
CONFIGURATIONS
限制测试在特定构建配置下运行:
add_test(NAME DebugTest COMMAND debug_test
CONFIGURATIONS Debug
)
add_test(NAME ReleaseTest COMMAND release_test
CONFIGURATIONS Release
)
WORKING_DIRECTORY
设置测试的工作目录:
add_test(NAME FileTest COMMAND file_test
WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}/test_data
)
使用目标作为测试命令
推荐使用目标名称而非文件路径:
# 推荐:使用目标名称
add_test(NAME MyTest COMMAND my_test_target)
# 不推荐:使用文件路径
add_test(NAME MyTest COMMAND ${CMAKE_BINARY_DIR}/my_test_exe)
原因:目标名称在不同生成器下都能正确工作,包括多配置生成器(如 Visual Studio)。
测试属性
使用 set_tests_properties() 设置测试属性:
超时设置
add_test(NAME LongTest COMMAND long_running_test)
set_tests_properties(LongTest PROPERTIES
TIMEOUT 300 # 5 分钟超时
)
超时后测试会被终止并标记为失败。
标签
为测试添加标签,便于分组执行:
add_test(NAME UnitTest1 COMMAND test1)
set_tests_properties(UnitTest1 PROPERTIES
LABELS "unit;quick"
)
add_test(NAME IntegrationTest COMMAND integration_test)
set_tests_properties(IntegrationTest PROPERTIES
LABELS "integration;slow"
)
依赖关系
定义测试之间的依赖:
add_test(NAME Setup COMMAND setup_script)
add_test(NAME DataTest COMMAND data_test)
set_tests_properties(DataTest PROPERTIES
DEPENDS Setup
)
依赖测试会按顺序执行,如果前置测试失败,后续测试会被跳过。
环境变量
为测试设置环境变量:
add_test(NAME EnvTest COMMAND env_test)
set_tests_properties(EnvTest PROPERTIES
ENVIRONMENT "MY_VAR=value;PATH=/custom/path:$ENV{PATH}"
)
跳过测试
根据条件跳过测试:
add_test(NAME LinuxOnlyTest COMMAND linux_test)
set_tests_properties(LinuxOnlyTest PROPERTIES
SKIP_REGULAR_EXPRESSION "SKIP"
)
if(NOT UNIX)
set_tests_properties(LinuxOnlyTest PROPERTIES
DISABLED TRUE
)
endif()
测试输出验证
通过检查输出来判断测试结果:
add_test(NAME OutputTest COMMAND output_test)
set_tests_properties(OutputTest PROPERTIES
PASS_REGULAR_EXPRESSION "All tests passed"
FAIL_REGULAR_EXPRESSION "ERROR;FAILED"
SKIP_REGULAR_EXPRESSION "SKIP"
)
使用 Google Test
集成 Google Test
使用 FetchContent 下载 Google Test:
cmake_minimum_required(VERSION 3.15)
project(MyProject)
include(FetchContent)
FetchContent_Declare(
googletest
GIT_REPOSITORY https://github.com/google/googletest.git
GIT_TAG v1.14.0
)
FetchContent_MakeAvailable(googletest)
enable_testing()
add_executable(my_tests
tests/test_main.cpp
tests/test_math.cpp
)
target_link_libraries(my_tests PRIVATE GTest::gtest_main)
# 自动注册所有测试
include(GoogleTest)
gtest_discover_tests(my_tests)
Google Test 示例代码
tests/test_math.cpp:
#include <gtest/gtest.h>
// 被测试的函数
int add(int a, int b) {
return a + b;
}
// 测试用例
TEST(MathTest, AddPositiveNumbers) {
EXPECT_EQ(add(2, 3), 5);
}
TEST(MathTest, AddNegativeNumbers) {
EXPECT_EQ(add(-1, -1), -2);
}
TEST(MathTest, AddZero) {
EXPECT_EQ(add(0, 5), 5);
}
gtest_discover_tests 选项
gtest_discover_tests(my_tests
# 测试名称前缀
PROPERTIES LABELS "unit"
# 超时设置
TIMEOUT 60
# 额外参数
EXTRA_ARGS --gtest_output=xml:test_results/
# 测试列表超时
DISCOVERY_TIMEOUT 30
)
使用 Catch2
集成 Catch2
include(FetchContent)
FetchContent_Declare(
Catch2
GIT_REPOSITORY https://github.com/catchorg/Catch2.git
GIT_TAG v3.5.0
)
FetchContent_MakeAvailable(Catch2)
enable_testing()
add_executable(my_tests tests/test_main.cpp)
target_link_libraries(my_tests PRIVATE Catch2::Catch2WithMain)
# 自动注册测试
include(Catch)
catch_discover_tests(my_tests)
Catch2 示例代码
#include <catch2/catch_test_macros.hpp>
TEST_CASE("Addition works correctly", "[math]") {
REQUIRE(1 + 1 == 2);
REQUIRE(2 + 2 == 4);
}
TEST_CASE("Division by zero throws", "[math]") {
REQUIRE_THROWS(10 / 0);
}
SECTION("Nested sections") {
SECTION("First section") {
REQUIRE(true);
}
SECTION("Second section") {
REQUIRE(true);
}
}
运行测试
基本命令
# 运行所有测试
ctest
# 指定构建目录
ctest --test-dir build
# 显示进度
ctest --progress
详细输出
# 详细输出
ctest -V
# 更详细的输出
ctest -VV
# 仅失败时输出
ctest --output-on-failure
# 失败时停止
ctest --stop-on-failure
筛选测试
# 按名称匹配
ctest -R MathTest # 正则匹配测试名
ctest -R "Unit.*" # 正则表达式
# 排除测试
ctest -E Integration # 排除名称包含 Integration 的测试
# 按标签筛选
ctest -L unit # 运行标签为 unit 的测试
ctest -LE slow # 排除标签为 slow 的测试
# 多个标签(AND 关系)
ctest -L unit -L quick
并行执行
# 并行运行(自动检测 CPU 核心数)
ctest -j
# 指定并行数
ctest -j4
# 使用环境变量
CTEST_PARALLEL_LEVEL=4 ctest
构建配置
对于多配置生成器(如 Visual Studio):
# 指定配置
ctest -C Debug
ctest -C Release
重试失败的测试
# 重新运行失败的测试
ctest --rerun-failed
# 重复运行直到通过
ctest --repeat until-pass:5
# 重复运行直到失败(用于检测不稳定测试)
ctest --repeat until-fail:10
高级功能
测试固件(Fixtures)
测试固件用于管理测试的设置和清理:
# 设置固件
add_test(NAME SetupDB COMMAND setup_database)
set_tests_properties(SetupDB PROPERTIES
FIXTURES_SETUP DB
)
# 清理固件
add_test(NAME CleanupDB COMMAND cleanup_database)
set_tests_properties(CleanupDB PROPERTIES
FIXTURES_CLEANUP DB
)
# 使用固件的测试
add_test(NAME DBTest1 COMMAND db_test1)
set_tests_properties(DBTest1 PROPERTIES
FIXTURES_REQUIRED DB
)
固件执行顺序:
- SetupDB
- DBTest1
- CleanupDB
如果 SetupDB 失败,DBTest1 会被跳过,CleanupDB 仍会执行。
资源分配
限制测试使用的资源:
add_test(NAME GPUTest COMMAND gpu_test)
set_tests_properties(GPUTest PROPERTIES
RESOURCE_LOCK gpu
)
add_test(NAME AnotherGPUTest COMMAND another_gpu_test)
set_tests_properties(AnotherGPUTest PROPERTIES
RESOURCE_LOCK gpu
)
使用相同资源锁的测试不会并行执行。
自定义测试命令
编写自定义测试脚本:
tests/run_test.py:
#!/usr/bin/env python3
import subprocess
import sys
# 运行程序
result = subprocess.run(['./myapp', '--test'], capture_output=True)
# 检查输出
if b'All tests passed' in result.stdout:
sys.exit(0)
else:
print("Test failed:", result.stdout.decode())
sys.exit(1)
CMakeLists.txt:
find_package(Python3 REQUIRED)
add_test(NAME CustomTest
COMMAND Python3::Interpreter ${CMAKE_SOURCE_DIR}/tests/run_test.py
)
测试报告
JUnit 格式输出
# 生成 JUnit XML 报告
ctest --output-junit test_results.xml
输出到文件
# 输出日志
ctest -O test_log.txt
# 仅失败时输出到文件
ctest --output-on-failure -O failures.txt
配置测试输出大小
# 限制通过测试的输出大小
set(CMAKE_CTEST_OUTPUT_ON_FAILURE ON)
# 命令行控制
ctest --test-output-size-passed 1024 # 1KB
ctest --test-output-size-failed 10240 # 10KB
CDash 集成
CTest 可以将测试结果提交到 CDash 仪表板:
配置 CTest
CTestConfig.cmake:
set(CTEST_PROJECT_NAME "MyProject")
set(CTEST_NIGHTLY_START_TIME "00:00:00 UTC")
set(CTEST_DROP_METHOD "http")
set(CTEST_DROP_SITE "my.cdash.org")
set(CTEST_DROP_LOCATION "/submit.php?project=MyProject")
set(CTEST_DROP_SITE_CDASH TRUE)
提交测试结果
# 实验性提交
ctest -D Experimental
# 每日构建
ctest -D Nightly
# 持续集成
ctest -D Continuous
最佳实践
1. 测试命名规范
# 推荐的命名模式:模块.功能.场景
add_test(NAME Calculator.Add.PositiveNumbers COMMAND ...)
add_test(NAME Calculator.Add.NegativeNumbers COMMAND ...)
add_test(NAME Calculator.Divide.ByZero COMMAND ...)
2. 合理使用标签
set_tests_properties(QuickTest PROPERTIES LABELS "quick;unit")
set_tests_properties(SlowTest PROPERTIES LABELS "slow;integration")
set_tests_properties(NightlyTest PROPERTIES LABELS "nightly")
# 开发时运行快速测试
ctest -L quick
# CI 中运行所有单元测试
ctest -L unit
# 夜间构建运行所有测试
ctest
3. 设置合理的超时
# 单元测试:快速超时
set_tests_properties(UnitTest PROPERTIES TIMEOUT 10)
# 集成测试:较长超时
set_tests_properties(IntegrationTest PROPERTIES TIMEOUT 300)
# 性能测试:允许更长时间
set_tests_properties(PerformanceTest PROPERTIES TIMEOUT 3600)
4. 测试分组
# 使用标签分组
add_test(NAME Unit.Math COMMAND ...)
set_tests_properties(Unit.Math PROPERTIES LABELS "unit;math")
add_test(NAME Unit.String COMMAND ...)
set_tests_properties(Unit.String PROPERTIES LABELS "unit;string")
add_test(NAME Integration.API COMMAND ...)
set_tests_properties(Integration.API PROPERTIES LABELS "integration;api")
5. 条件测试
# 平台特定测试
if(WIN32)
add_test(NAME WindowsTest COMMAND windows_test)
elseif(UNIX)
add_test(NAME LinuxTest COMMAND linux_test)
endif()
# 功能条件测试
if(ENABLE_GPU_TESTS)
add_test(NAME GPUTest COMMAND gpu_test)
endif()
命令参考
常用 ctest 命令选项
| 选项 | 说明 |
|---|---|
-R <regex> | 运行匹配正则的测试 |
-E <regex> | 排除匹配正则的测试 |
-L <label> | 运行指定标签的测试 |
-LE <label> | 排除指定标签的测试 |
-C <config> | 指定构建配置 |
-j <n> | 并行运行测试 |
-V | 详细输出 |
--output-on-failure | 失败时显示输出 |
--stop-on-failure | 失败时停止 |
--rerun-failed | 重运行失败的测试 |
--timeout <sec> | 设置全局超时 |
-N | 显示测试列表但不运行 |
小结
本章我们学习了:
- CTest 基础:enable_testing、add_test
- 测试属性:超时、标签、依赖、环境变量
- 测试框架集成:Google Test、Catch2
- 运行测试:筛选、并行、重试
- 高级功能:固件、资源锁、自定义脚本
- 测试报告:JUnit 格式、CDash 集成
CTest 是 CMake 项目的标准测试工具,掌握它能有效提升项目的质量保证能力。
练习
- 为一个计算器模块编写单元测试
- 使用 Google Test 或 Catch2 创建测试项目
- 配置测试标签,区分快速测试和慢速测试
- 设置测试超时和重试机制