前置知识: C++

C++测试框架

3 minIntermediate2026/6/14

Google Test与Catch2

概述

测试是保证软件质量的基石。C++ 生态中有多个成熟的测试框架,其中 Google Test(gtest)和 Catch2 最为流行。Google Test 是 Google 出品的重量级框架,功能全面、企业级支持;Catch2 则以轻量、易用著称,单头文件即可使用。选择哪个框架取决于项目规模和团队偏好,但掌握测试的基本原则比框架选择更为重要。

良好的测试应该遵循 FIRST 原则:快速(Fast)、独立(Independent)、可重复(Repeatable)、自验证(Self-validating)、及时(Timely)。

基础概念

测试

说明
单元测试测试单个函数或的行为
集成测试测试模块之间的交互
参数化测试用不同输入重复运行同一测试
死亡测试验证程序在特定条件下是否按预期终止
基准测试测量代码执行性能

断言级别

  • EXPECT:断言失败后继续执行当前测试,收集所有失败信息
  • ASSERT:断言失败后立即终止当前测试,后续代码不再执行

快速上手

Google Test 基础

#include <gtest/gtest.h>

// 被测函数
int add(int a, int b) { return a + b; }
int divide(int a, int b) {
    if (b == 0) throw std::invalid_argument("除数不能为零");
    return a / b;
}

// 基本测试
TEST(MathTest, Add) {
    EXPECT_EQ(add(1, 2), 3);       // 相等
    EXPECT_NE(add(1, 2), 4);       // 不等
    EXPECT_GT(add(5, 3), 0);       // 大于
    EXPECT_LT(add(-1, 0), 1);      // 小于
}

// 测试异常
TEST(MathTest, DivideByZero) {
    EXPECT_THROW(divide(10, 0), std::invalid_argument);
    EXPECT_ANY_THROW(divide(10, 0));
    EXPECT_NO_THROW(divide(10, 2));
}

Catch2 基础

#include <catch2/catch_test_macros.hpp>

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

TEST_CASE("Addition works", "[math]") {
    REQUIRE(add(1, 2) == 3);       // REQUIRE 失败后停止
    CHECK(add(-1, 1) == 0);        // CHECK 失败后继续

    // 分区测试
    SECTION("positive numbers") {
        REQUIRE(add(2, 3) == 5);
    }

    SECTION("negative numbers") {
        REQUIRE(add(-2, -3) == -5);
    }
}

详细用法

Google Test 参数化测试

#include <gtest/gtest.h>

// 参数化测试类
class AddTest : public ::testing::TestWithParam<std::tuple<int, int, int>> {};

// 用不同参数运行同一测试
TEST_P(AddTest, VariousInputs) {
    auto [a, b, expected] = GetParam();
    EXPECT_EQ(add(a, b), expected);
}

// 定义测试数据
INSTANTIATE_TEST_SUITE_P(
    AddTests,
    AddTest,
    ::testing::Values(
        std::make_tuple(1, 2, 3),
        std::make_tuple(-1, 1, 0),
        std::make_tuple(0, 0, 0),
        std::make_tuple(100, 200, 300)
    )
);

Google Test 测试夹具

#include <gtest/gtest.h>
#include <vector>

class VectorTest : public ::testing::Test {
protected:
    // 每个测试前执行
    void SetUp() override {
        vec_ = {1, 2, 3, 4, 5};
    }

    // 每个测试后执行
    void TearDown() override {
        vec_.clear();
    }

    std::vector<int> vec_;
};

TEST_F(VectorTest, Size) {
    EXPECT_EQ(vec_.size(), 5u);
}

TEST_F(VectorTest, PushBack) {
    vec_.push_back(6);
    EXPECT_EQ(vec_.size(), 6u);
    EXPECT_EQ(vec_.back(), 6);
}

TEST_F(VectorTest, Clear) {
    vec_.clear();
    EXPECT_TRUE(vec_.empty());
}

Catch2 匹配器

#include <catch2/catch_test_macros.hpp>
#include <catch2/matchers/catch_matchers_string.hpp>
#include <catch2/matchers/catch_matchers_vector.hpp>

TEST_CASE("String matchers", "[matchers]") {
    std::string str = "Hello, World!";

    // 字符串匹配器
    REQUIRE_THAT(str, Catch::Matchers::ContainsSubstring("World"));
    REQUIRE_THAT(str, Catch::Matchers::StartsWith("Hello"));
    REQUIRE_THAT(str, Catch::Matchers::EndsWith("!"));
}

TEST_CASE("Vector matchers", "[matchers]") {
    std::vector<int> vec = {1, 2, 3, 4, 5};

    REQUIRE_THAT(vec, Catch::Matchers::Contains(3));
    REQUIRE_THAT(vec, Catch::Matchers::SizeIs(5));
}

模拟对象(Google Mock)

#include <gmock/gmock.h>

// 定义接口
class Database {
public:
    virtual ~Database() = default;
    virtual bool connect(const std::string& host) = 0;
    virtual std::string query(const std::string& sql) = 0;
    virtual void disconnect() = 0;
};

// 创建模拟类
class MockDatabase : public Database {
public:
    MOCK_METHOD(bool, connect, (const std::string& host), (override));
    MOCK_METHOD(std::string, query, (const std::string& sql), (override));
    MOCK_METHOD(void, disconnect, (), (override));
};

// 使用模拟对象测试
TEST(UserServiceTest, GetUser) {
    MockDatabase db;

    // 设置期望:connect 被调用一次,返回 true
    EXPECT_CALL(db, connect("localhost"))
        .Times(1)
        .WillOnce(::testing::Return(true));

    // 设置期望:query 被调用一次,返回指定结果
    EXPECT_CALL(db, query("SELECT * FROM users WHERE id=1"))
        .Times(1)
        .WillOnce(::testing::Return(R"({"name": "张三"})")"));

    // 执行测试
    EXPECT_TRUE(db.connect("localhost"));
    EXPECT_EQ(db.query("SELECT * FROM users WHERE id=1"), R"({"name": "张三"})");
}

常见场景

测试文件 IO

#include <gtest/gtest.h>
#include <fstream>
#include <filesystem>

class FileIOTest : public ::testing::Test {
protected:
    std::string test_dir_ = "test_temp";

    void SetUp() override {
        std::filesystem::create_directories(test_dir_);
    }

    void TearDown() override {
        std::filesystem::remove_all(test_dir_);
    }
};

TEST_F(FileIOTest, WriteAndRead) {
    std::string filepath = test_dir_ + "/data.txt";

    // 写入
    {
        std::ofstream out(filepath);
        out << "测试数据" << std::endl;
    }

    // 读取
    {
        std::ifstream in(filepath);
        std::string line;
        std::getline(in, line);
        EXPECT_EQ(line, "测试数据");
    }
}

测试多线程代码

#include <gtest/gtest.h>
#include <thread>
#include <vector>
#include <atomic>

TEST(ConcurrencyTest, AtomicCounter) {
    std::atomic<int> counter{0};
    std::vector<std::thread> threads;

    for (int i = 0; i < 8; ++i) {
        threads.emplace_back([&counter]() {
            for (int j = 0; j < 1000; ++j) {
                counter.fetch_add(1, std::memory_order_relaxed);
            }
        });
    }

    for (auto& t : threads) t.join();
    EXPECT_EQ(counter.load(), 8000);
}

注意事项

  • 测试代码和业务代码同等重要,应保持测试代码的可读性和可维护性
  • 每个测试应只验证一个行为,避免一个测试包含过多断言
  • 测试命名应清晰表达意,如 DivideByZero_ThrowsException 而非 Test1
  • 避免测试之间的依赖关系,每个测试应能独立运行
  • 不要测试私有方法,应通过公共接口间接验证
  • Google Test 的 ASSERT_* 系列在失败后会终止当前测试,适合后续代码无意义的场景

进阶用法

Google Test 死亡测试

#include <gtest/gtest.h>
#include <cassert>

void validateAge(int age) {
    assert(age >= 0 && age <= 150);
}

TEST(AgeValidationTest, InvalidAgeCausesDeath) {
    // 验证 assert 触发
    EXPECT_DEATH(validateAge(-1), "age >= 0");
    EXPECT_DEATH(validateAge(200), "age <= 150");
}

TEST(AgeValidationTest, ValidAgeDoesNotDie) {
    EXPECT_NO_FATAL_FAILURE(validateAge(25));
}

Catch2 生成器

#include <catch2/catch_test_macros.hpp>
#include <catch2/generators/catch_generators.hpp>

TEST_CASE("Fibonacci properties", "[fib]") {
    auto n = GENERATE(0, 1, 2, 5, 10, 20);

    SECTION("fib(n) >= 0") {
        REQUIRE(fibonacci(n) >= 0);
    }

    SECTION("fib(n+1) >= fib(n) for n >= 1") {
        if (n >= 1) {
            REQUIRE(fibonacci(n + 1) >= fibonacci(n));
        }
    }
}

CMake 集成测试

# CMakeLists.txt
enable_testing()

# 自动发现并注册 Google Test
add_executable(my_tests
    test/math_test.cpp
    test/string_test.cpp
)

target_link_libraries(my_tests PRIVATE mylib GTest::gtest_main)

include(GoogleTest)
gtest_discover_tests(my_tests)

# 运行: ctest --test-dir build --output-on-failure