GTest 快速入门

一、环境准备

  • 编译器:支持 C++17 及以上标准。
  • 构建工具:CMake(3.14+) + Make/Ninja 等。
  • 支持平台:Linux、macOS、Windows。

    二、项目文件布局

    这是一个极简的 GTest 项目目录结构:

    my_project/                 # 项目根目录
    ├── CMakeLists.txt          # CMake 构建配置文件
    └── hello_test.cc           # 测试源码文件

    构建临时目录(由命令生成,不属于源码):build/

三、CMakeLists.txt 核心内容拆解

这份文件分为三大块:基础配置拉取 GTest 依赖构建测试目标

# 1. 基础配置
cmake_minimum_required(VERSION 3.14)
project(my_project)

set(CMAKE_CXX_STANDARD 17)          # 必须 C++17
set(CMAKE_CXX_STANDARD_REQUIRED ON)

# 2. 拉取 GoogleTest 依赖(使用 FetchContent)
include(FetchContent)
FetchContent_Declare(
  googletest
  URL https://github.com/google/googletest/archive/[提交哈希].zip  # 建议常更新哈希值
)
set(gtest_force_shared_crt ON CACHE BOOL "" FORCE)   # Windows 特定设置,防止链接冲突
FetchContent_MakeAvailable(googletest)
# 2.5 这里也可以使用 vcpkg 的方式引入。

# 3. 构建与测试目标
enable_testing()                                      # 启用 CMake 测试

add_executable(hello_test hello_test.cc)              # 生成测试可执行文件
target_link_libraries(hello_test GTest::gtest_main)   # 链接 GTest 主入口库

include(GoogleTest)                                   # 引入 GTest CMake 模块
gtest_discover_tests(hello_test)                      # 自动发现并注册测试用例

四、测试源码内容

#include <gtest/gtest.h>   // 必须包含 GTest 主头文件

// 定义一个测试:套件名 HelloTest,用例名 BasicAssertions
TEST(HelloTest, BasicAssertions) {
    // 断言字符串不相等
    EXPECT_STRNE("hello", "world");
    // 断言数值相等
    EXPECT_EQ(7 * 6, 42);
}

五、编译与运行命令速查

在项目根目录(my_project)下执行:

步骤命令说明
配置cmake -S . -B build生成构建文件到 build/ 目录
编译cmake --build buildcmake --build build
测试cd build && ctest进入构建目录,运行所有测试用例
  • 依赖管理方式:官方推荐使用 FetchContent 模块直接从 GitHub 拉取源码,无需手动下载安装 GTest。
  • 链接库选择:链接 GTest::gtest_main 会自动提供 main() 函数入口,无需自己写 main;若链接 GTest::gtest,则需手动定义 RUN_ALL_TESTS()
  • 测试发现机制gtest_discover_tests 会自动扫描二进制文件中的测试宏,无需手动列出每个测试名,极大方便了维护。

GTest 入门补充

1. 断言表

EXPECT_* 失败后继续执行
ASSERT_* 失败立刻终止当前函数
  • 值相等EXPECT_EQ(val1, val2) / ASSERT_EQ
  • 值不等EXPECT_NE(val1, val2) / ASSERT_NE
  • 条件为真EXPECT_TRUE(condition) / ASSERT_TRUE
  • 条件为假EXPECT_FALSE(condition) / ASSERT_FALSE
  • 字符串相等EXPECT_STREQ(str1, str2) / ASSERT_STREQ
  • 字符串不等EXPECT_STRNE(str1, str2) / ASSERT_STRNE

2. 添加自定义失败消息

非常简单的示例,一看就懂,就是在 断言后面添加 << 和要输出的内容。

EXPECT_EQ(a, b) << "额外调试信息: a=" << a << ", b=" << b;
  • 帮助新人快速定位失败位置

3. 测试夹具 (Test Fixture) 基础

如果多个测试需要共享相同的初始数据或辅助方法,使用 TEST_F 宏。

#include <gtest/gtest.h>

// 1. 定义一个夹具类,继承 testing::Test
class MyTest : public ::testing::Test {
protected:
    // 2. 初始化工作放在 SetUp 里(每个测试前自动调用)
    void SetUp() override {
        value = 42;
        data = new int[100];  // 假设我们分配了动态内存
    }

    // 3. 清理工作放在 TearDown 里(每个测试后自动调用)
    void TearDown() override {
        delete[] data;        // 释放动态内存,防止泄漏
        data = nullptr;
    }

    // 测试夹具的成员变量,每个测试都可直接使用
    int value;
    int* data = nullptr;
};

// 4. 使用 TEST_F 代替 TEST,第一个参数必须是夹具类名
TEST_F(MyTest, TestA) {
    // 可以直接访问 value 和 data
    EXPECT_EQ(value, 42);
    EXPECT_NE(data, nullptr);
    // 给 data 赋值
    data[0] = 10;
}

TEST_F(MyTest, TestB) {
    // TestB 拥有全新的夹具实例
    // value 依然是 42,data 依然是新分配的数组
    EXPECT_EQ(value, 42);
    // data[0] 还是初始值 0,不会受 TestA 影响
    EXPECT_EQ(data[0], 0);
}

生命周期(自动调用机制)

对于每个 TEST_F,框架严格执行以下 5 步,无需手动干预:

  1. 构造:创建一个全新的夹具对象。
  2. SetUp()自动调用,执行初始化。
  3. 运行测试体:执行 TEST_F 中你写的代码。
  4. TearDown()自动调用,执行清理。
  5. 销毁:析构夹具对象。

必须遵守的关键规则

1. 命名规范

  • 方法名 必须是 SetUp(U大写)和 TearDown(D大写)。
  • 务必加上 override 关键字,写错会立刻编译报错。

    void SetUp() override { ... }    // √ 正确
    void Setup() override { ... }    // × 编译不通过
  1. 测试隔离性
  2. 每个测试会新建一个夹具实例,测试之间不共享状态
  3. 如示例中 TestA 对 data[0] 的修改,绝对不会影响到 TestB

3. SetUp/TearDown 于 构造/析构函数

构造函数 / 析构函数SetUp() / TearDown()
虚函数支持不支持,无法调用子类的重载版本完全支持
异常处理析构函数抛异常极危险,易致崩溃可安全地记录错误,不抛异常
错误信息失败信息不明确可用 GTest 宏输出明确的错误日志
推荐度仅用于极简单的值初始化首选方案

简单记:任务拆开,构造和析构只管资源本身(可选),所有测试的“准备动作”和“清扫动作”都交给 SetUp 和 TearDown,这样更安全、更规范。

4. 命名规范提醒

TEST(SuiteName, TestName) 中的两个名称都 不应包含下划线_),否则可能与框架内部标识符冲突。
例如:TEST(Foo, Bar_test) 错位 → TEST(Foo, BarTest) 正确。

5. 运行特定测试(过滤)

提到在命令行中如何只跑部分测试,会非常实用:

./hello_test --gtest_filter=HelloTest.*        # 运行某个套件全部
./hello_test --gtest_filter=*Basic*            # 名称含 Basic 的测试

也可以说明 ctest 环境下如何传递参数(如 ctest -R <regex> 按测试名过滤)。

6. 测试输出解读

简要解释运行后的输出:通过的点表示成功,失败会打印文件名、行号、期望值和实际值。让新手知道如何读报错。

GTest 高级特性(按使用频率排序)

第一梯队:日常调试与快速筛选

  1. 测试筛选(--gtest_filter)

    # 运行所有测试
    ./my_test
    
    # 只运行 FooTest 套件下的所有测试
    ./my_test --gtest_filter=FooTest.*
    
    # 运行名称包含 "Null" 或 "Constructor" 的测试
    ./my_test --gtest_filter=*Null*:*Constructor*
    
    # 运行 FooTest 下除了 Bar 之外的所有测试
    ./my_test --gtest_filter=FooTest.*-FooTest.Bar
    
    # 组合:运行 A 和 B 套件,但排除特定的几个
    ./my_test --gtest_filter=FooTest.*:BarTest.*-FooTest.Bar:BarTest.Foo
    
    # 仅列出所有测试名称(不执行)
    ./my_test --gtest_list_tests
  2. 临时禁用测试(DISABLED_ 前缀)

    // 单个测试禁用
    TEST(FooTest, DISABLED_DoesAbc) { 
      // 不会被执行 
    }
    
    // 整个套件禁用
    class DISABLED_BarTest : public testing::Test {};
    
    TEST_F(DISABLED_BarTest, DoesXyz) { 
      // 不会被执行 
    }
    
    // 强制运行禁用的测试(命令行)
    ./my_test --gtest_also_run_disabled_tests
  3. 动态跳过测试(GTEST_SKIP)

    TEST(SkipTest, NeedsDatabase) {
      if (!ConnectToDatabase()) {
     GTEST_SKIP() << "Database not available, skipping test.";
      }
      // 以下代码仅在数据库连接成功时执行
      EXPECT_TRUE(QueryData());
    }
    
    // 在 SetUp 中跳过整个 Fixture
    class DBTest : public testing::Test {
      void SetUp() override {
     if (!InitDB()) {
       GTEST_SKIP() << "Skipping all DB tests due to init failure.";
     }
      }
    };
    // 该 Fixture 下所有测试将被跳过
  4. 追踪上下文(SCOPED_TRACE)

    // 用于循环或多层调用,帮助定位失败发生在哪一次调用
    void TestValue(int n) {
      SCOPED_TRACE(n);  // 将 n 加入失败信息
      EXPECT_GT(n, 0);
    }
    
    TEST(TraceTest, Loop) {
      for (int i = -2; i <= 2; i++) {
     SCOPED_TRACE(testing::Message() << "Iteration: " << i);
     TestValue(i);  // 失败时会显示具体是哪个 i 导致的
      }
    }
    // 输出会显示: path/to/file.cc:line: Iteration: -1

第二梯队:测试结构组织与数据驱动

  1. 值参数化测试(TEST_P + INSTANTIATE_TEST_SUITE_P)

    // 1. 定义 Fixture
    class MathTest : public testing::TestWithParam<int> {
      void SetUp() override { value_ = GetParam(); }
     protected:
      int value_;
    };
    
    // 2. 写测试逻辑
    TEST_P(MathTest, IsPositive) {
      EXPECT_GT(value_, 0);
    }
    
    // 3. 实例化(传入参数列表)
    INSTANTIATE_TEST_SUITE_P(
      PositiveNumbers,
      MathTest,
      testing::Values(1, 5, 10, 20)  // 每个值运行一次
    );
    
    // 多参数组合(Combine)
    class CombineTest : public testing::TestWithParam<std::tuple<int, bool>> {};
    
    TEST_P(CombineTest, Check) {
      int i = std::get<0>(GetParam());
      bool b = std::get<1>(GetParam());
      // ...
    }
    INSTANTIATE_TEST_SUITE_P(
      MyCombos,
      CombineTest,
      testing::Combine(testing::Values(1, 2), testing::Values(true, false))
    );
    // 生成 4 个测试: (1,true), (1,false), (2,true), (2,false)

INSTANTIATE_TEST_SUITE_P()

  • 第一个参数:测试实例前缀 -> 用于生成最终的测试名称。
  • 第二个参数:类名(必须与 TEST_P 中定义的类名称完全一致) -> 指定你要实例化哪个参数化测试类。
  • 第三个参数:参数生成器 -> 提供具体的参数值列表。

    • 常用内置生成器:
    • testing::Values(v1, v2, ...):直接枚举固定值。
    • testing::ValuesIn(container):从 STL 容器(如 vectorlist)或数组迭代器中取值。
    • testing::Range(start, end, step):生成等差数列。
    • testing::Bool():生成 false 和 true
    • testing::Combine(g1, g2, ...):生成多个生成器的笛卡尔积(需要参数是 std::tuple 类型)。
  • 第四个参数:自定义命名函数—— 可选
  1. 测试套件级 SetUp/TearDown(共享昂贵资源)

    class SharedResourceTest : public testing::Test {
     protected:
      static int* shared_data_;  // 静态共享资源
    
      static void SetUpTestSuite() {
     shared_data_ = new int(42);  // 整个套件只初始化一次
      }
      static void TearDownTestSuite() {
     delete shared_data_;
     shared_data_ = nullptr;
      }
    };
    
    int* SharedResourceTest::shared_data_ = nullptr;
    
    TEST_F(SharedResourceTest, ReadData) {
      EXPECT_EQ(*shared_data_, 42);  // 所有测试共享同一份数据
    }
  2. 自定义值打印(提升失败信息可读性)

    // 方式一:定义 AbslStringify(推荐)
    namespace my_namespace {
    class Point {
     public:
      Point(int x, int y) : x_(x), y_(y) {}
      template <typename Sink>
      friend void AbslStringify(Sink& sink, const Point& p) {
     absl::Format(&sink, "(%d, %d)", p.x_, p.y_);
      }
     private:
      int x_, y_;
    };
    }  // namespace my_namespace
    
    TEST(PrintTest, Custom) {
      Point p(3, 4);
      EXPECT_EQ(p, Point(1, 2));  // 失败时输出 "Actual: (3,4) Expected: (1,2)"
    }
    
    // 方式二:定义 PrintTo(如果不想依赖 Abseil)
    void PrintTo(const Point& p, std::ostream* os) {
      *os << "(" << p.x_ << "," << p.y_ << ")";
    }

第三梯队:特殊场景验证

  1. 死亡测试(崩溃/退出验证)

    void Foo(int* p) {
      if (!p) {
     fprintf(stderr, "Error: Null pointer!\n");
     exit(1);
      }
    }
    
    TEST(DeathTest, FooWithNull) {
      // 断言进程死亡,且 stderr 匹配正则
      ASSERT_DEATH(Foo(nullptr), "Null pointer");
    }
    
    TEST(DeathTest, NormalExit) {
      // 检查正常退出码
      EXPECT_EXIT(exit(0), testing::ExitedWithCode(0), "");
    }
    
    TEST(DeathTest, SignalKill) {
      // 检查收到信号
      EXPECT_EXIT(raise(SIGKILL), testing::KilledBySignal(SIGKILL), "");
    }
    // 注意:测试套件名建议以 DeathTest 结尾
  2. 浮点数比较

    TEST(FloatTest, Compare) {
      double a = 0.1 + 0.2;
      double b = 0.3;
    
      // 绝对误差比较
      EXPECT_NEAR(a, b, 1e-9);
    
      // 浮点数关系(小于等于近似)
      using ::testing::FloatLE;
      using ::testing::DoubleLE;
      EXPECT_PRED_FORMAT2(FloatLE, 1.0f, 1.01f);   // 1.0 <= ~1.01
      EXPECT_PRED_FORMAT2(DoubleLE, 0.3, 0.300001);
    }
  3. 类型测试(用于模板验证)

    // 针对多种类型执行相同测试
    template <typename T>
    class MyTypeTest : public testing::Test {
     protected:
      T value_ = T(0);
    };
    
    // 定义要测试的类型列表
    using MyTypes = testing::Types<int, float, double>;
    TYPED_TEST_SUITE(MyTypeTest, MyTypes);
    
    // 每个类型都会执行此测试
    TYPED_TEST(MyTypeTest, IsPositive) {
      TypeParam n = this->value_;
      EXPECT_GE(n, 0);
    }
  4. 全局环境(程序级 Setup)

    class MyGlobalEnv : public testing::Environment {
     public:
      void SetUp() override {
     // 在所有测试开始前执行(如启动 Mock 服务)
     printf("Global Setup\n");
      }
      void TearDown() override {
     // 在所有测试结束后执行
     printf("Global Teardown\n");
      }
    };
    
    // 在 main() 中注册
    int main(int argc, char** argv) {
      testing::InitGoogleTest(&argc, argv);
      testing::AddGlobalTestEnvironment(new MyGlobalEnv);
      return RUN_ALL_TESTS();
    }

第四梯队:CI/CD 与批量执行策略

  1. 生成 XML / JSON 报告

    # 生成 XML
    ./my_test --gtest_output=xml:report.xml
    # 或仅指定目录(自动命名)
    ./my_test --gtest_output=xml:test_results/
    
    # 生成 JSON
    ./my_test --gtest_output=json:report.json
    
    # 环境变量方式
    export GTEST_OUTPUT=xml:report.xml
    ./my_test
    
  2. 重复执行与随机顺序

    # 重复执行 100 次(复现偶发性失败)
    ./my_test --gtest_repeat=100
    
    # 无限重复(按 Ctrl+C 停止)
    ./my_test --gtest_repeat=-1
    
    # 重复直到第一次失败(配合调试器)
    ./my_test --gtest_repeat=1000 --gtest_break_on_failure
    
    # 随机顺序执行
    ./my_test --gtest_shuffle
    
    # 指定随机种子(复现上次顺序)
    ./my_test --gtest_shuffle --gtest_random_seed=12345
  3. 失败快速退出与分片

    # 遇到第一个失败立即退出
    ./my_test --gtest_fail_fast
    
    # 分片执行(3 台机器并行)
    # 机器 0
    export GTEST_TOTAL_SHARDS=3
    export GTEST_SHARD_INDEX=0
    ./my_test
    
    # 机器 1
    export GTEST_SHARD_INDEX=1
    ./my_test
    
    # 机器 2
    export GTEST_SHARD_INDEX=2
    ./my_test

第五梯队:高级扩展与底层控制

  1. 捕获子例程中的致命失败

    void Subroutine() {
      ASSERT_EQ(1, 2);  // 这里会失败
    }
    
    TEST(FooTest, Bar) {
      // 方法一:断言子例程不产生致命失败
      ASSERT_NO_FATAL_FAILURE(Subroutine());
      
      // 方法二:手动检查并提前返回
      Subroutine();
      if (testing::Test::HasFatalFailure()) {
    return;  // 避免执行后续危险的代码
      }
      // 安全代码...
    }
  2. 断言捕获(测试你的测试工具)

    #include "gtest/gtest-spi.h"
    
    TEST(UtilTest, CheckFailure) {
      // 验证某段代码确实会产生非致命失败
      EXPECT_NONFATAL_FAILURE(
    EXPECT_EQ(1, 2),  // 这里会失败
    "Value of: 2"     // 期望的错误信息片段
      );
    
      // 验证会产生致命失败
      EXPECT_FATAL_FAILURE(
    ASSERT_TRUE(false),
    "Value of: false"
      );
    }
  3. 测试私有代码(FRIEND_TEST)

    // foo.h
    class Foo {
     private:
      int Secret(int x) { return x * 2; }
      FRIEND_TEST(FooTest, SecretReturnsDouble);  // 授权测试访问
    };
    
    // foo_test.cc
    TEST(FooTest, SecretReturnsDouble) {
      Foo f;
      EXPECT_EQ(f.Secret(5), 10);  // 直接访问私有方法
    }
  4. 动态注册测试(RegisterTest)

    // 适用于运行时从文件/配置读取测试参数
    class DynamicFixture : public testing::Test {
     public:
      explicit DynamicFixture(int data) : data_(data) {}
      void TestBody() override { EXPECT_GT(data_, 0); }
     private:
      int data_;
    };
    
    // 在 main() 中注册
    int main(int argc, char** argv) {
      testing::InitGoogleTest(&argc, argv);
      
      std::vector<int> values = LoadFromConfig(); // {1, 2, 3}
      for (int v : values) {
    testing::RegisterTest(
      "DynamicSuite",          // 套件名
      ("Test" + std::to_string(v)).c_str(),  // 测试名
      nullptr,                 // 类型参数(一般不用)
      std::to_string(v).c_str(), // 值参数
      __FILE__, __LINE__,
      [v]() -> testing::Test* { return new DynamicFixture(v); }
    );
      }
      return RUN_ALL_TESTS();
    }
  5. Sanitizer 集成(将 UB 转为测试失败)

    // 放在主二进制编译的任意 .cpp 中
    extern "C" {
    void __ubsan_on_report() {
      FAIL() << "Undefined Behavior Sanitizer error detected!";
    }
    void __asan_on_error() {
      FAIL() << "Address Sanitizer error detected!";
    }
    void __tsan_on_report() {
      FAIL() << "Thread Sanitizer error detected!";
    }
    }
  6. 自定义事件监听器(替换输出)

    class CustomListener : public testing::EmptyTestEventListener {
      void OnTestStart(const testing::TestInfo& info) override {
    printf("▶ Running: %s.%s\n", info.test_suite_name(), info.name());
      }
      void OnTestEnd(const testing::TestInfo& info) override {
    printf("√ %s\n", info.result()->Passed() ? "PASS" : "FAIL");
      }
    };
    
    int main(int argc, char** argv) {
      testing::InitGoogleTest(&argc, argv);
      
      auto& listeners = testing::UnitTest::GetInstance()->listeners();
      // 移除默认输出
      delete listeners.Release(listeners.default_result_printer());
      // 添加自定义
      listeners.Append(new CustomListener);
      
      return RUN_ALL_TESTS();
    }