catch2适合快速启动,单头文件、无需链接,但仅限一个源文件定义catch_config_main;googletest更适中大型项目,支持mock、参数化等工程需求,但编译慢且需正确配置。

项目刚起步,想写个测试但不想配环境?用 Catch2
直接 #include <catch2></catch2> 就能开测,连编译库都不用——Catch2 是单头文件设计,没有链接、没有 -lgtest、不用改 CMakeLists.txt。你写完一个 add() 函数,立刻就能在同一个文件里补上测试:
#define CATCH_CONFIG_MAIN
#include <catch2>
int add(int a, int b) { return a + b; }
TEST_CASE("add works") {
REQUIRE(add(2, 3) == 5);
}
</catch2>
常见错误现象:有人把 #define CATCH_CONFIG_MAIN 放错位置(比如放在 #include 之后),结果链接时报 undefined reference to 'main';还有人漏掉这个宏,又没自己写 main(),程序根本跑不起来。
-
CATCH_CONFIG_MAIN必须在任何#include <catch2></catch2>之前定义 - 只允许在一个源文件里定义它(否则重复定义
main) - 如果项目已有
main(),就用CATCH_CONFIG_RUNNER替代
团队在做中大型项目,要长期维护、加 Mock、跑 CI?选 GoogleTest
GoogleTest 不是“更好”,而是“更扛事”:它和 GoogleMock 天然打通,支持 TEST_F 测试夹具、INSTANTIATE_TEST_SUITE_P 参数化测试、ASSERT_DEATH 死亡测试——这些不是炫技,是真实工程里绕不开的需求。比如你要验证某个函数在非法输入下是否真的 abort,Catch2 得自己 fork + sigaction 拦截,而 GoogleTest 一行 ASSERT_DEATH(func(-1), ".*invalid.*"); 就搞定。
性能影响明显:因为重度依赖模板和宏展开,GoogleTest 编译慢,单个测试文件动辄多花 1–3 秒;但运行时开销几乎为零,CI 上稳定可靠。
- 必须调用
::testing::InitGoogleTest(&argc, argv),否则RUN_ALL_TESTS()可能静默跳过所有测试 - 链接时漏掉
-pthread会导致多线程测试随机 hang 住(尤其在 Linux CI 环境) - Windows 下若用 MSVC,记得关掉
/GL(全程序优化),否则链接时报 LNK2019
写测试时总纠结命名、语法太重?Catch2 的 BDD 风格真省心
GoogleTest 要求测试名是合法 C++ 标识符:TEST(MathTest, Add_Two_Positive_Numbers)——看着就累;Catch2 直接写自然语言:TEST_CASE("add two positive numbers"),甚至支持嵌套描述:
SCENARIO("string reversal") {
GIVEN("a non-empty string") {
std::string s = "hello";
WHEN("reversed") {
auto r = reverse(s);
THEN("it should be 'olleh'") {
REQUIRE(r == "olleh");
}
}
}
}
这不是为了好看。BDD 结构让产品、测试、开发三方看同一份测试代码时,能对齐验收逻辑。但注意:SCENARIO/GIVEN 这些宏本质还是函数作用域,变量不能跨 WHEN 传递——有人误以为能像真实语句一样“延续上下文”,结果编译报 use of undeclared identifier。
-
REQUIRE失败立即退出当前SECTION或WHEN,CHECK则继续执行,适合检查多个独立断言 - 别在
GIVEN里做 heavy setup(比如读文件),它可能被多次执行(因 Catch2 的 section 重入机制) - 中文字符串当测试名?可以,但某些旧版终端或 CI 日志会乱码,建议仍用英文描述
已经写了部分 GoogleTest,现在想切到 Catch2?别硬切,先混用
没有“迁移工具”,也别删旧测试重写。实际做法是:新模块用 Catch2,老模块维持 GoogleTest,两个测试可共存于同一项目——只要它们的 main() 不冲突。比如用 Catch2 的 CATCH_CONFIG_RUNNER 写个自定义入口,手动注册两套测试:
#include <catch2>
extern "C" int run_gtest_main(int, char**); // 原 gtest main 封装
int main(int argc, char* argv[]) {
Catch::Session session;
auto cli = session.cli();
session.applyCommandLine(argc, argv);
int catch_result = session.run();
int gtest_result = run_gtest_main(argc, argv);
return catch_result || gtest_result ? 1 : 0;
}
</catch2>
容易踩的坑:两个框架都试图接管标准输出或信号处理(比如 SIGABRT),导致死亡测试行为不一致;更稳妥的做法是分进程运行——CI 脚本里分别跑 ./test_gtest 和 ./test_catch2。
真正卡住人的从来不是语法差异,而是团队对“测试失败算不算阻断发布”的共识。框架只是工具,选哪个,最终取决于你们愿不愿意为每个 if 分支补上对应测试,而不是等它在线上崩了再翻日志。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











