本文是专门为 C++ 测试新手编写的 gtest 教程,通过大量实际例子和通俗易懂的讲解,帮助您快速掌握这个强大的测试框架。
1. 什么是 Google Test?
1.1 测试框架是什么?
想象一下,你写了一段代码,想知道它是否按预期工作。你可以手动测试,但这样效率很低。测试框架就是帮你自动化这个过程的工具。
Google Test(gtest) 是 Google 开发的一个 C++ 测试框架,它让你能够:
- 🚀 自动运行大量测试用例
- ✅ 快速验证代码是否正确
- 🔄 回归测试确保修改不破坏现有功能
- 📊 生成报告展示测试结果
1.2 为什么需要测试?
cpp
// 假设你写了一个计算器函数
int Divide(int a, int b) {
return a / b;
}
// 手动测试很麻烦:
// 1. 编译程序
// 2. 运行程序
// 3. 输入测试数据
// 4. 检查输出是否正确
// 5. 重复以上步骤...
// 使用 gtest,你只需要写一次测试,然后自动运行
2. 环境搭建
2.1 安装 gtest
在 Ubuntu/Linux 上:
bash
sudo apt-get install libgtest-dev
sudo apt-get install cmake # 如果还没有安装 cmake
使用 CMake 管理项目(推荐):
cmake
cmake_minimum_required(VERSION 3.14)
project(MyProject)
# 查找 gtest
find_package(GTest REQUIRED)
# 创建可执行文件
add_executable(my_tests test.cpp)
# 链接 gtest 库
target_link_libraries(my_tests GTest::gtest GTest::gtest_main)
2.2 验证安装
创建一个简单的测试文件 test_basic.cpp:
cpp
#include <gtest/gtest.h>
TEST(BasicTest, OnePlusOne) {
EXPECT_EQ(1 + 1, 2);
}
int main(int argc, char **argv) {
testing::InitGoogleTest(&argc, argv);
return RUN_ALL_TESTS();
}
编译并运行:
bash
g++ -std=c++11 test_basic.cpp -lgtest -lgtest_main -pthread -o test_basic
./test_basic
如果看到测试通过的输出,说明安装成功!
3. 第一个 gtest 程序
让我们从一个完整的例子开始:
cpp
#include <gtest/gtest.h>
#include <string>
// 要测试的函数
std::string Greet(const std::string& name) {
return "Hello, " + name + "!";
}
int Add(int a, int b) {
return a + b;
}
// 最简单的测试用例
TEST(FunctionTest, GreetFunction) {
EXPECT_EQ(Greet("Alice"), "Hello, Alice!");
EXPECT_EQ(Greet("Bob"), "Hello, Bob!");
}
TEST(FunctionTest, AddFunction) {
EXPECT_EQ(Add(2, 3), 5);
EXPECT_EQ(Add(-1, 1), 0);
EXPECT_EQ(Add(0, 0), 0);
}
// 另一个测试套件
TEST(StringTest, BasicOperations) {
std::string str = "hello";
EXPECT_EQ(str.length(), 5);
EXPECT_EQ(str + " world", "hello world");
EXPECT_NE(str, "goodbye");
}
int main(int argc, char **argv) {
// 初始化 gtest
testing::InitGoogleTest(&argc, argv);
std::cout << "🎯 开始运行测试..." << std::endl;
// 运行所有测试
return RUN_ALL_TESTS();
}
输出结果:
🎯 开始运行测试...
[==========] Running 3 tests from 2 test suites.
[----------] Global test environment set-up.
[----------] 2 tests from FunctionTest
[ RUN ] FunctionTest.GreetFunction
[ OK ] FunctionTest.GreetFunction (0 ms)
[ RUN ] FunctionTest.AddFunction
[ OK ] FunctionTest.AddFunction (0 ms)
[----------] 2 tests from FunctionTest (0 ms total)
[----------] 1 test from StringTest
[ RUN ] StringTest.BasicOperations
[ OK ] StringTest.BasicOperations (0 ms)
[----------] 1 test from StringTest (0 ms total)
[----------] Global test environment tear-down.
[==========] 3 tests from 2 test suites ran. (1 ms total)
[ PASSED ] 3 tests.
4. 理解 TEST 宏
4.1 TEST 宏的基本结构
cpp
TEST(TestSuiteName, TestName) {
// 测试代码
}
- TestSuiteName:测试套件名称,用于分组相关的测试
- TestName:具体的测试名称,描述要测试什么
4.2 实际例子
cpp
#include <gtest/gtest.h>
TEST(MathTest, Addition) {
// 这个测试属于 "MathTest" 套件,测试加法
EXPECT_EQ(1 + 1, 2);
EXPECT_EQ(10 + 20, 30);
}
TEST(MathTest, Subtraction) {
// 同样属于 "MathTest" 套件,但测试减法
EXPECT_EQ(5 - 3, 2);
EXPECT_EQ(0 - 5, -5);
}
TEST(StringTest, Length) {
// 这个测试属于 "StringTest" 套件
std::string str = "hello";
EXPECT_EQ(str.length(), 5);
}
TEST(StringTest, Concatenation) {
std::string s1 = "hello";
std::string s2 = " world";
EXPECT_EQ(s1 + s2, "hello world");
}
5. 常用断言宏
gtest 提供了丰富的断言宏来验证结果:
5.1 基本断言
cpp
TEST(AssertionTest, BasicExamples) {
int value = 42;
std::string text = "hello";
// 相等检查
EXPECT_EQ(value, 42); // 期望相等
ASSERT_EQ(value, 42); // 断言相等(失败则终止测试)
// 不等检查
EXPECT_NE(value, 0); // 期望不等
// 大小比较
EXPECT_GT(value, 30); // 大于
EXPECT_LT(value, 50); // 小于
EXPECT_GE(value, 42); // 大于等于
EXPECT_LE(value, 42); // 小于等于
// 布尔检查
EXPECT_TRUE(value > 0); // 期望为真
EXPECT_FALSE(value < 0); // 期望为假
// 字符串检查
EXPECT_STREQ(text.c_str(), "hello"); // C字符串相等
EXPECT_STRNE(text.c_str(), "world"); // C字符串不等
}
5.2 EXPECT vs ASSERT 的区别
cpp
TEST(ExpectVsAssert, Difference) {
// EXPECT: 测试继续执行,即使失败
EXPECT_EQ(1, 2); // 失败,但继续执行
EXPECT_EQ(3, 3); // 这个仍然会执行
// ASSERT: 测试立即终止,如果失败
ASSERT_EQ(1, 1); // 成功,继续执行
// ASSERT_EQ(1, 2); // 如果取消注释,测试会在这里终止
// EXPECT_EQ(3, 3); // 这行不会执行
}
5.3 浮点数比较
cpp
TEST(FloatingPointTest, FloatComparison) {
double a = 1.0 / 3.0;
double b = 0.333333;
// 错误的比较方式
// EXPECT_EQ(a, b); // 可能失败,因为浮点数精度问题
// 正确的比较方式
EXPECT_NEAR(a, b, 0.001); // 允许0.001的误差
EXPECT_DOUBLE_EQ(1.0, 1.0); // 精确比较double
EXPECT_FLOAT_EQ(1.0f, 1.0f); // 精确比较float
}
6. 局部环境测试(Test Fixtures)
6.1 什么是测试夹具?
想象你要测试一个银行账户类,每个测试都需要:
- 创建一个账户对象
- 设置初始余额
- 测试完成后清理资源
测试夹具(Test Fixtures) 就是帮你自动化这个过程的工具。
6.2 创建测试夹具
cpp
#include <gtest/gtest.h>
#include <string>
#include <vector>
// 业务类:银行账户
class BankAccount {
public:
BankAccount(const std::string& name, double balance)
: name_(name), balance_(balance) {}
void Deposit(double amount) {
balance_ += amount;
transaction_count_++;
}
bool Withdraw(double amount) {
if (amount <= balance_) {
balance_ -= amount;
transaction_count_++;
return true;
}
return false;
}
double GetBalance() const { return balance_; }
std::string GetName() const { return name_; }
int GetTransactionCount() const { return transaction_count_; }
private:
std::string name_;
double balance_;
int transaction_count_ = 0;
};
// 测试夹具类 - 必须继承 testing::Test
class BankAccountTest : public testing::Test {
protected:
// 每个测试用例开始前执行
void SetUp() override {
std::cout << "🔄 设置银行账户测试环境" << std::endl;
// 创建测试账户
account1 = new BankAccount("张三", 1000.0);
account2 = new BankAccount("李四", 500.0);
}
// 每个测试用例结束后执行
void TearDown() override {
std::cout << "🧹 清理银行账户测试环境" << std::endl;
// 清理资源
delete account1;
delete account2;
account1 = nullptr;
account2 = nullptr;
}
// 辅助函数
void ResetAccounts() {
delete account1;
delete account2;
account1 = new BankAccount("张三", 1000.0);
account2 = new BankAccount("李四", 500.0);
}
// 所有测试用例都可以访问的成员
BankAccount* account1;
BankAccount* account2;
};
6.3 使用 TEST_F 宏
cpp
// 使用 TEST_F 而不是 TEST
// F 代表 Fixture(夹具)
TEST_F(BankAccountTest, InitialState) {
// 可以直接使用夹具中的 account1 和 account2
EXPECT_EQ(account1->GetBalance(), 1000.0);
EXPECT_EQ(account1->GetName(), "张三");
EXPECT_EQ(account2->GetBalance(), 500.0);
EXPECT_EQ(account2->GetName(), "李四");
}
TEST_F(BankAccountTest, DepositOperations) {
// 测试存款
account1->Deposit(500.0);
EXPECT_EQ(account1->GetBalance(), 1500.0);
EXPECT_EQ(account1->GetTransactionCount(), 1);
account1->Deposit(100.0);
EXPECT_EQ(account1->GetBalance(), 1600.0);
EXPECT_EQ(account1->GetTransactionCount(), 2);
}
TEST_F(BankAccountTest, WithdrawOperations) {
// 测试取款
bool success = account1->Withdraw(300.0);
EXPECT_TRUE(success);
EXPECT_EQ(account1->GetBalance(), 700.0);
// 测试余额不足
success = account1->Withdraw(2000.0);
EXPECT_FALSE(success);
EXPECT_EQ(account1->GetBalance(), 700.0); // 余额不变
}
6.4 测试夹具的执行流程
对于上面的 BankAccountTest:
运行 TEST_F(BankAccountTest, InitialState):
1. BankAccountTest::SetUp() - 创建账户
2. 执行测试代码
3. BankAccountTest::TearDown() - 删除账户
运行 TEST_F(BankAccountTest, DepositOperations):
1. BankAccountTest::SetUp() - 创建新账户(重新初始化)
2. 执行测试代码
3. BankAccountTest::TearDown() - 删除账户
关键点:每个测试用例都有全新的环境,互不干扰!
7. 全局环境测试(Global Test Environment)
7.1 什么是全局环境?
当所有测试用例需要共享相同的全局设置时,可以使用全局环境。
适用场景:
- 🌐 建立数据库连接
- ⚙️ 初始化全局配置
- 📁 准备测试文件
- ⏱️ 统计测试总时间
7.2 创建全局环境
cpp
#include <gtest/gtest.h>
#include <iostream>
#include <chrono>
#include <fstream>
// 全局测试环境类 - 必须继承 testing::Environment
class GlobalTestEnvironment : public testing::Environment {
public:
// 在所有测试开始前执行(只执行一次)
virtual void SetUp() override {
std::cout << "🌍 全局测试环境初始化" << std::endl;
std::cout << "================================" << std::endl;
start_time = std::chrono::steady_clock::now();
// 模拟全局初始化工作
database_connected = ConnectToDatabase();
config_loaded = LoadConfiguration();
test_file_created = CreateTestFile();
std::cout << "✅ 数据库连接: " << (database_connected ? "成功" : "失败") << std::endl;
std::cout << "✅ 配置文件加载: " << (config_loaded ? "成功" : "失败") << std::endl;
std::cout << "✅ 测试文件创建: " << (test_file_created ? "成功" : "失败") << std::endl;
}
// 在所有测试结束后执行(只执行一次)
virtual void TearDown() override {
auto end_time = std::chrono::steady_clock::now();
auto duration = std::chrono::duration_cast<std::chrono::milliseconds>(
end_time - start_time);
std::cout << "================================" << std::endl;
std::cout << "🌍 全局测试环境清理" << std::endl;
std::cout << "⏱️ 测试总耗时: " << duration.count() << " 毫秒" << std::endl;
// 模拟全局清理工作
DisconnectDatabase();
CleanupTestFile();
std::cout << "✅ 数据库已断开连接" << std::endl;
std::cout << "✅ 测试文件已清理" << std::endl;
}
// 全局状态访问方法
static bool IsDatabaseConnected() { return database_connected; }
static bool IsConfigLoaded() { return config_loaded; }
private:
// 模拟全局操作
bool ConnectToDatabase() {
std::cout << "连接数据库中..." << std::endl;
return true;
}
bool LoadConfiguration() {
std::cout << "加载配置文件中..." << std::endl;
return true;
}
bool CreateTestFile() {
std::ofstream file("test_temp.txt");
if (file) {
file << "临时测试文件内容";
file.close();
return true;
}
return false;
}
void DisconnectDatabase() {
database_connected = false;
}
void CleanupTestFile() {
remove("test_temp.txt");
}
std::chrono::steady_clock::time_point start_time;
// 全局状态
static bool database_connected;
static bool config_loaded;
static bool test_file_created;
};
// 初始化静态成员
bool GlobalTestEnvironment::database_connected = false;
bool GlobalTestEnvironment::config_loaded = false;
bool GlobalTestEnvironment::test_file_created = false;
7.3 注册和使用全局环境
cpp
// 普通的测试用例
TEST(DatabaseTest, ConnectionStatus) {
// 可以检查全局环境的状态
EXPECT_TRUE(GlobalTestEnvironment::IsDatabaseConnected());
EXPECT_TRUE(GlobalTestEnvironment::IsConfigLoaded());
}
TEST(APITest, BasicFunctionality) {
// 假设这些测试依赖于全局的数据库连接
EXPECT_TRUE(GlobalTestEnvironment::IsDatabaseConnected());
// 模拟一些测试逻辑
int result = 2 + 3;
EXPECT_EQ(result, 5);
}
// 文件操作测试
TEST(FileTest, FileExistence) {
// 检查测试文件是否存在(依赖全局环境创建的文件)
std::ifstream file("test_temp.txt");
bool file_exists = file.good();
file.close();
EXPECT_TRUE(file_exists);
}
int main(int argc, char **argv) {
// 初始化 gtest
testing::InitGoogleTest(&argc, argv);
// 注册全局测试环境 - 必须步骤!
testing::AddGlobalTestEnvironment(new GlobalTestEnvironment);
std::cout << "🚀 开始运行测试套件..." << std::endl;
int result = RUN_ALL_TESTS();
std::cout << "🎉 测试套件运行完成" << std::endl;
return result;
}
8. TEST vs TEST_F:关键区别
8.1 语法区别
cpp
// TEST 宏:用于简单测试
TEST(TestSuiteName, TestName) {
// 测试代码
}
// TEST_F 宏:用于基于夹具的测试
TEST_F(TestFixtureClassName, TestName) {
// 测试代码
}
8.2 功能对比
| 特性 | TEST 宏 | TEST_F 宏 |
|---|---|---|
| 是否需要夹具类 | ❌ 不需要 | ✅ 必须继承 ::testing::Test |
| 设置清理 | 手动处理 | 自动通过 SetUp()/TearDown() |
| 数据共享 | 不能共享设置代码 | 共享夹具类的成员和方法 |
| 执行时机 | 直接执行 | 每个测试前调用SetUp,后调用TearDown |
| 适用场景 | 简单、独立测试 | 复杂初始化、资源管理 |
8.3 实际选择指南
使用 TEST:
cpp
// 简单数学运算
TEST(MathTest, BasicOperations) {
EXPECT_EQ(1 + 1, 2);
EXPECT_EQ(10 * 2, 20);
}
// 字符串操作
TEST(StringTest, Concatenation) {
std::string s1 = "hello";
std::string s2 = " world";
EXPECT_EQ(s1 + s2, "hello world");
}
使用 TEST_F:
cpp
// 需要复杂设置的测试
class DatabaseTest : public testing::Test {
protected:
void SetUp() override { /* 连接数据库 */ }
void TearDown() override { /* 断开连接 */ }
};
TEST_F(DatabaseTest, QueryTest) {
// 测试数据库查询
}
TEST_F(DatabaseTest, InsertTest) {
// 测试数据插入
}
9. 完整项目示例
让我们用一个完整的例子来总结所学内容:
cpp
#include <gtest/gtest.h>
#include <iostream>
#include <string>
#include <vector>
#include <memory>
#include <stdexcept>
// ==================== 全局环境 ====================
class ProjectTestEnvironment : public testing::Environment {
public:
static int total_tests;
void SetUp() override {
std::cout << "🧪 项目测试套件开始" << std::endl;
total_tests = 0;
std::cout << "初始化日志系统..." << std::endl;
std::cout << "加载应用配置..." << std::endl;
}
void TearDown() override {
std::cout << "🧪 项目测试套件结束" << std::endl;
std::cout << "📊 总共运行了 " << total_tests << " 个测试" << std::endl;
std::cout << "清理日志系统..." << std::endl;
}
};
int ProjectTestEnvironment::total_tests = 0;
// ==================== 业务代码 ====================
class Calculator {
public:
int Add(int a, int b) { return a + b; }
int Subtract(int a, int b) { return a - b; }
int Multiply(int a, int b) { return a * b; }
double Divide(double a, double b) {
if (b == 0) throw std::invalid_argument("除数不能为零");
return a / b;
}
};
class UserManager {
public:
void AddUser(const std::string& name) {
users.push_back(name);
user_count++;
}
bool UserExists(const std::string& name) {
for (const auto& user : users) {
if (user == name) return true;
}
return false;
}
int GetUserCount() { return user_count; }
void ClearUsers() {
users.clear();
user_count = 0;
}
private:
std::vector<std::string> users;
int user_count = 0;
};
// ==================== 局部测试环境 ====================
class CalculatorTest : public testing::Test {
protected:
void SetUp() override {
calculator = std::make_unique<Calculator>();
ProjectTestEnvironment::total_tests++;
}
void TearDown() override {
// unique_ptr 自动管理内存
}
std::unique_ptr<Calculator> calculator;
};
class UserManagerTest : public testing::Test {
protected:
void SetUp() override {
manager = std::make_unique<UserManager>();
ProjectTestEnvironment::total_tests++;
// 预先添加测试用户
manager->AddUser("admin");
manager->AddUser("guest");
}
void TearDown() override {
// unique_ptr 自动清理
}
std::unique_ptr<UserManager> manager;
};
// ==================== 测试用例 ====================
// 使用 TEST 的简单测试
TEST(SimpleMathTest, BasicOperations) {
EXPECT_EQ(1 + 1, 2);
EXPECT_EQ(10 * 2, 20);
EXPECT_GT(100, 50);
}
// 使用 TEST_F 的计算器测试
TEST_F(CalculatorTest, Addition) {
EXPECT_EQ(calculator->Add(2, 3), 5);
EXPECT_EQ(calculator->Add(-1, 1), 0);
EXPECT_EQ(calculator->Add(0, 0), 0);
}
TEST_F(CalculatorTest, Multiplication) {
EXPECT_EQ(calculator->Multiply(4, 5), 20);
EXPECT_EQ(calculator->Multiply(0, 100), 0);
EXPECT_EQ(calculator->Multiply(-2, 3), -6);
}
TEST_F(CalculatorTest, Division) {
EXPECT_EQ(calculator->Divide(10, 2), 5);
EXPECT_EQ(calculator->Divide(9, 3), 3);
EXPECT_THROW(calculator->Divide(10, 0), std::invalid_argument);
}
// 使用 TEST_F 的用户管理器测试
TEST_F(UserManagerTest, InitialUsers) {
EXPECT_EQ(manager->GetUserCount(), 2);
EXPECT_TRUE(manager->UserExists("admin"));
EXPECT_TRUE(manager->UserExists("guest"));
}
TEST_F(UserManagerTest, AddNewUsers) {
manager->AddUser("alice");
manager->AddUser("bob");
EXPECT_EQ(manager->GetUserCount(), 4);
EXPECT_TRUE(manager->UserExists("alice"));
EXPECT_TRUE(manager->UserExists("bob"));
EXPECT_FALSE(manager->UserExists("charlie"));
}
TEST_F(UserManagerTest, ClearUsers) {
manager->ClearUsers();
EXPECT_EQ(manager->GetUserCount(), 0);
EXPECT_FALSE(manager->UserExists("admin"));
}
// ==================== 主函数 ====================
int main(int argc, char **argv) {
testing::InitGoogleTest(&argc, argv);
// 注册全局环境
testing::AddGlobalTestEnvironment(new ProjectTestEnvironment);
std::cout << "==========================================" << std::endl;
std::cout << " Google Test 完整示例程序" << std::endl;
std::cout << " 演示 TEST 和 TEST_F 的用法" << std::endl;
std::cout << "==========================================" << std::endl;
int result = RUN_ALL_TESTS();
std::cout << "==========================================" << std::endl;
std::cout << "程序退出代码: " << result << " (0表示所有测试通过)" << std::endl;
return result;
}
10. 编译和运行
10.1 使用 g++ 编译
bash
# 编译并运行完整示例
g++ -std=c++11 -o my_tests main.cpp -lgtest -lgtest_main -pthread
./my_tests
10.2 使用 CMake(推荐)
创建 CMakeLists.txt:
cmake
cmake_minimum_required(VERSION 3.14)
project(MyGTestProject)
find_package(GTest REQUIRED)
add_executable(my_tests main.cpp)
target_link_libraries(my_tests GTest::gtest GTest::gtest_main pthread)
编译运行:
bash
mkdir build
cd build
cmake ..
make
./my_tests
11. 最佳实践和常见问题
11.1 测试命名规范
cpp
// 好的命名
TEST(UserAuthenticationTest, ValidLoginSucceeds)
TEST(CalculatorTest, DivisionByZeroThrowsException)
TEST(FileManagerTest, CreateNewFileWithValidPath)
// 不好的命名
TEST(Test1, Test1) // 太模糊
TEST(user, test) // 不清晰
11.2 测试组织原则
cpp
// 相关测试分组到同一个套件
TEST(NetworkTest, ConnectToServer)
TEST(NetworkTest, SendData)
TEST(NetworkTest, ReceiveData)
// 不同功能使用不同套件
TEST(DatabaseTest, InsertRecord)
TEST(DatabaseTest, QueryRecords)
TEST(FileSystemTest, CreateFile)
TEST(FileSystemTest, DeleteFile)
11.3 常见错误和解决方案
错误1:忘记链接 pthread
错误:未定义的引用 to 'pthread_create'
解决:编译时添加 -pthread 参数
错误2:TEST_F 使用错误的夹具类名
cpp
// 错误
TEST_F(WrongClassName, TestName) // 夹具类不存在
// 正确
TEST_F(CalculatorTest, TestName) // 使用已定义的夹具类
错误3:全局环境忘记注册
cpp
// 错误:只定义不注册
class MyEnvironment : public testing::Environment { /* ... */ };
// 正确:必须注册
testing::AddGlobalTestEnvironment(new MyEnvironment);
12. 总结
通过这个完整的教程,我们学习了:
📚 核心概念
- gtest 基础:测试框架的作用和优势
- TEST 宏:用于简单测试,参数是测试套件名和测试名
- TEST_F 宏:用于基于夹具的测试,参数是夹具类名和测试名
- 断言宏:丰富的验证工具(EXPECT_EQ, ASSERT_TRUE等)
🛠️ 环境管理
- 局部环境(夹具):用于测试用例级别的设置和清理
- 全局环境:用于测试套件级别的设置和清理
- 关键区别:局部环境不需要注册,全局环境必须注册
🎯 实践技巧
- TEST vs TEST_F:根据测试复杂度选择合适的方法
- 测试组织:合理的命名和分组
- 编译运行:多种编译方式的选择
🚀 下一步学习
掌握了基础之后,您可以继续学习:
- 参数化测试(TEST_P)
- 类型参数化测试
- 死亡测试(检查程序崩溃)
- 模拟对象(Google Mock)
记住:好的测试是代码质量的守护者。开始在自己的项目中使用 gtest,您会发现代码变得更加健壮和可靠!
Happy testing!