【C++脚手架】gtest 单元测试库的介绍与使用

GTest 使用指南:C++ 单元测试框架从入门到实践

GTest 是 Google 出品的跨平台 C++ 单元测试框架,提供了丰富的断言、事件机制和参数化测试能力。本文从安装到高级事件机制,结合完整可运行的代码示例,带你全面掌握 GTest。

摘要:本文系统介绍了 Google Test (GTest) C++ 单元测试框架的核心使用方法。从基础安装、断言宏、测试宏(TEST/TEST_F)到高级的事件机制(全局事件、TestSuite事件、TestCase事件),通过完整的代码示例演示了如何编写可维护的单元测试。文章详细讲解了 GTest 的三层事件机制及其适用场景,帮助开发者根据测试需求选择合适的环境隔离级别,确保测试的独立性和可重复性。

C++ 脚手架仓库地址:https://gitee.com/chen-weifeng-cwf/developing-scaffolding-for-c

📖 目录

  • [1. 简介与安装](#1. 简介与安装)
    • [1.1 介绍](#1.1 介绍)
    • [1.2 安装](#1.2 安装)
  • [2. 快速入门](#2. 快速入门)
    • [2.1 头文件包含](#2.1 头文件包含)
    • [2.2 框架初始化](#2.2 框架初始化)
    • [2.3 运行所有测试](#2.3 运行所有测试)
    • [2.4 TEST 宏](#2.4 TEST 宏)
    • [2.5 断言宏](#2.5 断言宏)
    • [2.6 入门示例](#2.6 入门示例)
  • [3. 事件机制](#3. 事件机制)
    • [3.1 全局事件 --- 测试程序级别](#3.1 全局事件 — 测试程序级别)
    • [3.2 TestSuite 事件 --- 测试套件级别](#3.2 TestSuite 事件 — 测试套件级别)
    • [3.3 TestCase 事件 --- 测试用例级别](#3.3 TestCase 事件 — 测试用例级别)
  • [4. 三层事件机制总结](#4. 三层事件机制总结)
  • [5. 总结](#5. 总结)

GTest 是 Google 出品的跨平台 C++ 单元测试框架,提供了丰富的断言、事件机制和参数化测试能力。本文从安装到高级事件机制,结合完整可运行的代码示例,带你全面掌握 GTest。


1. 简介与安装

1.1 介绍

GTest 是一个跨平台的 C++ 单元测试框架,由 Google 公司发布。它为在不同平台上编写 C++ 单元测试而生,提供了:

  • 丰富的断言宏(致命和非致命判断)
  • 参数化等测试所需的宏
  • 全局测试单元测试组件

1.2 安装

bash 复制代码
sudo apt-get install libgtest-dev

2. 快速入门

2.1 头文件包含

cpp 复制代码
#include <gtest/gtest.h>

2.2 框架初始化

cpp 复制代码
testing::InitGoogleTest(&argc, argv);

将命令行参数传递给 GTest,使其可以解析内置的测试选项。

2.3 运行所有测试

cpp 复制代码
RUN_ALL_TESTS();

2.4 TEST 宏

cpp 复制代码
TEST(测试名称, 测试样例名称)      // 简单测试
TEST_F(test_fixture, test_name)   // 测试夹具(多样测试)
  • TEST:创建一个简单测试,定义了一个测试函数,可以使用任何 C++ 代码并使用框架提供的断言进行检查。同测试下多个样例不能同名。
  • TEST_F:主要用来进行多样测试,适用于多个测试场景需要相同数据配置的情况(即相同数据测不同行为)。

2.5 断言宏

GTest 中的断言宏分为两类:

类型 行为
ASSERT_ 系列 如果检测失败则退出当前函数(致命断言)
EXPECT_ 系列 如果检测失败则继续往下执行(非致命断言)

常用的断言宏:

cpp 复制代码
// bool 值检查
ASSERT_TRUE(参数);       // 期待结果为 true
ASSERT_FALSE(参数);      // 期待结果为 false

// 数值型数据检查
ASSERT_EQ(参数1, 参数2);  // equal,两个数相等才返回 true
ASSERT_NE(参数1, 参数2);  // not equal,不等于才返回 true
ASSERT_LT(参数1, 参数2);  // less than,小于才返回 true
ASSERT_GT(参数1, 参数2);  // greater than,大于才返回 true
ASSERT_LE(参数1, 参数2);  // less equal,小于等于才返回 true
ASSERT_GE(参数1, 参数2);  // greater equal,大于等于才返回 true

2.6 入门示例

cpp 复制代码
#include <iostream>
#include <gtest/gtest.h>

int abs(int x) {
    return x > 0 ? x : -x;
}

TEST(abs_test, test1) {
    ASSERT_TRUE(abs(1) == 1) << "abs(1)=1";
    ASSERT_TRUE(abs(-1) == 1);
    ASSERT_FALSE(abs(-2) == -2);
    ASSERT_EQ(abs(1), abs(-1));
    ASSERT_NE(abs(-1), 0);
    ASSERT_LT(abs(-1), 2);
    ASSERT_GT(abs(-1), 0);
    ASSERT_LE(abs(-1), 2);
    ASSERT_GE(abs(-1), 0);
}

int main(int argc, char *argv[]) {
    testing::InitGoogleTest(&argc, argv);  // 将命令行参数传递给 GTest
    return RUN_ALL_TESTS();                 // 运行所有测试用例
}

编译运行:

makefile 复制代码
main: main.cc
	g++ -std=c++17 $^ -o $@ -lgtest
bash 复制代码
$ ./main
[==========] Running 1 test from 1 test case.
[----------] Global test environment set-up.
[----------] 1 test from abs_test
[ RUN      ] abs_test.test1
[       OK ] abs_test.test1 (0 ms)
[----------] 1 test from abs_test (0 ms total)
[----------] Global test environment tear-down
[==========] 1 test from 1 test case ran. (0 ms total)
[  PASSED  ] 1 test.

提示ASSERT 断言失败后可通过 << 操作符输出自定义错误信息,如 ASSERT_TRUE(abs(1) == 1) << "abs(1)=1";


3. 事件机制

GTest 中的事件机制是指在测试前和测试后提供给用户自行添加操作的机制,让同一测试套件下的测试用例可以共享数据 。事件机制的核心价值在于:在测试前准备好测试环境,在测试后销毁环境------如果有一段代码需要多种不同方法的测试,事件机制可以为每个测试用例自动初始化测试数据,并在测试完毕后清理影响。

GTest 的事件机制分为三个层次:

复制代码
测试程序 (Program)
  └── 测试套件 (Test Suite)
        └── 测试用例 (TestCase)

3.1 全局事件 --- 测试程序级别

针对整个测试程序 ,在程序开始和结束时执行。需要创建一个继承自 testing::Environment 的类,实现 SetUpTearDown 成员函数,并在 main 函数中通过 testing::AddGlobalTestEnvironment 注册。

cpp 复制代码
#include <iostream>
#include <gtest/gtest.h>
#include <unordered_map>

// 全局测试环境类:继承 testing::Environment
// SetUp 在测试前调用,TearDown 在测试后调用
std::unordered_map<std::string, std::string> dict;

class HashTestEnv : public testing::Environment {
public:
    virtual void SetUp() override {
        std::cout << "测试前:提前准备数据!!\n";
        dict.insert(std::make_pair("Hello", "你好"));
        dict.insert(std::make_pair("hello", "你好"));
        dict.insert(std::make_pair("雷吼", "你好"));
    }

    virtual void TearDown() override {
        std::cout << "测试结束后:清理数据!!\n";
        dict.clear();
    }
};

TEST(hash_case_test, find_test) {
    auto it = dict.find("hello");
    ASSERT_NE(it, dict.end());
}

TEST(hash_case_test, size_test) {
    ASSERT_GT(dict.size(), 0);
}

int main(int argc, char *argv[]) {
    testing::AddGlobalTestEnvironment(new HashTestEnv);  // 注册全局事件
    testing::InitGoogleTest(&argc, argv);
    return RUN_ALL_TESTS();
}

运行结果:

复制代码
[==========] Running 2 tests from 1 test case.
[----------] Global test environment set-up.
测试前:提前准备数据!!
[----------] 2 tests from hash_case_test
[ RUN      ] hash_case_test.find_test
[       OK ] hash_case_test.find_test (0 ms)
[ RUN      ] hash_case_test.size_test
[       OK ] hash_case_test.size_test (0 ms)
[----------] 2 tests from hash_case_test (0 ms total)
[----------] Global test environment tear-down
测试结束后:清理数据!!
[==========] 2 tests from 1 test case ran. (0 ms total)
[  PASSED  ] 2 tests.

3.2 TestSuite 事件 --- 测试套件级别

针对一组相关的测试用例 ,在套件的第一个测试用例开始前和最后一个测试用例结束后执行。需要创建一个继承自 testing::Test 的类,实现两个静态函数 SetUpTestCaseTearDownTestCase,并使用 TEST_F 宏替代 TEST 宏。

cpp 复制代码
#include <iostream>
#include <gtest/gtest.h>
#include <unordered_map>

// 测试套件环境类:继承 testing::Test
class HashTestEnv1 : public testing::Test {
public:
    static void SetUpTestCase() {
        std::cout << "环境1 第一个TEST之前调用\n";
    }

    static void TearDownTestCase() {
        std::cout << "环境1 最后一个TEST之后调用\n";
    }

public:
    std::unordered_map<std::string, std::string> dict;
};

// TEST_F 的第一个参数必须是环境类名
// 可以直接访问环境类中的成员变量和成员函数
TEST_F(HashTestEnv1, insert_test) {
    std::cout << "环境1,中间 insert 测试\n";
    dict.insert(std::make_pair("Hello", "你好"));
    dict.insert(std::make_pair("hello", "你好"));
    dict.insert(std::make_pair("雷吼", "你好"));
    auto it = dict.find("hello");
    ASSERT_NE(it, dict.end());
}

TEST_F(HashTestEnv1, sizeof_test) {
    std::cout << "环境1,中间 size 测试\n";
    ASSERT_GT(dict.size(), 0);
}

int main(int argc, char *argv[]) {
    testing::InitGoogleTest(&argc, argv);
    return RUN_ALL_TESTS();
}

运行结果:

复制代码
[==========] Running 2 tests from 1 test case.
[----------] 2 tests from HashTestEnv1
环境1 第一个TEST之前调用
[ RUN      ] HashTestEnv1.insert_test
环境1,中间 insert 测试
[       OK ] HashTestEnv1.insert_test (0 ms)
[ RUN      ] HashTestEnv1.sizeof_test
环境1,中间 size 测试
HashTestEnv1.sizeof_test: Failure
Expected: (dict.size()) > (0), actual: 0 vs 0
[  FAILED  ] HashTestEnv1.sizeof_test (0 ms)
环境1 最后一个TEST之后调用
[  PASSED  ] 1 test.
[  FAILED  ] 1 test.

注意 :在 TestSuite 事件中,SetUpTestCase 只在第一个 TEST 前调用一次,TearDownTestCase 只在最后一个 TEST 后调用一次。上面的例子中,sizeof_test 失败是因为它运行时 insert_test 已经执行完毕,但数据并不会自动重置------两个测试用例共享了同一个 dict 对象 ,而 insert_test 中插入的数据在测试之间没有被清理。这就是为什么需要 TestCase 事件。

3.3 TestCase 事件 --- 测试用例级别

针对每一个测试用例 ,在每个测试用例开始前和结束后执行。同样继承 testing::Test,但实现的是非静态成员函数 SetUpTearDown------每个测试用例执行时都会创建一个全新的环境对象实例,从而保证各测试用例之间数据完全隔离。

函数 调用时机 关键字
SetUpTestCase() 套件第一个测试用例(整体一次) static
TearDownTestCase() 套件最后一个测试用例(整体一次) static
SetUp() 每个测试用例 virtual(非静态)
TearDown() 每个测试用例 virtual(非静态)
cpp 复制代码
#include <iostream>
#include <gtest/gtest.h>
#include <unordered_map>

// 测试用例环境类:继承 testing::Test
class HashTestEnv2 : public testing::Test {
public:
    // 套件级别:仅执行一次
    static void SetUpTestCase() {
        std::cout << "环境2 第一个TEST之前被调用,进行总体环境配置\n";
    }

    static void TearDownTestCase() {
        std::cout << "环境2 最后一个TEST之后被调用,进行总体环境清理\n";
    }

    // 用例级别:每个 TEST 前后各执行一次
    virtual void SetUp() override {
        std::cout << "环境2 测试前:提前准备数据!!\n";
        dict.insert(std::make_pair("bye", "再见"));
        dict.insert(std::make_pair("see you", "再见"));
    }

    virtual void TearDown() override {
        std::cout << "环境2 测试结束后:清理数据!!\n";
        dict.clear();
    }

public:
    std::unordered_map<std::string, std::string> dict;
};

TEST_F(HashTestEnv2, insert_test) {
    std::cout << "环境2,中间测试\n";
    dict.insert(std::make_pair("hello", "你好"));
    ASSERT_EQ(dict.size(), 3);  // bye + see you + hello = 3
}

TEST_F(HashTestEnv2, size_test) {
    std::cout << "环境2,中间 size 测试\n";
    auto it = dict.find("hello");
    ASSERT_EQ(it, dict.end());    // hello 未插入
    ASSERT_EQ(dict.size(), 2);    // 只有 bye + see you
}

int main(int argc, char *argv[]) {
    testing::InitGoogleTest(&argc, argv);
    return RUN_ALL_TESTS();
}

运行结果:

复制代码
[==========] Running 2 tests from 1 test case.
[----------] 2 tests from HashTestEnv2
环境2 第一个TEST之前被调用,进行总体环境配置
[ RUN      ] HashTestEnv2.insert_test
环境2 测试前:提前准备数据!!
环境2,中间测试
环境2 测试结束后:清理数据!!
[       OK ] HashTestEnv2.insert_test (1 ms)
[ RUN      ] HashTestEnv2.size_test
环境2 测试前:提前准备数据!!
环境2,中间 size 测试
环境2 测试结束后:清理数据!!
[       OK ] HashTestEnv2.size_test (0 ms)
环境2 最后一个TEST之后被调用,进行总体环境清理
[  PASSED  ] 2 tests.

两个测试全部通过。每个测试用例拥有独立的 dict 实例,互不影响。


4. 三层事件机制总结

事件级别 基类 实现方法 注册方式 适用场景
全局事件 testing::Environment SetUp() / TearDown() AddGlobalTestEnvironment 整个程序的初始化/清理(如启动服务、准备数据库连接)
TestSuite 事件 testing::Test static SetUpTestCase() / static TearDownTestCase() 自动(通过 TEST_F 一组相关测试共享的昂贵资源(如加载配置文件)
TestCase 事件 testing::Test virtual SetUp() / virtual TearDown() 自动(通过 TEST_F 每个测试用例独立的测试数据准备和清理

选择建议

  • 如果数据需要被所有测试 共享且只初始化一次 → 全局事件
  • 如果数据需要被一组相关测试 共享 → TestSuite 事件
  • 如果每个测试 都需要干净独立的数据环境 → TestCase 事件(最常用)

5. 总结

GTest 的核心知识体系:

  1. 基础流程InitGoogleTestRUN_ALL_TESTS → 自动执行所有 TEST/TEST_F 宏定义
  2. 断言分类ASSERT_*(致命,失败即退出) vs EXPECT_*(非致命,失败继续)
  3. 测试定义TEST 用于简单测试,TEST_F 用于需要数据共享的测试
  4. 三层事件:Global(全局)→ TestSuite(套件)→ TestCase(用例),逐级提供更细粒度的环境控制
  5. 隔离原则 :TestCase 级别的 SetUp/TearDown 保证每个测试用例拥有独立的数据环境,避免交叉影响

掌握了这些,你就可以在 C++ 项目中编写规范、可维护的单元测试了。

相关推荐
小欣加油1 小时前
leetcode 3300 替换为数位和后的最小元素
数据结构·c++·算法·leetcode
晚风予卿云月1 小时前
【枚举】普通枚举
数据结构·c++·算法·竞赛·算法随笔
MR.欻2 小时前
ZLMediaKit 源码分析(四):RTP/RTCP 协议栈实现分析
c++·人工智能·vscode·ffmpeg·音视频
郝学胜-神的一滴2 小时前
Qt 高级开发 019:从零定制登录窗口按钮、Logo 样式与交互悬浮效果
开发语言·c++·qt·程序人生·交互·用户界面
YikNjy2 小时前
string(c++)
java·服务器·c++
呉師傅2 小时前
联想ideapad 310-15ABR拔掉充电器使用电池工作花屏问题的解决方法【维修个例】
运维·服务器·网络·智能手机·电脑
汉克老师2 小时前
GESP6级C++考试语法知识(三十四、二叉搜索树(BST)(四、BST的退化))
c++·二叉搜索树·bst·gesp6级·gesp六级
y_m_h2 小时前
llvm介绍
c++
农民小飞侠2 小时前
SandboxFusion搭建教程
linux·ubuntu