# Googletest Primer 繁中隨意翻
###### tags: `軟體工程`
[TOC]
:::success
:mega: 資料來源(All credits to):[Googletest Github](https://github.com/google/googletest/blob/master/googletest/docs/primer.md)
:mega: 整篇都是口語隨便翻譯的,有看到錯得太誇張的地方麻煩留言告知或來信 cykuotw45@gmail.com。
:::
## 為什麼要用googletest
**googletest**可以幫你撰寫良好的C++測試。
googletest是一個Google發展的測試框架,不論你用的是Linux、Windows還是Mac,只要你寫C++就能用googletest幫助你做任何測試,不只有單元測試。
良好的測試必須達到以下條件:
1. 測試必須**獨立**且**可重複**,當一個測試必須仰賴另一個測試結果,除錯過程會很麻煩。googletest以不同的物件隔離每項測試,當某個測試失敗時,googltest能夠讓你獨立執行此測試,並加快除厝流程。
2. 測試必須**有組織性**且反映出程式碼的的架構。googletest使用共同資料及子流程將所有測試單元關聯起來,這個模式會比較好維護,當工程師切換專案時也比較好上手。
3. 測試必須為**可攜帶**且**可重複使用**,不管程式碼是否與平台相關,都可以使用googletest
4. 當測試失敗時,必須盡可能提供多一點資訊,googletest不會在第一個測項失敗就直接停止,他只會停止當前測項並開始下一個測項,所以使用者可以同時偵測及修復多個bug
5. 測試框架必須讓測試人員專心於撰寫測試流程,免於做一堆雜事,googletest可以自行追蹤所有測試項目,測試人員不需要一一列舉才能執行。
6. 測試必須快速,使用者可以利用共有資源,而且不用讓測項必須依賴其他測項。
googletest是基於常用的xUnit架構,假如之前用過JUnit或PyUnit的話就能馬上上手,不然也能在參照此教學,在十分鐘左右了解一些基礎。
## 基礎概念
使用googletest時,可以從**Assertion**下手,檢查何種狀況時該敘述為真。
Assertion的結果可以為:**Success**、**Nonfatal Failure**、**Fatal Failure**。當fatal failure發生時:當前的fuction會中止,其他情況就繼續正常運行。
**Tests**把Assertion拿來確認被測試程式碼的行為。如果測試當掉或是不符合assertion,則此測項為fail,反之則為success。
**Test Suite**包含了一個或多個Test。使用者應該要把test以test suite整合起來,以便反映程式碼的結構。若多個test有共同的物件或子流程,可以用**Test Fixture**包起來。
**Test Program**包含了多個test suite。
接下來會開始解釋如何撰寫測試程式,我們先從assertion開始,然後依序建立test以及test suite。
## Assertion
googletest的assertion就是用*巨集*代替函式呼叫,使用者以assertion來測試class或fuction的行為。
當assertion fail時,googletest會列出原始碼檔名、行數以及失敗訊息。使用者也能提供自訂的失敗訊息,會被附在gtest訊息後面。
Assertion可以分成兩種:`ASSERT_*`以及`EXPECT_*`。
`ASSERT_*`失敗時會丟出fatal failure並且停止執行當前函式,`EXPECT_*`失敗時會丟出nonfatal failure。
通常會使用`EXPECT_*`,因為這個可以容忍測試報告出現多個failure,不過`ASSERT_*`可以用在當錯誤產生時,繼續執行下去已經沒意義的時候。
由於`ASSERT_*`錯誤會造成function return,可能跳過清理回收程序並導致記憶體流失。當heap檢查錯誤跟著此asserttion錯誤時,必須留心這個特性。
自訂錯誤訊息可以使用`<<`運算子輸出,例如:
```cpp=
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]) << "Vector and y differ at index" << i;
}
```
所有能透過`ostream`輸出的都能夠以assertion輸出,特別是C string和`string`物件。wide string(`wchar_t*`, Windows中Unicode模式的`TCHAR*`, `std::wstring`)會轉成UTF-8之後再透過assertion輸出。
## 基本 Assertion
| Fatal assertion | Nonfatal assertion | Verifies |
| ------------------------ | ------------------------ | ------------------
| `ASSERT_TRUE(condition)` | `EXPECT_TRUE(condition)` | `condition` = true |
`ASSERT_FALSE(condition)` | `EXPECT_FALSE(condition)` | `condition` = false |
## 基本比對
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`
如果要使用自訂的比較運算子(operator overloading),需要使用`ASSERT_TRUE()`或`EXPECT_TRUE()`來比較兩個自訂的物件型態。
不過相較於`ASSERT_TRUE(actual == expected)`,還是比較推薦使用`ASSERT_EQ(actual, expected)`,至少可以清楚看到`actual`, `expected`的值是多少。
從引數(Arguement)傳入的值只會使用一次,所以argument有些副作用是能接受的。不過要注意引數的執行順序(未定義行為,編譯器會自己選他想要的順序),最好不要依賴特定引數執行順序。
`ASSERT_EQ()`支援指標比較,比較兩個C string時,此函式只有比對記憶體位置,但是不會比較裡面的值。
比較C string(`const char*`)的值,請使用`ASSERT_STREQ`。
指定C string為`NULL`時,請使用`ASSERT_STREQ(c_string, NULL)`。
支援C++11,請使用`ASSERT_STREQ(c_string, nullptr)`。
單純比對兩個`string`物件,請使用`ASSERT_EQ。`
若要做指標比較,請使用`*_EQ(ptr, nullptr)`和`*_NE(ptr, nullptr)`,避免使用`_EQ(ptr, NULL)`和`*_NE(ptr, NULL)`,使用NULL可能會碰到型態問題。詳細解釋請看[FAQ](https://github.com/google/googletest/blob/master/googletest/docs/faq.md#why-does-googletest-support-expect_eqnull-ptr-and-assert_eqnull-ptr-but-not-expect_nenull-ptr-and-assert_nenull-ptr)
假如要比較浮點數,要選用適合浮點數的函式,請參照[Advanced googletest Topics](https://github.com/google/googletest/blob/master/googletest/docs/advanced.md#floating-point-comparison)
此處的巨集適用一般字串與寬字串(`string`和`wstring`)
適用平台:Linux、Windows、Mac
## 字串比對
此處適用**C string**,`string`請使用基本比對即可
| 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 |
命名中的CASE代表比對時不管大小寫。
`NULL`指標與空的string不相同
`*STREQ*`與`*STRNE*`可適用於寬字串,當兩寬字串比對失敗後,會以UTF-8一般字串輸出。
其他的進階字串比對技巧,請參照[Advanced googletest Topics](https://github.com/google/googletest/blob/master/googletest/docs/advanced.md)
適用平台:Linux、Windows、Mac
## 簡單測試
建立測試必須:
1. 使用`TEST()`巨集定義測試函式的名字,這些是一般沒有回傳值的C++函式
2. 在此函式中(包含任何有效的C++statement)使用googletest的assertion來比對數值。
3. 測試結果由assertion決定,如果測試失敗或崩潰,則整個測試視為失敗。反之則視為成功。
```cpp=
TEST(TestSuiteName, TestName){
// test body
}
```
`TEST()`的引數由廣義到狹義,第一個引數是test suite的名字,第二個引數是該test suite中的test名字。兩者必須為有效的C++識別字,並且不能含有任何底線(`_`)。一個測試的全名包含了test suite與個別測試的名字,不同test suite的測試可以有相同的名字。
舉個例子,我們有一個簡單的整數函數:
```cpp
int Factorial(int n); // return the factorial of n
```
這個函數的test suite會長這樣:
```cpp
// 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 suite分開來,邏輯上來說相關的測試必須屬於相同的test suite,換句話說,就是TEST()的第一個引數必須相同。
上面的例子可以看到:兩個測試`HandlesZeroInput` 和 `HandlesPositiveInput` 屬於同一個叫做 `FactorialTest` 的test suite。
命名慣例可以參考[這邊](https://google.github.io/styleguide/cppguide.html#Function_Names)
適用平台:Linux、Windows、Mac
## Test Fixtures
:::success
使用相同的資料組成來跑多個測試項
:::
如果你發現有好幾個測試都用差不多的資料,你可以考慮使用test fixure,這個可以多個不同的測試項目中重複使用相同的物件。
建立test fixture必須要:
1. 從`::testing::Test`繼承一個class,起手式把程式主體設定為`protected`,因為我們想要從sub-class使用fixture member。
2. 把你想要用的物件宣告在class裡面。
3. 有需要的話,加入建構子或`SetUp()`函式來初始化這些物件。常見的錯誤是把`SetUp()`拼成`Setup()`,在C++11中可以使用`override`確保沒有拼錯字。
4. 有需要的話,加入解構子或`TearDown()`函式來釋放資源。解構子/建構子 以及 `SetUp()` `TearDown()`的使用時機請參考[FAQ的這邊](https://github.com/google/googletest/blob/master/googletest/docs/faq.md#should-i-use-the-constructordestructor-of-the-test-fixture-or-setupteardown-ctorvssetup)
5. 有需要的話,定義子流程分享給所有測項
當你使用fixture時,請改用`TEST_F()`,這樣才能訪問test fixture中的物件及子流程
```cpp
TEST_F(TestFixtureName, TestName) {
// test body
}
```
引數規則跟`TEST()`類似,第一個必須為test fixture的名字。C++的巨集系統並不允許我們以一個巨集來處理兩個不同的測試,巨集用錯會導致編譯器錯誤。
使用者必須在使用一個test fixture class前,先在`TEST_F()`定義好,不然會導致`virtual outside class declaration` 的編譯器錯誤。
執行`TEST_F()`時,googletest會在runtime中建立一個全新的test fixture,並且透過`SetUp()`初始化,測試執行結束後,會透過`TearDown()`釋放記憶體,最後清掉test fixture。
在同一個test suite的不同測項不會使用同一個test fixture物件,googletest在建立新test fixture物件之前一定會清掉舊的,googletest不會重複使用一個test fixture物件,所以各個測項對fixture的修改都不會影響到其他測項。
舉個例子,一個FIFO queue class叫做`Queue`
```cpp=
template <typename E>
class Queue{
public:
Queue();
void Enqueue(const E& element);
E* Dequeue();
size_t size() const;
// ...
}
```
首先我們先定義fixture class,按照慣例,我們把它命名為`FooTest`,`Foo`就是被測試的class名字。
```cpp=
class QueueTest : public ::testing::Test{
protected:
void SetUp() override{
q1_.Enqueue(1);
q2_.Enqueue(2);
q3_.Enqueue(2);
}
// void TearDown() override{}
Queue<int> q0_;
Queue<int> q1_;
Queue<int> q2_;
}
```
在這個情況時,因為每個測試之後不用解構子之外的清理資源函式,所以不用`TearDown()`。
然後我們利用`TEST_F()`跟上面的fixture寫一個測試
```cpp=
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 m;
}
```
上面的測試我們用到`ASSERT_*`和`EXPECT_*`,經驗上來說,如果我們要在assertion failure之前得到更多error,通常使用`EXPECT_*`;如果該測試之後的其他測試都不合理的話,通常使用`ASSERT_*`。
舉例來說,上面`Dequeue`的第二個Assertion`ASSERT_NE(n, nullptr)`,接下來我們要dereference指標n,假如n是`NULL`的話,接下來就會導致segmentation fault。
當測試開始時,會出現下列步驟:
1. googletest建構`QueueTest`物件(我們先叫他`t1`)
2. `t1.SetUp()`初始化`t1`
3. 第一個測試(`IsEmptyInitially`)在`t1`上面跑
4. 測試結束後,`t1.TearDown()`清理資料
5. `t1`解構
6. 上面的步驟一直重複,下一個`QueueTest`物件建立之後,但是這次跑`DequeueWorks`測試
適用平台:Linux、Windows、Mac
## 調用測試
`TEST()`和`TEST_F()`是隱式的註冊在googletest中,所以我們不用跟其他C++測試框架一樣,不用為了要跑測試就把所有測項列出來。
定義好測項之後,你可以用`RUN_ALL_TESTS`跑全部的測項,全部測項都成功回傳`0`,反之回傳`1`。值得一提的是:`RUN_ALL_TESTS()`就是跑**全部**的測試,可能在不同的test suite或是不同的原始檔。
當`RUN_ALL_TESTS()`巨集被調用時
1. 儲存所有googletest的flag
2. 創建test fixture物件給第一個測項
3. 透過`SetUp()`初始化fixture
4. 跑fixture物件上的測試
5. 透過`TearDown()`清理fixture
6. 刪除fixture
7. 回復googletest flag
8. 重複上面所有步驟,直到全部的測項都跑完
如果出現重大錯誤時,後續的測試都會跳過。
:::danger
請**不要**忽略`RUN_ALL_TESTS()`的回傳值,不然可能導致編譯錯誤。這個測試的基本原理是:自動測試服務必須以回傳值來決定該測項是否通過,而不是透過stdout或stderr印出來,所以`main()`必須回傳`RUN_ALL_TESTS()`的回傳值。
你必須只呼叫`RUN_ALL_TESTS()`一次,不支援呼叫超過一次,因為會跟一些進階googletest特性衝突(如:執行續安全的[death test](https://github.com/google/googletest/blob/master/googletest/docs/advanced.md#death-tests))
:::
## 撰寫main()函式
大多數人不需要寫自己的`main`,而是使用`gtest_main`(相對於`gtest`),此函式定義了合適的進入點,細節可以參考這個章節的最後面。
必須提醒一下此節提到的技巧適用在:使用者想要在跑測試程序前做一些自定義的事情,而且沒辦法使用框架中的fixture與test suite表示出來。
樣板程式如下:
```cpp=
#include "this/package/foo.h"
#include "gtest/gtest.h"
namespace my{
namespace project{
namespace {
// The fixture for testing class Foo.
class FooTest : public ::testing::Test{
protected:
// You can remove any or all of the following functions
// if their bodies would be 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 each destructor).
}
// Class members declared here can be used by all tests
// in the test suite 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);
}
// Test that Foo does Xyz
TEST_F(FooTest, DoesXyz) {
// Exercises the Xyz feature of Foo.
}
} // namespace
} // namespace project
} // namespace my
int main(int argc, char **argv) {
::testing::InitGoogleTest(&argc, argv);
return RUN_ALL_TESTS();
}
```
函式`::testing::InitGoogleTest()` 用來把命令列指令解析成googletest flags,同時移除其他的flag。使用者可以透過不同的flags來控制測試程式的行為,詳細定義可以參考[ AdvancedGuide](https://github.com/google/googletest/blob/master/googletest/docs/advanced.md)。使用者在呼叫`RUN_ALL_TESTS()`前**一定要**呼叫此函式,不然flags不會被正常初始化。
在Windows系統中,`InitGoogleTest()`適用於寬字串,所以能用在以`UNICODE`模式編譯的程式。
你可能會覺得寫`main()`函式有點麻煩,所以Google Test也提供main()的基礎實作,如果這也是你需要的,你可以將你的測試程式連接到`gtest_main`函式庫就好了。
註:`ParseGUnitFlags()`已棄用,請使用`InitGoogleTest()`
## 已知限制
Google Test被設計成執行續安全的,此實作在可以使用`pthreads`函式庫的系統中都是執行續安全的,不過在其他系統(如Windows)測試兩個並行的執行續無法達到線程安全。不過大多數的測試都是在主執行續中跑所以沒差。
如果你想要幫點忙,可以在你的平台上在`gtest-port.h`中實作必須的同步化原始物件。