23、CPack与CTest集成:在打包前自动运行测试
说实话,我见过太多这样的场景:开发团队辛辛苦苦打了包,结果用户一安装就报错。排查半天,发现是某个单元测试没跑过,代码里有明显的逻辑错误。嗯,这种尴尬我经历过不止一次。
所以这一章,我们来聊聊怎么让CPack在打包之前,自动把CTest跑一遍。说白了,就是给打包流程加一道「安检门」。
为什么要在打包前跑测试?
你想想看,打包的目的是什么?是交付。交付的东西如果连基本测试都过不了,那跟废品有什么区别?
我个人习惯的做法是:测试不通过,坚决不打。这个原则听起来简单,但真正落地需要工具链的支持。CPack和CTest的集成,就是帮你把这个原则变成自动化流程。
方案一:用CMake脚本控制流程
最直接的方式,是在你的打包脚本里显式调用CTest。我一般会在CMakeLists.txt里这样写:
# 启用测试
enable_testing()
# 添加测试用例
add_test(NAME test_core COMMAND test_core)
add_test(NAME test_io COMMAND test_io)
# 自定义打包目标,先测试再打包
add_custom_target(package_with_test
COMMAND ${CMAKE_CTEST_COMMAND} --output-on-failure
COMMAND ${CMAKE_CPACK_COMMAND}
DEPENDS ${PROJECT_NAME}
COMMENT "Running tests before packaging..."
)
然后你运行make package_with_test,它就会先跑测试,再打包。测试失败了,打包就不会执行。
--output-on-failure参数,测试失败时能看到详细输出。我在调试复杂项目时,这个参数救过我很多次。
方案二:利用CPack预安装钩子
如果你用的是CPack的预安装机制,可以在CPackConfig.cmake里加一段逻辑:
# 在安装前运行测试
if(CPACK_INSTALL_CMAKE_PROJECTS)
# 遍历所有要安装的项目
foreach(proj ${CPACK_INSTALL_CMAKE_PROJECTS})
# 提取项目路径
list(GET proj 0 proj_path)
# 在该路径下运行测试
execute_process(
COMMAND ${CMAKE_CTEST_COMMAND}
WORKING_DIRECTORY ${proj_path}
RESULT_VARIABLE test_result
)
if(NOT test_result EQUAL 0)
message(FATAL_ERROR "Tests failed for ${proj_path}, aborting package generation.")
endif()
endforeach()
endif()
这段代码会在CPack执行安装步骤之前,遍历所有要安装的子项目,逐个跑测试。任何一个子项目测试失败,整个打包过程就停掉。
ALL依赖里。
CPACK_INSTALL_CMAKE_PROJECTS 的使用场景
这个变量,说白了就是告诉CPack:「我要安装哪些项目,以及它们分别在哪」。它的典型结构是这样的:
set(CPACK_INSTALL_CMAKE_PROJECTS
"/path/to/build1;project1;ALL;/"
"/path/to/build2;project2;runtime;/usr/local"
)
每一行包含四个字段:
| 字段 | 含义 | 示例 |
|---|---|---|
| 构建目录 | 项目编译的路径 | /path/to/build1 |
| 项目名称 | CMake项目名 | project1 |
| 安装组件 | ALL 或具体组件名 | ALL |
| 安装前缀 | 目标安装路径 | / 或 /usr/local |
我在实际项目中遇到过这样的场景:一个产品包含核心库、插件和工具集三个子项目。每个子项目独立构建,但最终要打包成一个安装包。这时候CPACK_INSTALL_CMAKE_PROJECTS就派上用场了:
set(CPACK_INSTALL_CMAKE_PROJECTS
"${CMAKE_BINARY_DIR}/core;CoreLib;ALL;/opt/myapp"
"${CMAKE_BINARY_DIR}/plugins;Plugins;ALL;/opt/myapp/plugins"
"${CMAKE_BINARY_DIR}/tools;Tools;runtime;/opt/myapp/bin"
)
这样CPack就会依次安装这三个项目,最终生成一个完整的安装包。
集成测试与打包的完整流程
下面这张图展示了整个流程的逻辑:
避坑指南
我曾经在一个大型项目中踩过一个坑:测试依赖外部数据库,但打包环境里没有数据库服务。结果CTest跑了一堆连接超时,打包流程卡了半小时。
后来我学乖了,在测试脚本里加了环境检测:
if(NOT DEFINED ENV{DB_HOST})
message(WARNING "Database not available, skipping integration tests")
return()
endif()
另外还有一个常见问题:测试超时。默认CTest没有超时限制,如果某个测试卡住了,整个打包流程就死在那了。我建议给每个测试加个超时:
add_test(NAME test_performance COMMAND test_performance)
set_tests_properties(test_performance PROPERTIES TIMEOUT 30)
30秒跑不完?那这个测试大概率有问题,直接标记失败,别让它拖累打包。
总结一下
CPack和CTest的集成,本质上就是给打包流程加了一道质量门。我个人觉得,这是CI/CD流水线里最值得投入的一环。你想想看,与其让用户发现bug,不如在打包阶段就把它拦住。
至于CPACK_INSTALL_CMAKE_PROJECTS,它解决的是多项目协同打包的问题。当你需要把多个独立构建的项目塞进一个安装包时,这个变量就是你的最佳拍档。