【GTest 】GTest 详解以及安装教程
提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档
文章目录
一、GTest 是什么?单元测试的 “标准化武器”
二、GTest 核心概念:从 “测试用例” 到 “测试套件”
1. 测试用例(Test Case)
2. 测试套件(Test Suite)
3. 断言(Assertion)
三、GTest 安装:两种方式,按需选择
方式 1:系统命令直接安装(以 Ubuntu 为例)
方式 2:源码编译安装(适合定制或无包管理器场景)
步骤 1:下载源码
步骤 2:编译安装
验证安装是否成功
四、GTest 实战:从 “单测入门” 到 “工程化测试”
示例 1:基础断言与测试用例组织
示例 2:参数化测试(减少重复代码)
示例 3:事件机制(测试环境的初始化与清理)
五、GTest 工程化实践:那些你该知道的细节
1. 致命断言 vs 非致命断言
2. 死亡测试(验证程序异常退出)
3. 与构建系统的集成
一、GTest 是什么?单元测试的 “标准化武器”
GTest 是谷歌推出的跨平台 C++ 单元测试框架,它的诞生就是为了解决 “不同平台下 C++ 单元测试编写繁琐” 的痛点。无论是 Windows、Linux 还是 macOS,GTest 都能无缝运行。
它到底强在哪?核心优势一目了然:
- 断言丰富:提供几十种断言宏(如
ASSERT_EQ判断相等、ASSERT_TRUE判断真),能精准表达测试逻辑。 - 场景覆盖全:支持致命断言(断言失败则测试用例直接终止)、非致命断言(断言失败不终止,继续执行后续逻辑),还能做参数化测试、死亡测试(验证程序异常退出场景)。
- 结构清晰:通过 “测试套件 - 测试用例” 的层级结构,让大量测试用例也能组织得井井有条。
- 自动化程度高:只需写好测试用例,GTest 能自动发现、执行并生成直观的测试报告。
二、GTest 核心概念:从 “测试用例” 到 “测试套件”
在动手写测试前,先理清 GTest 的核心概念,能让你少走很多弯路。
1. 测试用例(Test Case)
一个 “测试用例” 对应一个最小的测试单元,用来验证某一个具体功能或逻辑点。比如测试 “绝对值函数是否正确” 就是一个测试用例。
2. 测试套件(Test Suite)
多个相关的测试用例可以组成一个 “测试套件”。比如 “数学工具类” 这个套件,可以包含 “绝对值函数测试”“求和函数测试” 等多个用例。
3. 断言(Assertion)
断言是 GTest 的 “灵魂”,它用来判断代码运行结果是否符合预期。比如ASSERT_EQ(a, b)表示 “断言 a 和 b 相等”,如果不相等,测试就会失败。
三、GTest 安装:两种方式,按需选择
GTest 的安装很灵活,你可以用系统包管理器快速装,也可以从源码编译定制,我们逐个说明。
方式 1:系统命令直接安装(以 Ubuntu 为例)
这种方式最省心,适合快速上手:
sudo apt-get update
sudo apt-get install libgtest-dev
安装完成后,系统会把 GTest 的头文件和库文件放到标准路径,编译测试程序时只需链接 GTest 库即可。
方式 2:源码编译安装(适合定制或无包管理器场景)
如果你的系统没有现成包,或者需要指定版本,源码编译是更灵活的选择。
步骤 1:下载源码
GTest 的源码托管在 GitHub 上,执行以下命令下载:
git clone https://github.com/google/googletest.git
cd googletest/
步骤 2:编译安装
GTest 使用 CMake 构建,执行以下命令编译安装:
mkdir build
cd build/
cmake ..
make
sudo make install
安装完成后,头文件默认在/usr/local/include/gtest,库文件在/usr/local/lib。编译测试程序时,链接-lgtest -lgtest_main -pthread即可(-pthread是因为 GTest 依赖多线程)。
验证安装是否成功
写一个简单的测试程序,能正常编译运行就说明安装成功了。比如下面这个 “绝对值函数测试”:
#include <gtest/gtest.h>// 要测试的绝对值函数
int abs(int x) {return x > 0 ? x : -x;
}// 测试套件:abs_test;测试用例:test1
TEST(abs_test, test1) {ASSERT_TRUE(abs(1) == 1) << "abs(1) 应该等于 1";ASSERT_TRUE(abs(-1) == 1) << "abs(-1) 应该等于 1";ASSERT_FALSE(abs(-2) == -2) << "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) {// 初始化GTest框架testing::InitGoogleTest(&argc, argv);// 运行所有测试用例return RUN_ALL_TESTS();
}
编译并运行:
g++ gtest_demo.cpp -o gtest_demo -lgtest -lgtest_main -pthread
./gtest_demo
如果输出类似 “[==========] Running 1 test from 1 test suite.” 的测试报告,说明 GTest 安装和运行都没问题。
四、GTest 实战:从 “单测入门” 到 “工程化测试”
光说不练假把式,我们通过几个场景,看看 GTest 在实际开发中怎么用。
示例 1:基础断言与测试用例组织
我们以 “字符串工具类” 为例,测试它的 “拼接” 和 “长度计算” 功能:
#include <gtest/gtest.h>
#include <string>// 要测试的字符串工具类
class StringUtil {
public:static std::string concat(const std::string& a, const std::string& b) {return a + b;}static size_t length(const std::string& s) {return s.length();}
};// 测试套件:StringUtilTest
TEST(StringUtilTest, ConcatTest) {std::string res = StringUtil::concat("hello", "world");ASSERT_EQ(res, "helloworld");ASSERT_EQ(StringUtil::length(res), 10);
}TEST(StringUtilTest, LengthTest) {ASSERT_EQ(StringUtil::length(""), 0);ASSERT_EQ(StringUtil::length("gtest"), 5);
}int main(int argc, char** argv) {testing::InitGoogleTest(&argc, argv);return RUN_ALL_TESTS();
}
这个例子中,我们把两个相关的测试用例(ConcatTest和LengthTest)放到StringUtilTest套件下,结构非常清晰。
示例 2:参数化测试(减少重复代码)
如果要测试 “多个输入下的同一逻辑”,参数化测试能帮你少写很多重复代码。比如测试 “加法函数” 在不同输入下的结果:
#include <gtest/gtest.h>// 要测试的加法函数
int add(int a, int b) {return a + b;
}// 定义参数化测试的参数类型
class AddParamTest : public testing::TestWithParam<std::tuple<int, int, int>> {
};// 参数化测试用例
TEST_P(AddParamTest, Test) {int a = std::get<0>(GetParam());int b = std::get<1>(GetParam());int expected = std::get<2>(GetParam());ASSERT_EQ(add(a, b), expected);
}// 注册参数
INSTANTIATE_TEST_SUITE_P(AddTest,AddParamTest,testing::Values(std::make_tuple(1, 2, 3),std::make_tuple(0, 0, 0),std::make_tuple(-1, 2, 1),std::make_tuple(100, 200, 300))
);int main(int argc, char** argv) {testing::InitGoogleTest(&argc, argv);return RUN_ALL_TESTS();
}
这样,GTest 会自动遍历所有参数,执行测试用例,大大减少了代码冗余。
示例 3:事件机制(测试环境的初始化与清理)
在实际测试中,经常需要 “测试前准备数据,测试后清理数据”,GTest 的事件机制就能帮你实现这个需求。
GTest 的事件分为三个层级:
- 全局事件:针对整个测试程序,在所有测试开始前执行一次初始化,所有测试结束后执行一次清理。
- 测试套件事件:针对某个测试套件,在该套件的所有用例开始前初始化,所有用例结束后清理。
- 测试用例事件:针对单个测试用例,在用例开始前初始化,用例结束后清理。
我们以 “全局事件” 为例,实现一个 “测试前加载配置,测试后释放配置” 的逻辑:
#include <gtest/gtest.h>
#include <unordered_map>
#include <string>// 全局配置字典
std::unordered_map<std::string, std::string> g_config;// 自定义环境类,继承自testing::Environment
class GlobalEnv : public testing::Environment {
public:// 测试程序启动前执行void SetUp() override {std::cout << "全局初始化:加载配置..." << std::endl;g_config["db_host"] = "127.0.0.1";g_config["db_port"] = "3306";}// 测试程序结束后执行void TearDown() override {std::cout << "全局清理:释放配置..." << std::endl;g_config.clear();}
};// 测试用例:验证配置是否加载
TEST(ConfigTest, LoadTest) {ASSERT_EQ(g_config["db_host"], "127.0.0.1");ASSERT_EQ(g_config["db_port"], "3306");
}int main(int argc, char** argv) {// 注册全局事件testing::AddGlobalTestEnvironment(new GlobalEnv());testing::InitGoogleTest(&argc, argv);return RUN_ALL_TESTS();
}
运行这个程序,你会看到 “加载配置” 在所有测试前执行,“释放配置” 在所有测试后执行,完美实现了测试环境的管理。
五、GTest 工程化实践:那些你该知道的细节
1. 致命断言 vs 非致命断言
- 致命断言:以
ASSERT_xxx开头(如ASSERT_EQ),断言失败时当前测试用例直接终止。 - 非致命断言:以
EXPECT_xxx开头(如EXPECT_EQ),断言失败时当前测试用例继续执行后续逻辑。 - 选择原则:如果一个断言失败后,后续逻辑无意义,用
ASSERT_xxx;否则用EXPECT_xxx。
2. 死亡测试(验证程序异常退出)
如果要测试 “程序在异常输入下是否会按预期退出”,可以用死亡测试:
#include <gtest/gtest.h>// 一个会崩溃的函数
void crash() {int* p = nullptr;*p = 10; // 解引用空指针,程序崩溃
}// 死亡测试用例
TEST(DeathTest, CrashTest) {// 断言程序执行crash()时会崩溃(退出码非0)ASSERT_DEATH(crash(), "");
}
3. 与构建系统的集成
在实际项目中,建议把 GTest 测试代码和业务代码分开编译。比如用 CMake 的话,可以这样配置:
# 查找GTest库
find_package(GTest REQUIRED)# 测试可执行文件
add_executable(test_my_lib test_my_lib.cpp)
# 链接GTest和业务库
target_link_libraries(test_my_lib PRIVATE my_lib GTest::GTest GTest::Main)# 添加测试目标
add_test(NAME test_my_lib COMMAND test_my_lib)
