今天在 GitHub Trending 上看到一个有意思的项目:Catch2——一个现代、C++ 原生的单元测试框架,它把测试写成了像普通 C++ 布尔表达式一样自然的代码,还顺手内置了微基准测试和 BDD 风格宏。
一、项目概述
Catch2 主要由 catchorg 组织维护,是 C++ 生态中最被广泛采用的测试框架之一。它的定位不是"又一个小巧的单头文件库",而是一套完整的测试基础设施。
- 单元测试:核心能力,断言写成
REQUIRE(expr)形式,读起来就像一句布尔表达式。 - 微基准(Micro-benchmarking):内置
BENCHMARK宏,无需额外依赖即可对函数做吞吐/耗时测量。 - BDD 风格:提供行为驱动开发的宏,让测试用例以"故事"的方式组织。
它最大的卖点是"简单且自然":测试名不必是合法标识符、断言像普通 C++ 表达式、用 SECTION 在测试内局部共享 setup/teardown 代码。
下面这段来自 README 的示例,最能体现它的风格:
#include <catch2/catch_test_macros.hpp>
#include <cstdint>
uint32_t factorial( uint32_t number ) {
return number <= 1 ? number : factorial(number-1) * number;
}
TEST_CASE( "Factorials are computed", "[factorial]" ) {
REQUIRE( factorial( 1) == 1 );
REQUIRE( factorial( 2) == 2 );
REQUIRE( factorial( 3) == 6 );
REQUIRE( factorial(10) == 3'628'800 );
}
注意 TEST_CASE 的第一个字符串是可读的测试名(不是标识符),第二个是 tag(用于按标签筛选用例,[factorial] 即属于阶乘分组)。
二、技术原理
架构演进:从单头文件到真正的库
Catch2 发展历程里最关键的一次变化是 v3 重写。在 devel 分支上,项目已经发布 v3,它不再是 v2 时代的"单头文件库(single-header)",而是回归为一个正常的 C++ 库:多个头文件 + 独立编译的实现(implementation TUs)。
从 CMakeLists.txt 可以看到这一设计的落地:
project(Catch2
VERSION 3.15.2 # CML version placeholder, don't delete
LANGUAGES CXX
HOMEPAGE_URL "https://github.com/catchorg/Catch2"
DESCRIPTION "A modern, C++-native, unit test framework."
)
版本号 3.15.2 还被 conanfile.py 通过正则从 CMakeLists.txt 反向解析:
pattern = re.compile(r"\w*VERSION (\d+\.\d+\.\d+) # CML version placeholder, don't delete")
这种"单一事实来源(single source of truth)“的做法,避免了版本在多处维护而出错。
核心技术栈与构建系统
Catch2 选择 CMake 作为一等公民构建系统,并提供丰富的 CMake 选项(来自 CMakeLists.txt):
CATCH_INSTALL_DOCS:是否随库安装文档(默认 ON)CATCH_INSTALL_EXTRAS:是否安装 extras(CMake 脚本、调试器助手)CATCH_BUILD_TESTING/CATCH_BUILD_EXAMPLES/CATCH_BUILD_FUZZERS:按需构建测试/示例/模糊测试CATCH_ENABLE_WERROR:开发构建时开启-Werror(默认 ON)
它还贴心地处理了不支持 in-tree 构建的情况,直接 FATAL_ERROR 防止误用:
if(CMAKE_BINARY_DIR STREQUAL CMAKE_CURRENT_SOURCE_DIR)
message(FATAL_ERROR "Building in-source is not supported! ...")
endif()
跨包管理支持
除 CMake 外,Catch2 还提供 Conan 配方(conanfile.py),通过 package_info 暴露 Catch2::Catch2 与 Catch2::Catch2WithMain 两个 CMake target,并处理 Release/Debug 的库名后缀(lib_suffix = "d")。它要求的编译标准是不低于 C++14:
@property
def _min_cppstd(self):
return "14"
Section 机制与数据流
SECTION 是 Catch2 的数据流核心。它基于 RAII + 分支重入(re-run)实现:同一个 TEST_CASE 会被多次执行,每次进入不同的 Section 分支,从而自然地复用 setup 代码、隔离 teardown,而不需要手写 fixture 类。这比传统 xUnit 的 SetUp/TearDown 更轻、更局部。
三、安装与快速开始
环境要求
- C++14 及以上编译器(GCC ≥ 7、Clang ≥ 5、MSVC ≥ 191/VS2017 等)
- CMake ≥ 3.16(用于 CMake 构建)
- 可选:Conan ≥ 1.53(用于包管理)
安装方式
方式一:CMake(推荐,获取完整库)
git clone https://github.com/catchorg/Catch2.git
cd Catch2
cmake -B build -S . -DBUILD_TESTING=OFF
cmake --build build
cmake --install build
方式二:Conan
conan install catch2/3.15.2@ --build=missing
方式三:包管理器(vcpkg / Homebrew / apt 等) 视平台而定。
最简运行示例
写一个测试文件 test.cpp:
#include <catch2/catch_test_macros.hpp>
TEST_CASE("1 + 1 == 2", "[sanity]") {
REQUIRE(1 + 1 == 2);
}
链接 Catch2WithMain(自带 main 函数),编译运行即可看到测试报告。
四、使用方法与实战
基础用法:断言家族
REQUIRE(expr):表达式为假时失败并停止该 SectionCHECK(expr):表达式为假时记录失败但继续执行REQUIRE_FALSE(expr)/CHECK_FALSE(expr):反向断言- 浮点比较:
REQUIRE(a == Approx(b).epsilon(0.01))
进阶用法:微基准
内置 benchmark 无需默认执行,需用 [!benchmark] tag 显式运行:
#include <catch2/catch_test_macros.hpp>
#include <catch2/benchmark/catch_benchmark.hpp>
#include <cstdint>
uint64_t fibonacci(uint64_t number) {
return number < 2 ? number : fibonacci(number - 1) + fibonacci(number - 2);
}
TEST_CASE("Benchmark Fibonacci", "[!benchmark]") {
REQUIRE(fibonacci(20) == 6'765);
BENCHMARK("fibonacci 20") {
return fibonacci(20);
};
REQUIRE(fibonacci(25) == 75'025);
BENCHMARK("fibonacci 25") {
return fibonacci(25);
};
}
运行:your_test_binary "[!benchmark]",即可看到各 BENCHMARK 块的耗时统计。
实际项目示例:Section 共享夹具
#include <catch2/catch_test_macros.hpp>
#include <vector>
TEST_CASE("vector push_back", "[container]") {
std::vector<int> v;
REQUIRE(v.empty());
SECTION("push one") {
v.push_back(1);
REQUIRE(v.size() == 1);
}
SECTION("push three") {
v.push_back(1); v.push_back(2); v.push_back(3);
REQUIRE(v.size() == 3);
}
}
每个 SECTION 都会从 v 的初始(空)状态重新开始,天然实现了"共享 setup、隔离 tear-down”。
五、常见问题与解决方案
1. v2 → v3 迁移:找不到单头文件 / 链接错误
v3 不再是单头文件库。现象:#include "catch.hpp" 或直接把单头文件拖进项目后链接失败。
解决:改用 catch2/catch_test_macros.hpp 等拆分头文件,并链接 Catch2::Catch2 与 Catch2::Catch2WithMain;参考官方 docs/migrate-v2-to-v3.md 迁移指南。
2. in-tree 构建直接报错
现象:cmake -S . -B . 报 Building in-source is not supported!
解决:始终在独立 build 目录构建(cmake -B build -S .),不要在原目录内编译。
3. 基准测试"不运行"
现象:写了 BENCHMARK 但没有任何耗时输出。
解决:基准默认不执行,需显式带 [!benchmark] tag 运行测试二进制,例如 ./tests "[!benchmark]"。
4. 浮点断言偶发失败
现象:REQUIRE(a == b) 在浮点上不稳定。
解决:使用 Approx 做近似比较:REQUIRE(a == Approx(b).epsilon(1e-6))。
5. Werror 导致 CI 编译失败(开发构建)
现象:开启 CATCH_DEVELOPMENT_BUILD 后 -Werror 触发告警即失败。
解决:非贡献者构建时保持 CATCH_DEVELOPMENT_BUILD=OFF(默认),即不会开启 Werror。
六、总结
Catch2 凭借"像写普通 C++ 一样写测试"的设计哲学,成为现代 C++ 测试的事实标准之一。它的 v3 重构让它从单头文件实验品蜕变为一个结构清晰、构建系统完善(CMake + Conan 双支持)、并内置微基准与 BDD 能力的成熟框架。无论是小型库还是大型工程,引入 Catch2::Catch2WithMain 几乎零成本,就能获得可读性强、易维护的测试套件。
如果你正在为 C++ 项目挑选测试框架,Catch2 值得作为首选评估对象——尤其是当你希望测试代码读起来像规格说明、而非一堆样板时。
仓库地址:https://github.com/catchorg/Catch2