选Catch2因其header-only、零配置、启动最快,仅需#include头文件即可用TEST_CASE运行测试,无需链接库或CMake配置;Google Test需编译静态库并处理ABI,doctest虽header-only但默认不启用异常捕获和断言值打印。

为什么选 Catch2 而不是 Google Test 或 doctest
Catch2 是 header-only、零配置启动最快的 C++ 单元测试框架,#include <catch2/catch_test_macros.hpp> 后直接写 TEST_CASE 就能跑,不用链接库、不需 CMake 额外 find_package。Google Test 要编译静态库、处理 ABI 兼容性;doctest 虽也 header-only,但默认不启用异常捕获和字符串化断言(比如 REQUIRE(a == b) 在 Catch2 中会自动打印 a 和 b 的值,doctest 默认不)。如果你只是想快速验证一个算法或类行为,Catch2 的开箱即用成本最低。
最简可运行的 Catch2 测试文件怎么写
新建一个 test_main.cpp,内容如下:
#define CATCH_CONFIG_MAIN
#include <catch2/catch_test_macros.hpp>
TEST_CASE("addition works", "[math]") {
REQUIRE(2 + 2 == 4);
REQUIRE(0 + -1 == -1);
}
TEST_CASE("string concatenation", "[string]") {
std::string a = "hello";
std::string b = " world";
REQUIRE(a + b == "hello world");
}
注意三点:
-
#define CATCH_CONFIG_MAIN必须在#include前定义,否则链接时报错找不到main - 每个
TEST_CASE是独立函数,不能共用局部变量(如想共享 setup/teardown,改用SCENARIO+GIVEN/WHEN/THEN) -
REQUIRE失败立即终止当前测试;CHECK会继续执行后续断言,适合检查多个不相关条件
如何把测试和生产代码分离又保持编译一致
别把测试代码塞进 src/,也不要在生产构建中 include 测试头。标准做法是:
立即学习“C++免费学习笔记(深入)”;
- 项目结构划分为
src/(生产代码)、tests/(测试代码)、vendor/(放catch2/头文件) - CMake 中为测试单独建 target:
add_executable(tests tests/test_main.cpp),并用target_include_directories(tests PRIVATE vendor/) - 关键:确保测试 target 和生产 target 使用完全相同的编译选项(
target_compile_options、target_compile_definitions),否则可能因NDEBUG或优化级别差异导致行为不一致 - 如果生产代码用了
constexpr或模板特化,测试里必须用相同宏定义(例如-DENABLE_FOO=1),否则REQUIRE可能校验的是未定义行为
Catch2 常见报错和绕过方式
遇到 undefined reference to 'main':确认 #define CATCH_CONFIG_MAIN 写在 #include <catch2/catch_test_macros.hpp> 之前,且整个项目中只有**一个**源文件定义它(多文件链接时重复定义 main 会冲突)。
error: ‘REQUIRE’ was not declared in this scope:检查是否漏了 #include <catch2/catch_test_macros.hpp>,或者用了旧版 Catch1(头文件名是 catch.hpp,宏名是 TEST_CASE 但断言是 REQUIRE 不可用)。
测试通过但实际逻辑有 bug?常见原因是:
- 忘记
#include <string>导致std::string隐式转换失败,REQUIRE比较时调用错误重载 - 用
SECTION时没注意作用域——SECTION内定义的变量只在该 section 生效,跨 section 不能访问 - 浮点比较用
==:改用REQUIRE_THAT(value, WithinAbs(3.14, 0.001))
Catch2 的断言信息很全,但前提是你的类型支持 operator<< 或提供 StringMaker 特化,否则 REQUIRE(obj.x == 42) 只显示 “failed: obj.x == 42”,看不到 obj.x 实际值。


















