对于单元测试,使用Gtest、Gmock 等工具提升代码覆盖率学习总结。
Unitest
代码覆盖率(Code coverage)是软件测试中的一种度量,描述程序中源代码被测试的比例和程度,所得比例称为代码覆盖率。
单元测试主要包括三方面测试:行覆盖,分支覆盖,功能覆盖:
行覆盖 = 已执行的语句数目 / 所有语句的总数目 * 100%;
分支覆盖 = 已执行的分支数目 / 分支总数目 * 100%;
功能覆盖 = 已执行的功能数目 /功能总数目 * 100%;
Google Test
Google Test 是一个测试框架,允许开发人员在 C++中编写和运行单元测试。它是由 Google 开发的,作为其开源软件项目的一部分,现在在软件开发社区中被广泛使用。Google Test 提供了一种简单高效的方法来为代码的单个单元编写测试,例如类或函数,并验证它们是否正确。它还提供了报告测试结果和管理测试套件的工具。
GTest、Gmock
标题 | GTest、Gmock 学习 |
简介 | GTest 和 Gmock 是 Google 开发的一套 C++测试框架,用于单元测试和模拟对象。它们可以帮助您编写可重复、可靠和可扩展的测试用例,并简化代码的调试。本文档将介绍如何使用 GTest 和 Gmock 进行单元测试和对象模拟。 |
使用 GTest 进行单元测试 | 1. 创建 GTest 项目:使用 CMake 或其他构建系统创建一个 GTest 项目,并在您的代码中包含 GTest 头文件和库文件。 2. 编写测试用例:定义一个公共的测试函数,并使用 GTest 提供的断言和测试宏来编写测试用例。 3. 运行测试:使用 GTest 提供的命令行工具或集成开发环境(IDE)来运行测试用例。 4. 分析测试结果:检查测试用例的执行结果,并通过断言失败来定位代码中的问题。 |
使用 Gmock 进行对象模拟 | 1. 创建 Gmock 项目:使用 CMake 或其他构建系统创建一个 Gmock 项目,并在您的代码中包含 Gmock 头文件和库文件。 2. 定义模拟对象:创建一个继承自 Gmock 提供的 Mock 类的模拟对象,并定义其期望的行为。 3. 使用模拟对象进行测试:在测试用例中使用模拟对象代替实际的对象,并验证其行为是否符合预期。 4. 分析测试结果:检查模拟对象的行为是否符合预期,并通过模拟对象的回调函数来验证其内部逻辑。 |
断言
基本断言
失败时中断执行的断言 | 失败时不中断执行的断言 | 断言成功情况 |
ASSERT_TRUE(condition); | EXPECT_TRUE(condition); | condition为真 |
ASSERT_FALSE(condition); | EXPECT_FALSE(condition); | condition为假 |
普通比较型断言
失败时中断执行的断言 | 失败时不中断执行的断言 | 断言成功情况 |
ASSERT_EQ(val1, val2); | EXPECT_EQ(val1, val2); | val1 == val2 |
ASSERT_NE(val1, val2); | EXPECT_NE(val1, val2); | val1 != val2 |
ASSERT_LT(val1, val2); | EXPECT_LT(val1, val2); | val1 < val2 |
ASSERT_LE(val1, val2); | EXPECT_LE(val1, val2); | val1 <= val2 |
ASSERT_GT(val1, val2); | EXPECT_GT(val1, val2); | val1 > val2 |
ASSERT_GE(val1, val2); | EXPECT_GE(val1, val2); | val1 >= val2 |
C字符串比较型断言
str1和str2是两个C字符串,以下断言对它们的值进行比较;如果要比较两个std::string对象,直接用之前 提到的EXPECT_NE,EXPECT_NE等。
失败时中断执行的断言 | 失败时不中断执行的断言 | 断言成功情况 |
ASSERT_STREQ(str1,str2); | EXPECT_STREQ(str1,str2); | 这两个C字符串具有相同的内容 |
ASSERT_STRNE(str1,str2); | EXPECT_STRNE(str1,str2); | 两个C字符串的内容不同 |
ASSERT_STRCASEEQ(str1,str2); | EXPECT_STRCASEEQ(str1,str2); | 忽略大小写,两个C字符串的内容相同 |
ASSERT_STRCASENE(str1,str2); | EXPECT_STRCASENE(str1,str2) | 忽略大小写,两个C字符串的内容不同 |
例如:char *str1 = "ABC";char *str2 = "ABC";,EXPECT_STREQ(str1, str2);断言通过, 因为它们的内容一样;而EXPECT_EQ(str1, str2);断言失败,因为它们的地址不一样。
注意:一个NULL指针和一个空字符串""是不同的。
浮点数比较型断言
val1和val2是两个浮点数,以下断言对其进行比较。
失败时中断执行的断言 | 失败时不中断执行的断言 | 断言成功情况 |
ASSERT_FLOAT_EQ(val1, val2); | EXPECT_FLOAT_EQ(val1, val2); | 这两个float值几乎相等 |
ASSERT_DOUBLE_EQ(val1, val2); | EXPECT_DOUBLE_EQ(val1, val2); | 这两个double值几乎相等 |
以下断言可以选择可接受的误差范围:
失败时中断执行的断言 | 失败时不中断执行的断言 | 断言成功情况 |
ASSERT_NEAR(val1, val2, abs_error); | EXPECT_NEAR(val1, val2, abs_error); | val1和val2的差的绝对值不超过abs_error |
异常断言
这些断言验证一段代码是否抛出(或不抛出)给定类型的异常:
失败时中断执行的断言 | 失败时不中断执行的断言 | 断言成功情况 |
ASSERT_THROW(statement, exception_type); | EXPECT_THROW(statement, exception_type); | statement抛出给定类型的异常 |
ASSERT_ANY_THROW(statement); | EXPECT_ANY_THROW(statement); | statement抛出任何类型的异常 |
ASSERT_NO_THROW(statement); | EXPECT_NO_THROW(statement); | statement不抛出任何异常 |
分支覆盖率提升,主要涉及了模版、虚函数等。
SetUp() 和 TearDown() 确实是单元测试框架中的两个成员函数,这些功能常见于像 Google Test 这样的框架。这些函数通常用于测试用例或测试套件的设置(setup)和拆解(teardown)工作。让我们来看一下它们的用途:
SetUp()
函数 SetUp() 通常在每个测试用例之前自动执行。它的主要目的是为即将运行的测试用例准备测试环境。这可能涉及到:
- 初始化变量。
- 分配/构造测试所需的对象。
- 配置虚拟环境、设置预期值、打桩(stubbing)或模拟(mocking)。
- 打开需要的文件或数据库连接。
通过在 SetUp() 中做准备工作,可以确保每个测试用例启动时都具有清洁且一致的状态,从而使每个测试结果都是独立的。
TearDown()
函数 TearDown() 通常在每个测试用例执行完毕后自动执行。它用于清理 SetUp() 期间设置的状态。这可能包括:
- 释放 SetUp() 中分配的资源。
- 销毁构造的对象,释放内存。
- 关闭文件或数据库连接。
- 撤销测试中创建的任何变临时更改。
通过使用 TearDown() 清理,确保这次测试不会对后续的测试用例产生副作用,例如释放 SetUp() 中分配的任何动态内存,这有助于防止资源泄漏。
使用示例(以 Google Test 为例)
class MyTestFixture : public ::testing::Test {
protected:
// 定义需要使用的成员变量和对象
virtual void SetUp() override {
// 设置代码
}
virtual void TearDown() override {
// 清理代码
}
};
// 使用测试用例
TEST_F(MyTestFixture, Test1) {
// 这个测试会先运行 SetUp,然后是测试代码,最后运行 TearDown
}
在 Google Test 中,SetUp() 和 TearDown() 被定义在派生自 ::testing::Test 的测试套件类中,并且可以被重写以提供所需的功能。然后你可以使用 TEST_F 宏来写具体的测试用例。这里 F 指的是 Fixture,代指的就是上面定义的测试套件类 MyTestFixture。