第1章:测试——enable_testing、add_test、CTest基础与测试用例编写

说实话,很多做C++项目的朋友,对CMake的测试模块了解得不多。我刚开始接触CMake时,也觉得测试嘛,不就是跑几个可执行文件看看结果?后来在一个中型项目中,手动测试搞得我焦头烂额,才意识到CMake自带的CTest有多香。

这一章,我们就来聊聊CMake的测试体系。说白了,就是怎么用CMake帮你自动跑测试,省去你每次改完代码都要手动验证的麻烦。

1.1 测试的起点:enable_testing()

在CMake里启用测试,只需要一行命令:

enable_testing()

这行代码要放在顶层的CMakeLists.txt中。为什么?因为测试是全局的,不是某个子目录私有的。我见过有人把它放在子目录里,结果死活跑不起来——嗯,这是个坑。

注意:enable_testing() 必须在 add_subdirectory() 之前调用,否则子目录里的测试不会被注册。

调用之后,CMake就会在构建目录生成一个CTestTestfile.cmake文件。这个文件记录了所有测试信息,CTest就是靠它来发现测试的。

1.2 注册测试:add_test()

有了测试框架,接下来就是注册具体的测试用例。用 add_test() 命令:

add_test(NAME 测试名称 COMMAND 可执行文件 [参数...])

举个实际例子。假设你写了一个数学库,有个加法函数需要测试:

# 先编译测试程序
add_executable(test_add test_add.cpp)
target_link_libraries(test_add PRIVATE mathlib)

# 注册测试
add_test(NAME test_addition COMMAND test_add 1 2 3)

这里有个细节:COMMAND后面跟的是可执行文件路径。CMake会自动处理路径问题,你只需要写目标名称就行。

小技巧:我习惯把测试可执行文件统一放在 tests/ 目录下,然后在顶层用 enable_testing() 开启测试,再通过 add_subdirectory(tests) 引入。这样结构清晰,不会乱。

1.3 CTest基础:跑起来看看

测试注册好了,怎么跑?两种方式:

  1. 直接运行 ctest 命令:在构建目录下执行 ctest,它会自动发现所有测试并运行。
  2. 通过 make test:如果你用Makefile生成器,make test 也能触发CTest。

我个人更推荐直接用 ctest,因为参数更灵活。比如:

# 运行所有测试
ctest

# 只运行名字匹配的测试
ctest -R test_add

# 输出详细信息
ctest -V

# 并行运行,4个任务同时跑
ctest -j4

说到并行,我曾经在一个有200多个测试用例的项目里,单线程跑要等10分钟。加上 -j8 之后,2分钟就跑完了。你想想看,这效率提升多明显。

1.4 测试用例编写:从简单到实用

测试用例本身就是一个普通的可执行程序。它的核心逻辑是:运行被测试的代码,然后检查结果是否符合预期。

最简单的写法:

// test_add.cpp
#include <iostream>
#include <cstdlib>

int add(int a, int b) { return a + b; }

int main() {
    if (add(2, 3) != 5) {
        std::cerr << "FAIL: 2 + 3 should be 5" << std::endl;
        return 1;  // 返回非0表示失败
    }
    std::cout << "PASS" << std::endl;
    return 0;
}

CTest判断测试是否通过的标准很简单:返回值是否为0。返回0表示通过,非0表示失败。所以你的测试程序最后一定要 return 0 或 return 1。

核心原则:测试程序退出码为0表示通过,非0表示失败。CTest只看这个,不看标准输出。

当然,实际项目中你不会手写这么多 if 判断。一般会用测试框架,比如 Google Test、Catch2 等。但底层原理是一样的——最终都是通过返回值告诉CTest结果。

1.5 知识体系总览

为了让你更直观地理解CMake测试的整体结构,我画了一张图:

CMake测试体系结构 CMakeLists.txt enable_testing() add_test() 注册测试 CTestTestfile.cmake ctest 命令执行 测试程序返回0通过,非0失败

从这张图可以看得很清楚:enable_testing() 是开关,add_test() 是注册,CTestTestfile.cmake 是桥梁,ctest 是执行者。四者缺一不可。

1.6 实战中的避坑指南

我在实际项目中踩过不少坑,挑几个典型的说说:

  • 测试找不到可执行文件:add_test 里的 COMMAND 如果写的是相对路径,一定要确保路径正确。我建议直接用目标名称,CMake会自动处理。
  • 测试一直通过但逻辑是错的:检查你的测试程序是否真的 return 了非0值。有时候忘记写 return 1,CTest就会误判为通过。
  • 并行测试数据竞争:如果多个测试写同一个文件,并行跑时会互相干扰。解决办法是让每个测试使用独立的临时目录。
曾经踩过的坑:有一次我写了个测试,里面调用了 abort() 而不是 return 1。结果CTest报告测试通过,因为abort()的退出码在某些系统上是0。从那以后,我每次都会用 ctest -V 确认输出。

1.7 小结

这一章我们聊了CMake测试的三大基础:

  • enable_testing():开启测试功能的开关,必须放在顶层CMakeLists.txt
  • add_test():注册测试用例,指定名称和命令
  • CTest:测试执行器,通过退出码判断通过/失败

测试用例的编写其实很简单——就是一个返回0或非0的可执行程序。但别小看这个基础,后面我们聊测试框架、覆盖率、自动化测试时,都是建立在这个基础之上的。

嗯,这一章就到这里。记住:测试不是可有可无的,它是代码质量的守门员。早点用上CTest,你会感谢自己的。


公众号:蓝海资料掘金营,微信deep3321