📌 项目地址catchorg/Catch2 | ⭐ 20,587 颗星 | 🔧 C++ | 📜 未标注

为什么需要 Catch2?

C++ 的测试框架不少,但大多要求你继承某个基类、用字符串宏定义测试名、断言语法像函数调用。Catch2 的设计思路很简单:让测试像正常 C++ 代码一样自然。测试名称不必是合法标识符(可以是任意字符串),断言直接用 REQUIRE(expr)CHECK(expr) 这种接近布尔表达式的写法,而 SECTION 机制让你在同一个测试用例里共享 setup / teardown 代码,无需写额外的 fixture 类。

此外,Catch2 还内建了基本的微基准测试BDD 风格的宏SCENARIOGIVENWHENTHEN),一个框架覆盖了日常测试、性能回归、行为驱动开发三种场景。

实际用法:从单元测试到基准测试

1. 单元测试:自然断言 + SECTION

#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 的第一个参数就是测试名称,可以是带空格、标点的任意字符串。
  • 第二个参数是一个标签(如 [factorial]),用于筛选运行哪些测试(例如命令行 ./tests "[factorial]")。
  • REQUIRE 如果失败会立即终止当前测试用例,CHECK 只记录错误不终止,适合收集多个失败。

2. Section:共享 setup / teardown 的优雅方式

TEST_CASE( "vectors can be sized and resized", "[vector]" ) {
    std::vector<int> v( 5 );

    REQUIRE( v.size() == 5 );
    REQUIRE( v.capacity() >= 5 );

    SECTION( "resizing bigger changes size and capacity" ) {
        v.resize( 10 );

        REQUIRE( v.size() == 10 );
        REQUIRE( v.capacity() >= 10 );
    }

    SECTION( "resizing smaller changes size but not capacity" ) {
        v.resize( 0 );

        REQUIRE( v.size() == 0 );
        REQUIRE( v.capacity() >= 5 );
    }

    SECTION( "reserving bigger changes capacity but not size" ) {
        v.reserve( 10 );

        REQUIRE( v.size() == 5 );
        REQUIRE( v.capacity() >= 10 );
    }
}

每个 SECTION 都会从 TEST_CASE 的入口(构造向量)开始执行,但只执行该 SECTION 内部的代码,其余 SECTION 被跳过。这比 Google Test 的 SetUp/TearDown + 多个 TEST_F 更紧凑,避免了重复创建不同测试函数。

3. 微基准测试(内建)

#include <catch2/benchmark/catch_benchmark.hpp>

uint64_t fibonacci(uint64_t number) {
    return number < 2 ? number : fibonacci(number - 1) + fibonacci(number - 2);
}

TEST_CASE("Benchmark Fibonacci", "[!benchmark]") {
    REQUIRE(fibonacci(5) == 5);
    REQUIRE(fibonacci(20) == 6'765);
    BENCHMARK("fibonacci 20") {
        return fibonacci(20);
    };
    REQUIRE(fibonacci(25) == 75'025);
    BENCHMARK("fibonacci 25") {
        return fibonacci(25);
    };
}
  • 基准测试默认不会运行,需要在命令行指定 [!benchmark] 标签才能执行(例如 ./tests "[!benchmark]")。
  • BENCHMARK 宏接受一个代码块,框架会自动多次执行并统计耗时,给出平均/最小/最大时间。
  • 基准测试与断言可以混用,适合先验证正确性再测性能。

与同类工具的区别

  • vs Google Test:GTest 需要写 TEST()TEST_F(),测试名是标识符;Catch2 的测试名可以是任意字符串。GTest 的 ASSERT_TRUE/EXPECT_TRUE 不如 Catch2 的 REQUIRE(expr) 自然。Catch2 的 SECTION 机制在 fixture 组织上更轻量,无需继承。Catch2 内建基准测试,GTest 没有。
  • vs boost.test:boost.test 宏更冗长,需要 BOOST_AUTO_TEST_CASE,且 boost 库体积大。Catch2 对现代 C++(C++14/17/20)支持更好,编译速度也更快(单头文件时期优势明显,v3 虽然改为分离库,但仍保持模块化)。
  • vs doctest:doctest 是 Catch2 的轻量化变种,编译速度更快,但功能(benchmark、BDD)不如 Catch2 完整。

需要注意的事项

  1. Catch2 v3 不再是一个单头文件库
    v3 版本需要将 src/catch2.cpp 编译成单独的库文件,然后链接。迁移指南参考 docs/migrate-v2-to-v3.md。如果仍想用单头文件,可以使用 v2.x 分支(支持 C++11)。

  2. 基准测试并非默认运行
    必须显式使用 [!benchmark] 标签,否则框架会跳过基准测试块。

  3. 依赖
    Catch2 v3 需要 C++14 或更高版本。C++11 用户请使用 v2.x 分支。

  4. 文档尚在更新
    v3 版本的文档(尤其是安装和构建部分)还在完善中,如有疑问可查阅 GitHub Issues 或加入 Discord 社区

  5. 不完全兼容 v2
    v3 改动了头文件组织方式和部分宏名称,迁移时需要修改 include 路径和少量代码。

总结

Catch2 适合那些希望用最少的样板代码写出可读性高、维护成本低的 C++ 测试的团队。它的自然断言、Section、标签系统、内建基准测试和 BDD 支持,让一个框架覆盖了大多数日常测试需求。如果项目已经使用 C++14 或更新版本,并且不希望依赖 Google Test 或 Boost 那样庞大的依赖,Catch2 值得投入。

这篇文章对你有帮助吗?

发表回复