【C++】GoogleTest入門指南
- 2022 年 9 月 21 日
- 筆記
- C++, GoogleTest
參考:
GoogleTest官網
基本概念
要使用GoogleTest,需要包含header gtest/gtest.h
斷言Assertions
斷言是檢查條件是否為真的語句,其結果可能是成功或失敗,失敗分為非致命失敗和致命失敗兩種,後者會終止當前運行,前者則會繼續運行。
GoogleTest中,斷言類似於函數調用的宏,斷言失敗時,GoogleTest會輸出斷言的源文件和行號位置以及失敗消息(所有斷言都可以使用<<輸出自定義失敗消息)
ASSERT_*
會拋出致命失敗故障的斷言,斷言失敗時中止當前測試函數的運行(不是中斷整個TEST)。
ASSERT_EQ(x.size(),y.size()) << "x與y的大小不相同"
EXPECT_*
會拋出非致命失敗故障的斷言,不會停止當前函數運行,而是繼續往下運行下去
EXPECT_EQ(x,y) << "x與y不相等"
斷言分類
前綴都會是ASSERT_或者EXPECT_,它們的區別上面已經進行了說明,所以以下都用X_來略寫
基本斷言
- X_TRUE(condition):斷言condition為True
- X_FALSE(condition):斷言condition為False
普通比較型斷言
- X_EQ(v1,v2):==
- X_NE(v1,v2):!=
- X_LT(v1,v2):<
- X_LE(v1,v2):<=
- X_GT(v1,v2):>
- X_GE(v1,v2):>=
C字元串比較型斷言
- X_STREQ(s1,s2):s1==s2
- X_STRNE(s1,s2):s1!=s2
- X_STRCASEEQ(s1,s2):忽略大小寫,s1==s2
- X_STRCASENE(s1,s2):忽略大小寫,s1!=s2
注意: - Null指針和空字元””是不相同的
- 假如char *s1 = “abc”,char *s2 = “abc”,那麼X_EQ(s1,s2)不通過,因為s1與s2實際上是地址指針,不相同;X_STREQ(s1,s2)通過,因為字元串相同
浮點數比較型斷言
對於浮點數,斷言只是判斷幾乎相等
- X_FLOAT_EQ(f1,f2):f1和f2兩個float值幾乎相等
- X_DOUBLE_EQ(f1,f2):f1和f2兩個double值幾乎相等
- X_NEAR(v1,v2,abs_error):v1和v2兩個浮點數的值差的絕對值不超過abs_error
明確的成功與失敗
- SUCCEED():生成一個成功,放行,但是並不代表整個測試成功
- FAIL():生成致命錯誤,立即終止當前測試
- ADD_FAILURE():生成非致命錯誤,繼續運行測試
- ADD_FAILURE_AT(“file_path”,line_number):生成非致命錯誤,輸出文件名和行號
- GTEST_SKIP():直接結束當前測試
明確的成功與失敗相較於前面的斷言更適合判斷條件複雜的情況,因為判斷條件複雜不適合寫成一個表達式condition用於判斷。例如if...else if...else if... else...
異常斷言
用於驗證一段程式碼是否拋出給定類型的異常
- X_THROW(statement,exception_type):statement程式碼會拋出exception_type的異常
- X_ANY_THROW(statement):statement程式碼會拋出異常,不限異常類型
- X_NO_THROW(statement):statement程式碼不會拋出任何類型異常
自定義布爾函數斷言(謂詞斷言)
- X_PREDn(fun,v1,v2…):擁有n個參數的函數fun會返回True
例如有一個函數equal(a,b),那麼就是ASSERT_PRED2(equal,a,b)
與ASSERT_EQ、ASSERT_TRUE()這些斷言的區別在於輸出的錯誤資訊不同,同時它的功能更加強大
謂詞格式化程式斷言
普通的斷言輸出資訊的內容是預定好的,如果想要自定義輸出的內容,可以使用謂詞格式化程式斷言
具體介面使用可參考:EXPECT_PRED_FORMAT
為了避免新的斷言宏爆炸式增長,GoogleTest提供了很多謂詞格式函數,它們可以使用謂詞斷言的方式組裝成需要的斷言,例如浮點數的小於等於
using ::testing::FloatLE;
using ::testing::DoubleLE;
...
EXPECT_PRED_FORMAT2(FloatLE, val1, val2);
EXPECT_PRED_FORMAT2(DoubleLE, val1, val2);
匹配器斷言
-
X_THAT(value, matcher):value的值滿足matcher的要求
#include “gmock/gmock.h”
using ::testing::AllOf;
using ::testing::Gt;
using ::testing::Lt;
using ::testing::MatchesRegex;
using ::testing::StartsWith;…
EXPECT_THAT(value1, StartsWith(“Hello”));
EXPECT_THAT(value2, MatchesRegex(“Line \d+”));
ASSERT_THAT(value3, AllOf(Gt(5), Lt(10)));
關於matcher的具體介面文檔,詳見matchers
類型斷言
調用函數::testing::StaticAssertTypeEq<T1,T2>();
用於斷言T1和T2是同一種類型,如果斷言滿足,該函數什麼也不做,如果不同,函數調用會無法編譯並報錯T1 and T2 are not the same type
注意:如果是在類模板或者函數模板中使用時,僅當該函數被實例化(被調用)時才會生效報錯,否則不會報錯
斷言使用的位置
除了在測試程式碼中使用斷言外,在任何C++函數中也都可以使用斷言。但是注意,產生致命錯誤的斷言只能用在返回void的函數(構造與析構函數不是返回void的函數)
測試
簡單測試
-
使用
TEST()
宏定義和命名測試函數,這個函數是不返回值的普通C++函數 -
函數中可以包含任何有效的C++語句以及各種GoogleTest斷言來檢查值
-
測試的結果由斷言決定,如果測試時沒有任何斷言失敗(致命或非致命)或者測試程式崩潰,則測試成功
-
第一個參數是測試套件的名稱,第二個參數是測試套件中的測試名稱,這兩個名稱都必須是有效的C++標識符,並且不能含有任何下劃線。測試的全名由測試套件和測試名稱組成,不同測試套件的測試可以有相同的測試名稱
TEST(TestSuiteName, TestName){
… test body …
}
舉個栗子
函數funA有一個輸入n,返回n^2,兩個測試都屬於FunATests測試套件,名字分別是HandlesZeroInput和HandlesPositiveInput用於測試不同的情況
int funA(int n);
TEST(FunATests, HandlesZeroInput){
EXPECT_EQ(funA(0), 0);
}
TEST(FunATests, HandlesPositiveInput){
EXPECT_EQ(funA(1), 1);
EXPECT_EQ(funA(2), 4);
...
}
測試夾具
如果發現自己編寫了兩個或多個對相似數據進行操作的測試,可以使用測試夾具,它允許我們為多個不同的測試重用相同的對象配置
創建並使用夾具
- 從::tesing::Test派生一個類,它的主體內容設置為protected,因為我們要從子類中訪問夾具成員
- 在類中,聲明計劃使用的所有對象數據
- 如有必要,編寫一個默認構造函數或者SetUp()函數來為每個測試準備對象
- 如有必要,編寫一個析構函數或者TearDown()函數來釋放測試對象數據
- 如有需要,編寫函數供使用該測試夾具的測試內使用
- 注意,GoogleTest不會在多個測試中重用同一個測試夾具對象。對於每個TEST_F,GoogleTest會創建一個新的測試夾具對象並立刻調用SetUp(),運行測試主題結束後調用TearDown(),最後刪除測試夾具對象
- 使用測試夾具的時候,用TEST_F代替TEST,TEST_F的第一個參數不再是測試套件名,而是測試夾具類名,具體見下方樣例
舉個栗子
假設我們有一個類Queue需要進行測試,它長這樣:
template <typename E> // E is the element type.
class Queue {
public:
Queue();
void Enqueue(const E& element);
E* Dequeue(); // Returns NULL if the queue is empty.
size_t size() const;
...
};
定義它的測試夾具類,一般情況下測試夾具類名=類名+Test
class QueueTest : public ::testing::Test {
protected:
void SetUp() override {
q1_.Enqueue(1);
q2_.Enqueue(2);
q2_.Enqueue(3);
}
// void TearDown() override {}
Queue<int> q0_;
Queue<int> q1_;
Queue<int> q2_;
};
在這裡,TearDown()並不需要,因為我們並不需要進行任何清理工作,直接析構就可以了
使用測試夾具進行測試
TEST_F(QueueTest, IsEmptyInitially) {
EXPECT_EQ(q0_.size(), 0);
}
TEST_F(QueueTest, DequeueWorks) {
int* n = q0_.Dequeue();
EXPECT_EQ(n, nullptr);
n = q1_.Dequeue();
ASSERT_NE(n, nullptr);
EXPECT_EQ(*n, 1);
EXPECT_EQ(q1_.size(), 0);
delete n;
n = q2_.Dequeue();
ASSERT_NE(n, nullptr);
EXPECT_EQ(*n, 2);
EXPECT_EQ(q2_.size(), 1);
delete n;
}
在這個栗子里,第一個TEST_F創建一個QueueTest對象t1,t1.SetUp()後進入測試內容進行使用。測試結束後t1.TearDown()然後銷毀。對於第二個TEST_F進行相同的過程
調用測試
TEST()和TEST_F都會自動的隱式註冊到GoogleTest,所以並不需要為了測試再重新列舉所有定義的測試
在定義測試之後,可以直接使用RUN_ALL_TESTS()來運行所有測試,如果所有測試都通過了,它會返回0。注意,RUNN_ALL_TESTS()會運行所有測試,哪怕這些測試來源於不同的測試套件、不同的源文件。
運行測試的過程
- 保存所有googletest標誌的狀態
- 為第一個測試創建測試夾具對象,通過SetUp()初始化
- 使用測試夾具對象運行測試
- 測試結束,調用TearDown()清理夾具然後銷毀夾具對象
- 恢復所有googletest標誌的狀態
- 對下一個測試重複以上步驟,直到所有測試都運行結束
注意:不能忽略RUN_ALL_TESTS()的返回值,否則會產生編譯器錯誤。自動化測試服務根據退出程式碼來判斷測試是否通過,而不是通過stdout/sederr來判斷,所以main()函數必須返回RUN_ALL_TESTS();
main()的編寫
大部分情況下,我們並不需要自己編寫main方法,而是直接鏈接gtest_main(注意不是gtest),這個鏈接庫定義了合適的接入點會幫我們進行測試
如果想自行書寫main方法,它需要返回RUN_ALL_TESTS()的返回值
int main(int argc, char **argv) {
::testing::InitGoogleTest(&argc, argv);
return RUN_ALL_TESTS();
}
在這段程式碼里,InitGoogleTest()的作用是解析命令行里GoogleTest的指令參數,這允許用戶控制測試程式的行為。它必須在RUN_ALL_TESTS之前調用,否則命令行參數不會生效
在舊版本里,使用的是ParseGUnitFlags(),但是目前它已經被棄用,需要使用InitGoogleTest()
後續可填坑
gMock