C++: 单元测试
最后更新:2026-08-26
第49课我们学了性能优化。
现在,我们要学习单元测试——保证代码质量的关键技能。
写了代码不测试,就像做了菜不尝味道。
1. 单元测试概述
(1) 1.1 什么是单元测试?
单元测试(Unit Test)是对最小可测试单元(函数、类)进行测试。
优势:
- 早期发现问题
- 重构时有保障
- 文档化代码行为
(2) 1.2 测试框架选择
| 框架 | 说明 |
|---|---|
| Google Test | Google出品,最流行 |
| Catch2 | 单头文件,简单易用 |
| Boost.Test | Boost库的一部分 |
| Doctest | 类似Catch2,更快 |
推荐: Google Test(功能全)或 Catch2(简单)
2. Google Test入门
(1) 2.1 安装Google Test
Linux:
BASH
sudo apt-get install libgtest-dev
Windows: 用vcpkg或源码编译。
(2) 2.2 第一个测试
示例:用Google Test测试加法函数(难度⭐)
▶ 示例 2:代码示例(难度⭐)
CPP
#include <gtest/gtest.h>
int add(int a, int b) {
return a + b;
}
TEST(AddTest, Positive) {
EXPECT_EQ(add(2, 3), 5);
}
TEST(AddTest, Negative) {
EXPECT_EQ(add(-2, -3), -5);
}
int main(int argc, char** argv) {
::testing::InitGoogleTest(&argc, argv);
return RUN_ALL_TESTS();
}
输出:
TEXT
📖 仅展示
[==========] Running 2 tests from 1 test suite.
[ PASSED ] 2 tests.
运行结果:
TEXT
📖 仅展示
[==========] Running 2 tests from 1 test suite.
[ PASSED ] 2 tests.
3. 断言宏
(1) 3.1 ASSERT vs EXPECT
| 宏 | 失败时的行为 |
|---|---|
ASSERT_* |
终止当前测试 |
EXPECT_* |
继续运行 |
推荐: 用 EXPECT_*(能看到更多失败)
(2) 3.2 常用断言
| 断言 | 说明 |
|---|---|
EXPECT_EQ(a, b) |
a == b |
EXPECT_NE(a, b) |
a != b |
EXPECT_LT(a, b) |
a < b |
EXPECT_GT(a, b) |
a > b |
EXPECT_TRUE(cond) |
cond为真 |
EXPECT_FALSE(cond) |
cond为假 |
EXPECT_THROW(statement, exception_type) |
期望抛出异常 |
4. 测试夹具(Test Fixture)
(1) 4.1 为什么需要夹具?
问题: 多个测试需要相同的初始化代码。
解决方案: 测试夹具(继承 testing::Test)
(2) 4.2 示例:用夹具测试队列(难度⭐⭐)
CPP
#include <gtest/gtest.h>
#include <queue>
class QueueTest : public testing::Test {
protected:
void SetUp() override {
q.push(1);
q.push(2);
q.push(3);
}
void TearDown() override {
// 清理(如果需要)
}
std::queue<int> q;
};
TEST_F(QueueTest, Size) {
EXPECT_EQ(q.size(), 3);
}
TEST_F(QueueTest, Front) {
EXPECT_EQ(q.front(), 1);
}
int main(int argc, char** argv) {
::testing::InitGoogleTest(&argc, argv);
return RUN_ALL_TESTS();
}
输出:
TEXT
📖 仅展示
(程序输出)
5. 测试驱动开发(TDD)
(1) 5.1 什么是TDD?
TDD(Test-Driven Development)是指先写测试,再写代码。
流程:
- 写测试(失败)
- 写代码(通过)
- 重构
(2) 5.2 示例:TDD开发栈(难度⭐⭐⭐)
CPP
// 1. 先写测试
TEST(StackTest, PushAndPop) {
Stackint s;
s.push(42);
EXPECT_EQ(s.pop(), 42);
}
// 2. 再写代码
template<typename T>
class Stack {
std::vectorT data;
public:
void push(const T& x) { data.push_back(x); }
T pop() { T x = data.back(); data.pop_back(); return x; }
};
输出:
TEXT
📖 仅展示
(程序输出)
6. 实战:测试学生类
▶ 示例 1:完整测试(难度⭐⭐⭐)
CPP
#include <gtest/gtest.h>
#include <string>
class Student {
std::string name;
int age;
public:
Student(const std::string& name, int age) : name(name), age(age) {}
std::string getName() const { return name; }
int getAge() const { return age; }
};
TEST(StudentTest, Constructor) {
Student s("张三", 20);
EXPECT_EQ(s.getName(), "张三");
EXPECT_EQ(s.getAge(), 20);
}
int main(int argc, char** argv) {
::testing::InitGoogleTest(&argc, argv);
return RUN_ALL_TESTS();
}
输出:
TEXT
📖 仅展示
(程序输出)
▶ 示例 3:简单的单元测试(难度⭐)
CPP
#include <iostream>
#include <cassert>
// 被测试的函数
int multiply(int a, int b) {
return a * b;
}
bool isPositive(int n) {
return n > 0;
}
int main() {
// 测试 multiply
assert(multiply(2, 3) == 6);
assert(multiply(-2, 3) == -6);
assert(multiply(0, 5) == 0);
std::cout << "multiply 测试通过" << std::endl;
// 测试 isPositive
assert(isPositive(5) == true);
assert(isPositive(-1) == false);
assert(isPositive(0) == false);
std::cout << "isPositive 测试通过" << std::endl;
std::cout << "所有测试通过!" << std::endl;
return 0;
}
输出:
TEXT
📖 仅展示
multiply 测试通过
isPositive 测试通过
所有测试通过!
❓ 常见问题
Q:单元测试能发现所有bug吗? A:不能。单元测试只能发现已知类型的错误。还需要集成测试、系统测试。
Q 测试代码要和产品代码一起发布吗?
A 不需要。测试代码通常只在开发时使用。
Q 怎么测试私有函数?
A - 方法1:测试公有接口(间接测试) - 方法2:用FRIEND_TEST暴露私有成员 - 方法3:重构,把私有函数提取到独立类
📖 小节
| 知识点 | 要点 |
|---|---|
| 单元测试 | 测试最小单元 |
| Google Test | 最流行的C++测试框架 |
| 断言宏 | EXPECT_* 和 ASSERT_* |
| 测试夹具 | 共享初始化代码 |
| TDD | 先写测试,再写代码 |
📝 作业
-
**基础题 (Difficulty ⭐):写一个 add(a,b) 函数,用 Google Test 编写 3 个测试用例:正数相加、负数相加、零相加。
-
**进阶题 (Difficulty ⭐⭐):写一个 isPalindrome 函数判断字符串是否为回文。编写至少 5 个测试用例覆盖正常/边界/特殊输入。
-
**挑战题 (Difficulty ⭐⭐⭐):为一个"银行账户"类(含 deposit/withdraw/getBalance 方法)编写完整的单元测试。测试正常存取、透支拒绝、并发安全(如果有)。
- 单元测试验证函数或模块的正确性
- 测试框架:Google Test / Catch2 / doctest
- 测试用例三部分:准备→执行→断言
- 测试驱动开发(TDD):先写测试再写代码
- 覆盖率衡量测试的全面程度
下一课:实战:通讯录系统(#51)