作者: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