【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