GoogleTest 之路2-Googletest 入門(Primer)


Why googletest?

為啥要用GoogleTest呢?

googletest 是由測試技術Team 開發的帶有google 特殊的需求和限制的測試框架。

不管你在什么平台上寫C++代碼,googleTest 可以幫助你實現任何類型的測試,不僅僅是單元測試

那么是什么成就了一個好的測試,googletest為什么能適用? we believe:

1.  測試必須是獨立的,並且是可以重復的。debug 非常痛苦因為發現 一個測試成功失敗依賴於其他測試。

googleTest 通過在不同的object上運行test,起到了隔離測試的效果。當一個測試失敗時,googletest allow you 運行在一個隔離的環境里做快速debug。

2. Tests 應當合理的組織起來,以反映 測試代碼的 結構。googletest 把相關的的測試組織起來,並且可以共享 數據和子程序。

這種共同模式很容易識別並且很容易維護測試case。一致性在人們切換Projects 並且在新的code base 上工作時會更有效

3. 測試必須是便攜式的(portable)和 可重用的。Google 有很多平台中立的(platform-neutral)代碼,那么其測試代碼也應該是平台中立的。GoogleTest在不同的平台上工作。不同的編譯器(gcc, icc, and MSVC), with or without exceptions, so googletest tests can easily work with a variety of configurations.

4.當測試失敗時,應該提供關於問題更多詳細的信息。googletest在第一個test失敗時並不停止工作。它只是停止當前測試,並繼續下一個測試。You can also set up tests that report non-fatal failures after which the current test continues. Thus, you can detect and fix multiple bugs in a single run-edit-compile cycle.

5. testing framework 把測試 作者從 雜事中解放出來,讓他們專注於測試的內容。googletest 自動跟蹤所有定義的case,不需要 user枚舉並運行他們

6. 測試需要快,googletest可以幫助你在不同的測試中重用共享的資源。and pay for the set-up/tear-down only once, without making tests depend on each other.

因為googleTest 是基於現在最流行的 xUnit 架構的,you'll feel right at home( 很地道的用法)如果你使用過Junit 或者PyUnit.如果沒使用過花個10分鍾學一下基礎,就可以開始了。

Beware of the nomenclature

Note: 對於 Test,Test Case and Test Suite 這幾個術語有幾個不同的定義,你可能會產生混淆。

歷史上,googletest 使用 Test Case 來表示 一組測試的集合,然而  目前公開的包含 ISTQB 和 不同的軟件質量的專業書籍用 Test Suite 來定義此。

googleTest 里面使用的術語 Test 在 ISTQB里面稱為Test Case.

Test 術語是很廣的定義,包含了ISTQB里的 Test Case的定義,所以這不是一個很大的問題。 但是 GoogleTest 里面使用的Test Case就是一個有矛盾的術語,因而會讓人產生困惑。

GoogleTest 最近開始用 Test Suite來替換 Test Case。推薦使用 TestSuite* 的API.老的 TestCase* API 漸漸的被棄用並且被重構。

Meaning googletest Term ISTQB Term
Exercise a particular program path with specific input values and verify the results TEST() Test Case

 

 

Basic Concepts(基礎概念)

當使用googletest,從寫assertions開始,assertion 用來check 什么 condition是 true。 An assertion 的結果可以是 success, nonfatal failure 或者 fatal failure. 如果 fatal failure 發生,當前function 就終止;否則 程序繼續正常運行

Tests使用 assertions 來驗證 被測代碼的行為。如果一個測試crash或者有一個 failed 的 assertion,那么 it fails;否則succeds.

一個 Test case 包含一個或多個 tests。你應當 組合你的 tests into test cases(這邊應該叫 test suite 吧?)來反映 tested code 的結構。當多個test在一個 test case 想要share 子程序的時候,你可以把它們放到 test fixture 類里面。

一個 Test program 可以包含多個 test cases。

我們開始解釋如何寫一個 test program,從每個單獨的 assertion 到構建test 和 test cases。

 

Assertions

googletest assertions 類似於函數調用的宏。你可以使用 assertions 對要測試的類和函數進行斷言。當一個 assertion 失敗。googletest 打印了 assertion的源文件和行號的位置,並且還有失敗信息。你也可以提供定制的失敗信息。這條信息會被加到googletest 的信息里面。

assertion成對出現,對於同一個 function有不同的影響。 ASSERT_* 版本失敗時產生fatal failure,並且終止當前的函數。EXPECT_* 版本產生 nonfatal failures,不終止當前的函數。 通常 EXPECT_* 更推薦使用。因為你可以在一個test里面產生多個 failure。然而,如果繼續當前測試沒有意思時,你就得用 ASSERT_*

因為一個 failed ASSERT_* 從當前函數中立馬返回,也許會提過 clean-up code,可能會引起內存泄漏。取決於泄漏的性質,這個也許值得修復,也許不值得修復。如果是 assertion 導致的內存泄漏就不值得修。

要提供定制的failure 信息,simply 使用 << 操作符,例子:

ASSERT_EQ(x.size(), y.size()) << "Vectors x and y are of unequal length";

for (int i = 0; i < x.size(); ++i) {
  EXPECT_EQ(x[i], y[i]) << "Vectors x and y differ at index " << i;
}

任何可以通過 ostream 輸出的都可以 streamed to 一個 assertion 宏-- 特別是 C 字符串 和 string 對象。如果一個寬的字符(windowsUNICODE模式下的wchar_t*,TCHAR*或者 std::wstring) 被 streamed 到一個斷言上,打印時會被轉換成 UTF-8。

 

Basic Assertions(基礎斷言)

下面的斷言為一個基礎的 true/false 情況的測試

Fatal assertion Nonfatal assertion Verifies
ASSERT_TRUE(condition); EXPECT_TRUE(condition); condition is true
ASSERT_FALSE(condition); EXPECT_FALSE(condition); condition is false

Availability: Linux, Windows, Mac.

 

Binary Comparison

下面的斷言描述了比較兩個值的斷言

Fatal assertion Nonfatal assertion Verifies
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

如果前后兩個值是不可以比較的,就會得到一個編譯錯誤。

過去我們要求 參數要支持<< 符號,但是現在不需要了。如果<< 被支持,當斷言失敗時,會被調用去打印參數。

否則googletest 會嘗試用最好的方法去打印它們。更多詳情請參考 recipe

這些斷言可以和user-defined類型一起工作,但是只在你定義的相對應的 比較運算符(e.g. ==,<,etc).因為這不被Google C++ style 所鼓勵。你也許可以使用 ASSERT_TRUE() 或者 EXPECT_TRUE()來斷言比較用戶自定義類型的值。

然而,當有可能時,ASSERT_EQ(actual,expected) 更好於 ASSERT_TRUE(actual == expected), 因為 前者會在失敗時告訴你 actual 和 expected 的值。

參數經常僅僅被評估一次。因此,參數有些副作用也是可以的。然而,和任何普通的C/C++ 函數相比較,參數的評估順序是未定義的(i.e.編譯器自自由選擇順序 )

所以你的code應該不依賴於任何特定的參數評價順序。

ASSERT_EQ()使用的是指針比較。如果使用兩個Cstring比較,它測試的是是否這兩個string使用的是同一地址,而不是是否兩值相等。因此如果你想比較兩個字符串的值,請使用ASSERT_STREQ(),稍后討論該內容。尤其是使用C string 和 NULL進行比較ASSERT_STREQ(c_string, NULL)。

當比較兩個指針的時候,記得用 *_EQ(ptr, nullptr) 和 *_NE(ptr, nullptr) 而不用 *_EQ(ptr, NULL) 和 *_NE(ptr, NULL)。 因為 nullptr 是類型符號而NULL 不是,See FAQ for more details.

如果你使用浮點數比較  See Advanced googletest Topics for details.

Historical note: Before February 2016 *_EQ had a convention of calling it as ASSERT_EQ(expected, actual), so lots of existing code uses this order. Now *_EQ treats both parameters in the same way.

String Comparison

這組斷言比較兩個 C string。 如果你想比較兩個string object,使用 EXPECT_EQ 和 EXPECT_NE等等

Fatal assertion Nonfatal assertion Verifies
ASSERT_STREQ(str1, str2); EXPECT_STREQ(str1, str2); the two C strings have the same content
ASSERT_STRNE(str1, str2); EXPECT_STRNE(str1, str2); the two C strings have different contents
ASSERT_STRCASEEQ(str1, str2); EXPECT_STRCASEEQ(str1, str2); the two C strings have the same content, ignoring case
ASSERT_STRCASENE(str1, str2); EXPECT_STRCASENE(str1, str2); the two C strings have different contents, ignoring case

Note that  "CASE"在一個 assertion 名字里面意味着 case被忽略了。 一個 NULL 指針和一個空字符串 被認為是不一樣的

*STREQ* 和 *STRNE* 同樣也接受 C字符串(wchar_t *). 如果 一個比較 兩個 寬字符的斷言失敗了,它們的值被打印成UTF-8 的 短字符串

See also: For more string comparison tricks (substring, prefix, suffix, and regular expression matching, for example), seethis in the Advanced googletest Guide.

簡單的測試

Simple Tests

1. 使用 TEST()宏 來定義和命名測試 function,這些是普通的C++函數並且不返回值。

2. 在這個函數里,和其他任何你想要包含的C++ 表達式,使用各種各樣的googletest 斷言去check values

3. 測試的結果由斷言所決定;如果測試里面的任何斷言失敗了(要么fatally 要么 non-fatally),或者如果測試crashes,整個測試失敗。否則,成功

TEST(TestSuiteName, TestName) {
  ... test body ...
}

TEST()  參數從 general 到 specific. 第一個參數是test case 的名字,第二個參數是test case里面的 test的名字。 兩者的名字都要是有效的C++ identifiers,並且不能有underscore(_)。一個測試的全名包含它 test case和其 individual name。Tests from different test cases can have the same individual name.

例如有一個簡單的 integers function:

int Factorial(int n);  // Returns the factorial of n

對於它的測試case 可能像這個樣子:

// Tests factorial of 0.
TEST(FactorialTest, HandlesZeroInput) {
  EXPECT_EQ(Factorial(0), 1);
}

// Tests factorial of positive numbers.
TEST(FactorialTest, HandlesPositiveInput) {
  EXPECT_EQ(Factorial(1), 1);
  EXPECT_EQ(Factorial(2), 2);
  EXPECT_EQ(Factorial(3), 6);
  EXPECT_EQ(Factorial(8), 40320);
}

googletest 通過 test cases來組合這些 test,所以說 邏輯上相關的tests應該在同一個test case里面;換句話說, TEST()第一個參數應該是相同的,上面的例子里面,我們有兩個tests,HandlesZeroInput 和 HandlesPositiveInput, 它們同屬於同一個 test case FactorialTest。

命名 testcases 和 tests,你應該遵循一定的規則 naming functions and classes

Test Fixtures: Using the Same Data Configuration for Multiple Tests

如果你發現你寫兩個或者多個 tests 用到了相同的數據,你可以使用 test fixture。它可以讓多個tests使用相同的配置。

To create a fixture:

1. 集成 ::testing:Test. 使用 protected 字段:我們可以訪問子類里的 fixture 成員。

2. 在類里面,申明你想要使用的任何對象。

3. 必要的時候,寫一個默認構造函數或者 SetUp() 函數 來准備每個 test的對象。記住拼寫 SetUp() 不是Setup()

Use override in C++11 to make sure you spelled it correctly

4.必要的時候,寫一個析構函數或者TearDown()釋放你在SetUp() 里面分配的資源,想知道什么時候使用 constructor/destructor 什么時候使用etUp()/TearDown(), read this FAQ entry

5. 有必要定義你tests里面的子函數供share。

定義了 fixture,想要在test里面訪問 fixture的對象和子函數,就得使用TEST_F() 而不是TEST()

TEST_F(TestSuiteName, TestName) {
  ... test body ...
}

和 TEST()一樣,第一個參數是 test case 的名字,但是對於TEST_F()來說第一個參數必須為 test fixture class. 你也許已經猜到了:_F 就是 fixture 的意思。

Unfortunately, C++ macro 系統不允許我們創建一個單獨的宏處理不同的tests. 使用錯誤的宏會導致編譯錯誤。

此外,你必須在使用TEST_F() 之前先定義 一個 test fixture, 否則 你將會收到 compiler 錯誤 “virtual outside class declaration"

每一個 用TEST_F() 定義的 test,googletest 會在運行時創建一個新的 test fixture,立刻通過 SetUp() 初始化,運行測試,通過調用 TearDown()來清理測試,最后刪除test fixture。注意 在 test case 里面的不同tests擁有的時不同的 test fixture 對象。 googletest 經常在 創建下一個 test fixture 前先刪除一個。 googletest 對於多個 tests不重復使用同一個 test fixture。 任何對於其中一個 test 的改變不影響其他的 tests。

舉一個例子, 我們寫了一個 命名為 Queue 的FIFO queue的 tests,接口如下:

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;
  ...
};

首先,定義一個 fixture 類。根據命名的傳統,FooTest實質上被測的類就是Foo。我們定義QueueTest.

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 結束后都清理,我們已經通過析構函數實現了這一功能。

現在我們就可以在TEST_F() 里面使用 這個 fixture

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;
}

上面的測試case 既用到了 ASSERT_* 也用到了 EXPECT_* 斷言。 使用EXPECT_*在斷言失敗之后可以 查看更多錯誤的信息。 如果斷言之后的信息沒有意義則使用ASSERT_*。

例如, 在Dequeue 的第二個斷言中 =ASSERT_NE(nullptr,n)=, as we need to dereference the pointer n later, which would lead to a segfault when n is NULL.

當這些 tests 運行的時候,如下過程執行了:

1. googletest 構造了 QueueTest 對象(let‘s call it t1).

2. t1.SetUp() 初始化 t1

3. t1 的 第一個 test(IsEmptyInitially) 運行

4. t1.TearDown() 在 test 結束時進行 清理.

5. t1 被析構

6.以上步驟在另一個 QueueTest 對象會被重復,這次運行的時 DequeueWorks test。

 

Invoking the Tests

調用測試

TEST() 和 TEST_F() 隱式向googletest 注冊了。所以說 和其他的C++ 測試框架不一樣的是,你不必要在運行時重新把test 重新按順序列出來。

當被調用時, RUN_ALL_TESTS() 宏:

1. 保存所有 googletest flag 的狀態

        為第一條test 創建 test fixture 對象

        通過 SetUp() 初始化

        基於 fixture 運行 tests

        通過TearDown() 清理 fixture

        刪除 fixture

        重新存儲  所有 googletest flags 的狀態

        為下一條測試執行上面同樣的步驟,直到所有tests 執行完。

如果 fatal failure 發生了,隨后的步驟會被跳過。

IMPORTANT: 你不能忽略 RUN_ALL_TESTS() 的返回值,否則你會達到編譯錯誤。這樣設計的合理之處在於 自動化測試的服務通過 exit code 來判斷其執行是否成功,而不是

通過 其 stdout/stderr 輸出。因此你的main 函數必須有RUN_ALL_TESTS() 的返回值

並且,你應當只調用 RUN_ALL_TESTS()一次。多次調用可能會和某些高級功能( thread-safe death tests)相沖突,並且這些在這時不支持的

 

Writing the main() Function

寫main 函數

寫你自己的 main 函數,應當有 RUN_ALL_TESTS() 的返回值。

#include "this/package/foo.h"
#include "gtest/gtest.h"

namespace {

// The fixture for testing class Foo.
class FooTest : public ::testing::Test {
 protected:
  // You can remove any or all of the following functions if its body
  // is empty.

  FooTest() {
     // You can do set-up work for each test here.
  }

  ~FooTest() override {
     // You can do clean-up work that doesn't throw exceptions here.
  }

  // If the constructor and destructor are not enough for setting up
  // and cleaning up each test, you can define the following methods:

  void SetUp() override {
     // Code here will be called immediately after the constructor (right
     // before each test).
  }

  void TearDown() override {
     // Code here will be called immediately after each test (right
     // before the destructor).
  }

  // Objects declared here can be used by all tests in the test case for Foo.
};

// Tests that the Foo::Bar() method does Abc.
TEST_F(FooTest, MethodBarDoesAbc) {
  const std::string input_filepath = "this/package/testdata/myinputfile.dat";
  const std::string output_filepath = "this/package/testdata/myoutputfile.dat";
  Foo f;
  EXPECT_EQ(f.Bar(input_filepath, output_filepath), 0);
}

// Tests that Foo does Xyz.
TEST_F(FooTest, DoesXyz) {
  // Exercises the Xyz feature of Foo.
}

}  // namespace

int main(int argc, char **argv) {
  ::testing::InitGoogleTest(&argc, argv);
  return RUN_ALL_TESTS();
}

::testing:InitGoogleTest() 函數 從命令行解析 googletest flags, 移除 所有已經識別的 flags。這允許用戶通過不同的flags 來控制 測試程序的行為,我們將在 AdvancedGuide里介紹。你必須在 調用 RUN_ALL_TESTS() 之前調用這個函數。否則flags 不會被合理的初始化。

在 Windows上,InitGoogleTest() 也可以在 wide string 模式下運行,所以這也會在 UNICODE模式下編譯。

但是也許你想寫這么多在main 函數里面太費事了?我們也完全同意你的想法,那就是為什么GoogleTest提供了main函數的記本實施。如果這滿足你的需要,把你的測試和gtest_main庫建立鏈接,你就可以開始啦!

 

已知的限制

Known Limitations

GoogleTest  按照線程安全設計的。有 pthreads library 的情況下,系統上的執行是線程安全的。 目前在你的系統上從多個線程使用Google Test 斷言是不安全的。在大多數的tests里面這不是一個問題,因為 assertion 都是在主線程里面的。If you want to help, you can volunteer to implement the necessary synchronization primitives in gtest-port.h for your platform.


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM