作者:billy
版权声明:著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处
gtest 简介
GoogleTest(也称为gtest)是由 Google 开发的一个 C++ 单元测试框架,用于编写、组织和运行自动化测试代码。它支持断言(如 EXPECT_EQ、ASSERT_TRUE 等)、测试夹具(test fixtures)、参数化测试等高级功能,能够帮助开发者在开发过程中快速发现和定位问题。GoogleTest 具有良好的跨平台性,广泛用于 C++ 项目的测试验证,是工业界最常用的 C++ 测试框架之一
构建
gtest 源代码下载:github 下载
百度网盘下载:下载链接
提取码: kjef
百度网盘里除了源代码,还有编译好的动态库可以直接使用,编译环境是 vs2019 64位
在 qt 中使用 gtest 需要导入依赖库,把 googletest_msvc2019 文件夹放在 pro 文件同目录下,然后在 pro 文件中添加以下信息:
INCLUDEPATH += $$PWD/googletest_msvc2019/includewin32:CONFIG(release, debug|release): {LIBS += -L$$PWD/googletest_msvc2019/lib/Release/ -lgmockLIBS += -L$$PWD/googletest_msvc2019/lib/Release/ -lgmock_mainLIBS += -L$$PWD/googletest_msvc2019/lib/Release/ -lgtestLIBS += -L$$PWD/googletest_msvc2019/lib/Release/ -lgtest_main
}
else:win32:CONFIG(debug, debug|release): {LIBS += -L$$PWD/googletest_msvc2019/lib/Debug/ -lgmockLIBS += -L$$PWD/googletest_msvc2019/lib/Debug/ -lgmock_mainLIBS += -L$$PWD/googletest_msvc2019/lib/Debug/ -lgtestLIBS += -L$$PWD/googletest_msvc2019/lib/Debug/ -lgtest_main
}
gtest 语法
1. TEST
这是定义测试用例的基本宏。它接受两个参数:测试用例的名称和一个测试函数
TEST(TestSuiteName, TestName) { // 测试代码
}
2. TEST_F
用于定义继承自某个测试夹具(Test Fixture)的测试。测试夹具是一个类,用于设置测试前的环境和清理测试后的环境。TEST_F 宏接受两个参数:测试夹具的类名和测试名称
class MyFixture : public ::testing::Test {
protected: // 设置测试环境 void SetUp() override { // 初始化代码 } // 清理测试环境 void TearDown() override { // 清理代码 }
}; TEST_F(MyFixture, TestName) { // 测试代码
}
3. TEST_P
用于定义参数化测试。参数化测试允许你运行同一个测试代码,但是使用不同的参数集。首先,你需要定义一个测试夹具类并继承自 ::testing::TestWithParam,其中T是参数的类型。然后,使用 TEST_P 宏定义测试
class MyParameterizedTest : public ::testing::TestWithParam<int> {
protected: void SetUp() override { // 使用GetParam()获取参数 }
}; TEST_P(MyParameterizedTest, TestName) { // 测试代码,使用GetParam()获取参数
} INSTANTIATE_TEST_SUITE_P(instanceName, MyParameterizedTest, ::testing::Values(1, 2, 3, 4));
4. SCOPED_TRACE
用于在测试失败时添加额外的日志信息的宏。它在日志消息中添加一个额外的文本信息,这对于理解测试失败时的上下文非常有用。通常与 EXPECT_ 或 ASSERT_ 宏一起使用,以便在测试失败时提供更多的调试信息
SCOPED_TRACE("SubFunction called with value " << value);
5. EXPECT_ 和 ASSERT_ 系列宏
这些宏用于在测试中验证代码的行为。EXPECT_ 系列的宏在遇到失败时会记录错误并继续执行测试中的后续语句,而 ASSERT_ 系列的宏在遇到失败时会记录错误并终止当前测试。下面以非致命断言 EXPECT_ 系列为例:
非致命断言 | 结果 |
---|---|
EXPECT_TRUE(condition) | 检查 condition 是否为真 |
EXPECT_FALSE(condition) | 检查 condition 是否为假 |
- | - |
EXPECT_EQ(val1, val2) | val1 == val2 |
EXPECT_NE(val1, val2) | val1 != val2 |
EXPECT_LT(val1, val2) | val1 < val2 |
EXPECT_LE(val1, val2) | val1 <= val2 |
EXPECT_GT(val1, val2) | val1 > val2 |
EXPECT_GE(val1, val2) | val1 >= val2 |
- | - |
EXPECT_STREQ(str1, str2) | 用于比较两个C字符串是否相等 |
EXPECT_STRNE(str1, str2) | 用于比较两个C字符串是否不相等 |
EXPECT_STRCASEEQ(str1, str2) | 用于比较两个C字符串是否相等,忽略字母大小写差异 |
EXPECT_STRCASENE(str1, str2) | 用于比较两个C字符串是否不相等,忽略字母大小写差异 |
- | - |
EXPECT_FLOAT_EQ(val1, val2) | 检测浮点数 val1 和 val2 是否相等,允许一定的误差 |
EXPECT_DOUBLE_EQ(val1, val2) | 检测双精度浮点数 val1 和 val2 是否相等,允许一定的误差 |
EXPECT_NEAR(val1, val2, abs_error) | 检查浮点数 val1 和 val2 的差值的绝对值是否小于或等于 abs_error |
- | - |
EXPECT_THROW(statement, expected_exception) | 检测 statement 是否抛出指定类型的异常 expected_exception |
EXPECT_ANY_THROW(statement) | 检测 statement 是否抛出任何异常 |
ASSERT_NO_THROW(statement) | 检测 statement 是否不抛出异常 |
- | - |
EXPECT_DEATH(statement, regex) | 用于测试在执行 statement 时是否会导致程序崩溃,并且崩溃信息是否符合正则表达式 regex 的要求 |
EXPECT_THAT(actual, matcher) | 用于进行复杂的条件检查,特别是在使用匹配器时。matcher 用于检查 actual 的匹配器 |
6. SUCCEED 和 FAIL
SUCCEED() 会标记当前测试用例成功并立即结束测试。无论之前的测试代码是否存在错误,都会标记为成功,并且SUCCEED 后面的代码不会被执行
FAIL() 会标记当前测试用例为失败并立即结束测试。可以用来在测试代码中发现不可接受的状态时报告测试失败。在 FAIL() 之后的代码不会被执行。可以 FAIL() << “ ”;
用于在测试失败后输出一段信息
7. ADD_FAILURE 和 ADD_FAILURE_AT
ADD_FAILURE 用于手动添加测试失败的报告,而不依赖于某个特定断言的失败。可以在代码中的任何一个位置来标记,当执行到 ADD_FAILUR 时,gtest 会报告测试失败,但程序仍然会继续执行后续的代码
ADD_FAILURE_AT 允许在指定的文件名和行号处报告失败
Qt 中的应用示例
// 源代码功能:加载一个json文件存储在内存中
QJsonObject CommonFunction::getObjFromConfig(QString fileName)
{QFileInfo fileInfo(fileName);if ( !fileInfo.isFile() ) {return QJsonObject();}QFile file(fileName);if ( !file.open(QIODevice::ReadOnly) ) {return QJsonObject();}// get json objectQByteArray jsonData = file.readAll();// QString jsonString = QString::fromUtf8(jsonData);// qDebug() << jsonString;file.close();QJsonDocument jsonDoc(QJsonDocument::fromJson(jsonData));QJsonObject obj = jsonDoc.object();return obj;
}
创建一个头文件 test_getObjFromConfig.h 编写单元测试代码
#ifndef TEST_GETOBJFROMCONFIG_H
#define TEST_GETOBJFROMCONFIG_H#include <gtest/gtest.h>
#include "commonfunction.h"
#include <QTemporaryFile>class Test_GetObjFromConfig : public testing::Test
{
protected:CommonFunction* commonFun;void SetUp() override {commonFun = new CommonFunction();}void TearDown() override {delete commonFun;}
};// 测试文件不存在的情况
TEST_F(Test_GetObjFromConfig, FileNotFoundReturnsEmptyObject) {QString nonExistentFile = "non_existent_file.json";QJsonObject result = commonFun->getObjFromConfig(nonExistentFile);EXPECT_TRUE(result.isEmpty());
}// 测试文件无法打开的情况(例如:没有权限)
TEST_F(Test_GetObjFromConfig, UnreadableFileReturnsEmptyObject) {// 创建一个临时文件并立即关闭它QTemporaryFile tempFile("XXXXXX.json");tempFile.open();tempFile.close();// 设置文件权限为只读(假设当前用户无法写入)QFile file(tempFile.fileName());file.setPermissions(QFile::ReadOwner);QJsonObject result = commonFun->getObjFromConfig(tempFile.fileName());EXPECT_TRUE(result.isEmpty());
}// 测试有效的JSON文件
TEST_F(Test_GetObjFromConfig, ValidJsonFileReturnsParsedObject) {// 创建临时文件并写入有效的JSON内容QTemporaryFile tempFile("XXXXXX.json");if (tempFile.open()) {QString jsonContent = R"({"company":"swyl"})";tempFile.write(jsonContent.toUtf8());tempFile.close();QJsonObject result = commonFun->getObjFromConfig(tempFile.fileName());EXPECT_FALSE(result.isEmpty());EXPECT_EQ(result["company"].toString(), "swyl");} else {FAIL() << "无法创建临时文件";}
}// 测试无效的JSON文件
TEST_F(Test_GetObjFromConfig, InvalidJsonFileReturnsEmptyObject) {// 创建临时文件并写入无效的JSON内容QTemporaryFile tempFile("XXXXXX.json");if (tempFile.open()) {QString invalidJson = "invalid json content";tempFile.write(invalidJson.toUtf8());tempFile.close();QJsonObject result = commonFun->getObjFromConfig(tempFile.fileName());EXPECT_TRUE(result.isEmpty());} else {FAIL() << "无法创建临时文件";}
}// 测试空JSON文件
TEST_F(Test_GetObjFromConfig, EmptyFileReturnsEmptyObject) {// 创建空的临时文件QTemporaryFile tempFile("XXXXXX.json");if (tempFile.open()) {tempFile.close();QJsonObject result = commonFun->getObjFromConfig(tempFile.fileName());EXPECT_TRUE(result.isEmpty());} else {FAIL() << "无法创建临时文件";}
}#endif // TEST_GETOBJFROMCONFIG_H