CMake ctest
CMake学习–ctest全面介绍
1. 环境准备
确保已安装 cmake
和编译工具:
sudo apt update
sudo apt install cmake build-essential
2. 创建示例项目
假设我们要测试一个简单的数学函数 add()
,项目结构如下:
math_project/
├── CMakeLists.txt
├── include/
│ └── math.h
├── src/
│ └── math.cpp
└── tests/└── test_math.cpp
文件内容:
- include/math.h(函数声明):
#ifndef MATH_H
#define MATH_Hint add(int a, int b);#endif
- src/math.cpp(函数实现):
#include "math.h"int add(int a, int b) {return a + b;
}
- tests/test_math.cpp(测试代码):
#include "math.h"
#include <iostream>int main() {if (add(2, 3) != 5) {std::cerr << "错误:2 + 3 应该等于 5,但实际得到 " << add(2, 3) << std::endl;return 1; // 返回非零表示测试失败}return 0; // 返回零表示测试成功
}
3. 编写 CMakeLists.txt
cmake_minimum_required(VERSION 3.10)
project(MathProject)# 添加主库
add_library(math_lib src/math.cpp)
target_include_directories(math_lib PUBLIC include)# 启用测试功能
enable_testing()# 添加测试可执行文件
add_executable(test_math tests/test_math.cpp)
target_link_libraries(test_math math_lib)# 注册测试用例
add_test(NAME math_test COMMAND test_math)
4. 构建并运行测试
# 创建构建目录
mkdir build
cd build# 生成 Makefile
cmake ..# 编译项目
make# 运行所有测试
ctest# 详细输出(可选)
ctest --verbose
5. 输出示例
如果一切正常,你会看到类似以下输出:
Test project /path/to/buildStart 1: math_test
1/1 Test #1: math_test ........................ Passed 0.00 sec100% tests passed, 0 tests failed out of 1
如果测试失败(比如修改 add
函数返回错误值),输出会显示:
1: 错误:2 + 3 应该等于 5,但实际得到 6
1/1 Test #1: math_test ........................***Failed 0.00 sec0% tests passed, 1 tests failed out of 1
6. 高级用法
- 并行测试:使用
ctest -j N
(N
是线程数)。 - 过滤测试:用
-R
按名称过滤,如ctest -R math
。 - 测试超时:在
add_test
后设置超时:set_tests_properties(math_test PROPERTIES TIMEOUT 10)
7. 使用测试框架(如 Google Test)
如果需要更复杂的测试,可以集成 Google Test:
# 查找 GTest
find_package(GTest REQUIRED)# 添加测试可执行文件
add_executable(gtest_test tests/gtest_test.cpp)
target_link_libraries(gtest_test math_lib GTest::GTest GTest::Main)# 注册测试
add_test(NAME gtest_test COMMAND gtest_test)
通过这个示例,你可以在 Ubuntu 22.04 下快速上手 CMake 的测试功能。实际开发中只需替换为自己的项目和测试逻辑即可。
好的!CMake 的 CTest 是一个用于管理和运行测试的工具,它可以与 CMake 项目无缝集成,自动化测试流程。下面我会从基础到实际用法详细讲解,结合示例帮助你快速上手。
一、CTest 基础概念
-
作用:
- 批量运行项目中定义的测试用例。
- 输出测试结果(通过/失败/耗时等)。
- 支持并行测试、过滤测试、重试失败测试等高级功能。
-
依赖:
- 测试用例需要编译为可执行文件(例如
my_test
)。 - 在
CMakeLists.txt
中通过enable_testing()
和add_test()
注册测试。
- 测试用例需要编译为可执行文件(例如
二、快速入门示例
1. 创建项目
假设项目结构如下:
demo/
├── CMakeLists.txt
└── tests/└── test_demo.cpp
2. 编写测试代码 (test_demo.cpp
)
#include <iostream>int main() {int result = 1 + 1;if (result != 2) {std::cerr << "测试失败: 1+1 应该等于 2,但实际得到 " << result << std::endl;return 1; // 非零返回值表示测试失败}return 0; // 零表示成功
}
3. 编写 CMakeLists.txt
cmake_minimum_required(VERSION 3.10)
project(DemoTest)enable_testing() # 启用测试功能# 添加测试可执行文件
add_executable(test_demo tests/test_demo.cpp)# 注册测试用例
add_test(NAME my_first_test COMMAND test_demo)
4. 构建并运行测试
mkdir build && cd build
cmake ..
make
ctest # 运行所有测试
输出结果:
Test project /path/to/buildStart 1: my_first_test
1/1 Test #1: my_first_test .............. Passed 0.00 sec100% tests passed, 0 tests failed out of 1
三、CTest 常用命令和选项
1. 基础命令
-
运行所有测试:
ctest
-
显示详细输出:
ctest --verbose # 或 -V
-
并行运行测试(例如使用 4 个线程):
ctest -j4
-
仅运行特定测试(按名称匹配):
ctest -R my_first_test # 正则表达式匹配测试名
-
排除某些测试:
ctest -E another_test # 排除名为 another_test 的测试
-
重试失败的测试:
ctest --rerun-failed # 仅重新运行之前失败的测试
2. 测试输出和日志
-
查看测试日志:
cat Testing/Temporary/LastTest.log
-
生成 XML 格式报告(便于 CI 工具解析):
ctest --output-on-failure --output-junit results.xml
3. 测试超时和重试
在 CMakeLists.txt
中可以为测试设置超时时间:
add_test(NAME my_test COMMAND test_demo)
set_tests_properties(my_test PROPERTIES TIMEOUT 5) # 超时 5 秒
四、进阶用法:多测试用例与参数传递
1. 定义多个测试用例
修改 CMakeLists.txt
:
add_test(NAME test_add COMMAND test_demo add)
add_test(NAME test_sub COMMAND test_demo sub)
对应的测试代码需要解析命令行参数:
int main(int argc, char* argv[]) {if (argc < 2) return 1;std::string mode = argv[1];if (mode == "add") {// 测试加法} else if (mode == "sub") {// 测试减法}return 0;
}
2. 运行特定测试
ctest -R test_add # 仅运行 test_add
五、集成测试框架(如 Google Test)
CTest 可以与其他测试框架(如 Google Test、Catch2)配合使用。以 Google Test 为例:
1. 安装 Google Test
sudo apt install libgtest-dev # Ubuntu
2. 修改 CMakeLists.txt
find_package(GTest REQUIRED) # 查找 GTestadd_executable(gtest_test tests/gtest_test.cpp)
target_link_libraries(gtest_test PRIVATE GTest::GTest GTest::Main)add_test(NAME gtest_example COMMAND gtest_test)
3. 测试代码 (gtest_test.cpp
)
#include <gtest/gtest.h>TEST(MathTest, Add) {EXPECT_EQ(1 + 1, 2);
}int main(int argc, char** argv) {testing::InitGoogleTest(&argc, argv);return RUN_ALL_TESTS();
}
4. 运行测试
ctest --verbose -R gtest_example
六、实际应用场景
- 自动化测试:在 CI/CD 中通过
ctest
自动验证代码。 - 调试失败测试:结合
--verbose
和日志文件定位问题。 - 性能测试:通过超时设置和耗时统计优化代码。
七、总结
- 核心步骤:
- 在
CMakeLists.txt
中注册测试 (add_test
)。 - 通过
ctest
命令运行测试。
- 在
- 关键选项:
-j
(并行)、-R
(过滤)、--rerun-failed
(重试失败)。 - 扩展性:支持自定义测试逻辑和第三方框架。
现在可以尝试在自己的项目中添加测试,体验自动化测试的便利性!
好的!enable_testing()
和 add_test()
是 CMake 中测试功能的核心命令,下面我会详细解释它们的用法,并结合具体示例说明。
一、enable_testing()
:启用测试功能
作用
- 激活测试模块:告诉 CMake 项目需要支持测试功能,后续才能使用
add_test()
注册测试。 - 必须调用:只有在调用
enable_testing()
后,add_test()
注册的测试才能被 CTest 识别。
语法
enable_testing()
注意
- 位置:通常放在
CMakeLists.txt
的顶层,或至少在所有add_test()
之前。 - 作用域:对整个 CMake 项目有效,无需重复调用。
二、add_test()
:注册测试用例
作用
- 定义测试:将一个可执行文件(或其他命令)注册为测试用例。
- 关联到项目:CTest 运行时可以批量执行这些测试。
语法
add_test(NAME <测试名称> # 自定义测试的唯一标识符(用于过滤、报告)COMMAND <可执行文件> # 要运行的命令(通常是编译后的测试程序)[WORKING_DIRECTORY <路径>] # 指定运行测试的工作目录(可选)[CONFIGURATIONS <Debug|Release...>] # 指定生效的构建类型(可选)
)
三、完整用法示例
1. 项目结构
demo/
├── CMakeLists.txt
├── src/
│ └── main.cpp
└── tests/└── test_demo.cpp
2. 测试代码 (tests/test_demo.cpp
)
#include <iostream>int main() {int result = 2 + 3;if (result != 5) {std::cerr << "测试失败: 2+3 应该等于 5,实际得到 " << result << std::endl;return 1; // 非零返回值表示失败}return 0; // 零表示成功
}
3. CMakeLists.txt
cmake_minimum_required(VERSION 3.10)
project(DemoTest)# 添加主程序(可选,这里仅为示例)
add_executable(main src/main.cpp)# 启用测试功能(必须!)
enable_testing()# 添加测试可执行文件
add_executable(test_demo tests/test_demo.cpp)# 注册测试用例
add_test(NAME arithmetic_test # 测试名称COMMAND test_demo # 要运行的命令(即编译后的可执行文件)
)# 可选:设置测试属性(例如超时时间)
set_tests_properties(arithmetic_test PROPERTIES TIMEOUT 10)
四、关键细节说明
1. COMMAND
参数
- 可执行文件:通常是项目中通过
add_executable()
编译的程序。 - 传递参数:可以向测试程序传递命令行参数:
add_test(NAME my_test COMMAND test_demo arg1 arg2)
- 非可执行文件:甚至可以运行脚本或系统命令:
add_test(NAME system_test COMMAND bash -c "echo Hello")
2. 测试名称 (NAME
)
- 唯一性:名称在项目中必须唯一。
- 过滤测试:通过
ctest -R <名称>
运行特定测试。
3. 测试属性
通过 set_tests_properties()
可以设置测试的额外行为:
set_tests_properties(arithmetic_testPROPERTIESTIMEOUT 5 # 超时时间(秒)WORKING_DIRECTORY "${CMAKE_BINARY_DIR}" # 运行目录LABELS "quick" # 标签(用于分类)
)
五、完整操作流程
1. 构建项目
mkdir build && cd build
cmake ..
make
2. 运行测试
ctest# 输出示例:
Test project /path/to/buildStart 1: arithmetic_test
1/1 Test #1: arithmetic_test .............. Passed 0.00 sec
100% tests passed.
3. 查看详细日志
ctest --verbose# 输出示例:
1: Test command: /path/to/build/test_demo
1: Test timeout computed to be: 10
1: 测试失败: 2+3 应该等于 5,实际得到 6
1/1 Test #1: arithmetic_test ..............***Failed 0.00 sec
六、常见问题解决
1. 测试未找到可执行文件
- 原因:未正确链接或编译测试程序。
- 解决:确保
add_executable(test_demo ...)
被正确调用。
2. 测试返回码被忽略
- CMake 约定:测试程序返回 0 表示成功,非零表示失败。
- 验证:手动运行测试程序检查返回值:
./test_demo echo $? # 输出 0 表示成功
3. 测试名称冲突
- 错误:
add_test
时出现重复的NAME
。 - 解决:确保每个测试名称唯一。
七、进阶用法
1. 多个测试用例
# 添加多个测试
add_test(NAME test_add COMMAND test_demo add)
add_test(NAME test_sub COMMAND test_demo sub)
2. 依赖其他测试
# 设置测试依赖(需 CMake 3.7+)
set_tests_properties(test_sub PROPERTIES DEPENDS test_add)
3. 集成测试框架(如 Google Test)
find_package(GTest REQUIRED)
add_executable(gtest_test tests/gtest_test.cpp)
target_link_libraries(gtest_test PRIVATE GTest::GTest GTest::Main)
add_test(NAME gtest_demo COMMAND gtest_test)
八、总结
enable_testing()
:必须调用,通常在CMakeLists.txt
的顶层。add_test()
:注册测试,核心是定义NAME
和COMMAND
。- 测试属性:通过
set_tests_properties()
控制超时、目录等。
通过这个流程,你可以将任意可执行文件(包括单元测试、脚本等)集成到 CMake 的测试框架中,并通过 ctest
统一管理。
你的观察非常准确!CMake 和 CTest 确实提供了更灵活的命令行操作方式,可以避免频繁切换目录,甚至实现“一气呵成”的构建和测试流程。下面我会详细介绍几种高效用法:
一、更快捷的构建和测试方式
1. 构建阶段:使用 cmake --build
替代 make
直接在项目根目录运行,无需进入 build
目录:
# 生成构建系统(仅首次需要)
cmake -B build -S . # -B 指定构建目录,-S 指定源码目录# 编译项目(自动调用 make/ninja 等)
cmake --build build # 相当于在 build 目录执行 make
2. 测试阶段:使用 ctest --test-dir
(CMake 3.20+)
直接在项目根目录运行测试:
ctest --test-dir build # 自动在 build 目录中查找测试
3. 组合命令:构建后立即测试
cmake --build build && ctest --test-dir build
二、完整操作流程示例
假设项目结构如下:
mycpp/
├── CMakeLists.txt
└── tests/└── test_demo.cpp
1. 首次配置和构建:
# 生成构建系统并编译
cmake -B build -S . # 生成构建文件到 build 目录
cmake --build build # 编译项目
2. 后续迭代开发:
# 修改代码后,只需重新编译和测试
cmake --build build && ctest --test-dir build --verbose
三、进阶用法
1. 并行构建和测试
cmake --build build --parallel 4 # 4 线程编译
ctest --test-dir build --parallel 4 # 4 线程并行测试
2. 过滤测试用例
# 仅运行名称包含 "math" 的测试
ctest --test-dir build -R math
3. 生成测试报告
ctest --test-dir build --output-junit report.xml # 生成 JUnit 格式报告
4. 清理构建产物
cmake --build build --target clean # 清理编译文件
四、跨平台兼容性
这些命令是跨平台的,适用于:
- Linux/macOS:自动调用
make
或ninja
- Windows:自动调用
MSBuild
或ninja
- 其他生成器:如 Xcode、Eclipse 等
五、自动化脚本示例
你可以将常用操作封装为脚本(如 run.sh
):
#!/bin/bash
set -e # 遇到错误立即退出# 配置和构建
cmake -B build -S .
cmake --build build --parallel# 运行测试
ctest --test-dir build --output-on-failure# 可选:生成覆盖率报告等后续操作
赋予执行权限后直接运行:
chmod +x run.sh
./run.sh
六、注意事项
-
CMake 版本要求:
--test-dir
需要 CMake 3.20+,若版本过低可升级:# Ubuntu 升级 CMake sudo apt install cmake --upgrade
- 旧版本 CTest 需进入构建目录操作。
-
构建目录命名:
- 你可以自由命名构建目录(如
cmake -B mybuild
),但需保持一致性。
- 你可以自由命名构建目录(如
-
IDE 集成:
- 大多数 IDE(如 VS Code、CLion)已内置这些命令的快捷操作。
七、完整示例验证
步骤 1:创建项目
mkdir -p mycpp/tests
cd mycpp
步骤 2:编写 CMakeLists.txt
cmake_minimum_required(VERSION 3.20)
project(MyDemo)enable_testing()add_executable(test_demo tests/test_demo.cpp)
add_test(NAME demo_test COMMAND test_demo)
步骤 3:编写测试代码 tests/test_demo.cpp
#include <iostream>int main() {if (1 + 1 == 2) {std::cout << "测试通过!" << std::endl;return 0;} else {std::cerr << "测试失败!" << std::endl;return 1;}
}
步骤 4:运行完整流程
# 在项目根目录执行
cmake -B build
cmake --build build
ctest --test-dir build --verbose
输出示例:
...
1: 测试通过!
1/1 Test #1: demo_test .............. Passed 0.00 sec
通过这种方式,你可以完全避免手动切换目录,实现更高效的开发测试循环!
你提到的 cmake --build build --target test
是一个常见的误解,它的行为取决于 CMake 生成的具体构建系统(如 Makefile 或 Ninja)。下面我会详细解释它的含义、潜在问题,并提供更推荐的用法。
1. cmake --build build --target test
的含义
--build build
:指定在build
目录中执行构建操作。--target test
:尝试构建名为test
的目标(类似于make test
)。
预期行为:
- 如果构建系统(如 Makefile)中存在名为
test
的目标,该命令会执行该目标定义的操作(例如运行测试)。 - 但默认情况下,CMake 不会生成名为
test
的构建目标,除非你在CMakeLists.txt
中手动定义了它。
2. 为什么这个命令可能无效?
默认情况下:
- CMake 不会生成
test
目标:当你调用enable_testing()
和add_test()
时,CMake 会生成CTestTestfile.cmake
,但不会创建test
构建目标。 - 历史兼容性:某些旧项目或教程可能通过
add_custom_target(test ...)
手动创建了test
目标,但这并非 CMake 的官方推荐做法。
典型错误输出:
cmake --build build --target test
# 输出可能为:
# make: *** No rule to make target 'test'. Stop.
# 或 ninja: error: unknown target 'test'
3. 官方推荐测试方式
CMake 的测试功能应通过 ctest
命令运行,而非构建目标。以下是正确用法:
直接运行所有测试:
ctest --test-dir build # CMake 3.20+ 支持,推荐!
# 或
cd build && ctest && cd .. # 传统方式
构建后立即测试:
cmake --build build && ctest --test-dir build
4. 如何让 --target test
生效?
如果你坚持想通过 cmake --build build --target test
触发测试,需要手动在 CMakeLists.txt
中添加自定义目标:
修改 CMakeLists.txt
:
enable_testing()# 添加一个名为 "test" 的自定义目标,关联到 ctest
add_custom_target(testCOMMAND ${CMAKE_CTEST_COMMAND} --output-on-failureWORKING_DIRECTORY ${CMAKE_BINARY_DIR}COMMENT "Running tests..."
)
操作效果:
- 此时
cmake --build build --target test
会调用ctest
。 - 但此操作冗余,直接使用
ctest
更简洁。
5. 为什么推荐用 ctest
而不是 --target test
?
- 标准化:
ctest
是 CMake 官方测试工具,与add_test()
无缝集成。 - 功能丰富:支持过滤测试 (
-R
)、并行测试 (-j
)、重试失败 (--rerun-failed
) 等。 - 跨平台一致性:无论底层是 Makefile、Ninja 还是 Visual Studio,
ctest
用法一致。
6. 完整操作示例
项目结构:
myproject/
├── CMakeLists.txt
└── tests/└── test_demo.cpp
CMakeLists.txt
:
cmake_minimum_required(VERSION 3.10)
project(MyProject)enable_testing()add_executable(test_demo tests/test_demo.cpp)
add_test(NAME my_test COMMAND test_demo)
test_demo.cpp
:
#include <iostream>
int main() {std::cout << "测试通过!" << std::endl;return 0;
}
构建并测试:
# 生成构建系统
cmake -B build# 编译项目
cmake --build build# 运行测试(推荐方式)
ctest --test-dir build --output-on-failure
7. 总结
- 避免使用
cmake --build build --target test
,除非你明确添加了自定义的test
目标。 - 始终优先使用
ctest --test-dir build
(CMake 3.20+)或cd build && ctest
。 - 测试配置的核心是
enable_testing()
和add_test()
,与构建目标无关。
--output-on-failure
是 CTest 的一个常用选项,它的作用是 在测试失败时自动输出测试程序的详细日志。这个选项可以帮助你快速定位测试失败的原因,而无需手动查看日志文件。
具体行为
-
默认情况下:
- 如果测试通过,CTest 只会显示
Passed
,不会打印测试程序的输出(例如stdout
或stderr
)。 - 如果测试失败,CTest 只会显示
Failed
,但仍然不显示失败时的详细输出(你可能需要手动查看日志)。
- 如果测试通过,CTest 只会显示
-
使用
--output-on-failure
后:- 如果测试通过,行为和默认一致(不打印输出)。
- 如果测试失败,自动打印该测试的所有输出(包括
stdout
和stderr
),直接显示在终端中。
对比其他选项
-
--verbose
(或-V
):- 无论测试是否通过,都会打印所有测试的完整输出。
- 适合需要实时跟踪所有测试细节的场景(但输出可能非常多)。
-
--output-on-failure
:- 仅在测试失败时打印输出。
- 适合大多数调试场景,既能减少信息干扰,又能快速定位失败原因。
使用示例
# 运行测试,仅在失败时打印详细日志
ctest --test-dir build --output-on-failure# 组合其他选项(如并行测试)
ctest --test-dir build --output-on-failure -j4 # 4 线程并行运行
实际场景演示
假设有一个测试程序 test_math
,其代码逻辑如下:
#include <iostream>
int main() {std::cout << "开始测试..." << std::endl;if (1 + 1 != 2) {std::cerr << "错误:1+1 不等于 2" << std::endl;return 1; // 失败}std::cout << "测试通过!" << std::endl;return 0; // 成功
}
1. 测试失败时的输出
$ ctest --test-dir build --output-on-failure
...
1/1 Test #1: math_test .....................***Failed 0.01 sec输出:
开始测试...
错误:1+1 不等于 2
2. 测试成功时的输出
$ ctest --test-dir build --output-on-failure
...
1/1 Test #1: math_test ..................... Passed 0.01 sec
(没有多余输出)
为什么要用它?
- 减少干扰:成功时不打印冗余信息,保持终端干净。
- 快速调试:失败时直接看到错误输出,无需手动查找日志文件。
- 自动化友好:在 CI/CD 中结合
--output-junit
生成报告时,失败日志会被自动捕获。
查看完整日志
如果需要查看所有测试的完整日志(无论成功与否),可以用:
ctest --test-dir build --verbose
总结
--output-on-failure
:仅在失败时打印日志,是调试测试的黄金选项。--verbose
:无条件打印所有日志,适合深度调试。- 结合使用:在自动化脚本中,可以同时生成 JUnit 报告和保留失败日志:
ctest --test-dir build --output-on-failure --output-junit test-results.xml