Google C++單元測試框架(Gtest)系列教程之四——引數化

weixin_33941350發表於2011-10-08

引言

在上一篇文章中,我們學習瞭如何使用Gtest的測試韌體(Test fixture)完成測試程式碼和測試資料的複用,這一節我們來學習如何使用Gtest值引數化的方法,簡化函式測試;使用型別引數化的方法,簡化對模板類的測試。

值引數化

假設我們要對以下函式進行測試:

// 判斷n是否為質數
bool IsPrime(int n)

假設我們要編寫判定結果為false的測試案例,根據之前學習的斷言和TEST()的使用方法,我們編寫測試程式碼如下:

// Tests negative input.
TEST(IsPrimeTest, Negative) {
EXPECT_FALSE(IsPrime(-1));
EXPECT_FALSE(IsPrime(-2));
EXPECT_FALSE(IsPrime(-5));
EXPECT_FALSE(IsPrime(-100));
EXPECT_FALSE(IsPrime(INT_MIN));
}

顯然我們對“EXPECT_FALSE(IsPrime(X))”這樣的語句複製貼上了5次,但當被測資料有幾十個上百個的時候,再使用複製粘帖的方式就弱爆了。下面我們來看Gtest中為解決這個問題,給我們提供的方法。
首先,我們新增一個繼承自::testing::TestWithParam<T>的類,其中T就是我們被測資料的型別,針對以上函式IsPrimeTest,新增以下類:

class IsPrimeParamTest : public::testing::TestWithParam<int>
{
};

在該類中,我們可以編寫SetUp()和TearDown()函式,分別完成資料初始化和資料清理,還可以新增類成員、其他類成員函式,相關的用法,可以參看Gtest Project的這個例子,這裡我們僅對函式作測試,SetUp()等方法都不需要用到,IsPrimeParamTest為一個空的類。

接著我們需要使用巨集TEST_P來編寫相應的測試程式碼:

TEST_P(IsPrimeParamTest, Negative)
{
int n = GetParam();
EXPECT_FALSE(IsPrime(n));
}

GetParam()方法用於獲取當前引數的具體值,這段測試程式碼相比上面的是不是精簡多了?!

最後,我們使用INSTANTIATE_TEST_CASE_P()告知Gtest我們的被測引數都有哪些:

INSTANTIATE_TEST_CASE_P(NegativeTest, IsPrimeParamTest, testing::Values(-1,-2,-5,-100,INT_MIN));

以上第一個引數為測試例項的字首,可以隨意取;第二個引數為測試類的名稱;第三個引數指示被測引數,test::Values表示使用括號內的引數。執行該測試用例,得到結果如下:

Running main() from gtest_main.cc
[==========] Running 5 tests from 1 test case.
[----------] Global test environment set-up.
[----------] 5 tests from NegativeTest/IsPrimeParamTest
[ RUN ] NegativeTest/IsPrimeParamTest.Negative/0
[ OK ] NegativeTest/IsPrimeParamTest.Negative/0 (0 ms)
[ RUN ] NegativeTest/IsPrimeParamTest.Negative/1
[ OK ] NegativeTest/IsPrimeParamTest.Negative/1 (0 ms)
[ RUN ] NegativeTest/IsPrimeParamTest.Negative/2
[ OK ] NegativeTest/IsPrimeParamTest.Negative/2 (0 ms)
[ RUN ] NegativeTest/IsPrimeParamTest.Negative/3
[ OK ] NegativeTest/IsPrimeParamTest.Negative/3 (0 ms)
[ RUN ] NegativeTest/IsPrimeParamTest.Negative/4
[ OK ] NegativeTest/IsPrimeParamTest.Negative/4 (0 ms)
[----------] 5 tests from NegativeTest/IsPrimeParamTest (1 ms total)

[----------] Global test environment tear-down
[==========] 5 tests from 1 test case ran. (1 ms total)
[ PASSED ] 5 tests.

從結果上可以看出每個測試例項的全稱為:字首/測試用例名稱.測試例項名稱。

型別引數化

像能以引數的形式列出被測值一樣,我們也可以以引數的形式列出被測型別,這極大地方便了對模板類的測試。針對編寫測試程式碼前已知被測型別和未知被測型別兩種情況,Gtest還為我們提供了兩種不同的方法,下面假設我們分別使用兩種方法對以下類進行測試:

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


方法一:已知被測型別

對於以上模板類Queue,假設我們在編寫測試程式碼之前已知需要對其作int和char型別的測試,首先我們需要編寫生成具體型別的Queue工廠方法:

template <class T>
Queue<T>* CreateQueue();
template <>
Queue<int>* CreateQueue<int>()
{
return new Queue<int>;
}
template <>
Queue<char>* CreateQueue<char>()
{
return new Queue<char>;
}

然後我們需要編寫測試韌體類别範本(test fixture class template):

template <class T>
class QueueTest:public testing::Test
{
protected:
QueueTest():queue(CreateQueue<T>()){}
virtual ~QueueTest(){delete queue;}
Queue<T>* const queue;
};

可以看到QueueTest的建構函式中使用了工廠方法對類成員變數queue進行初始化。然後我們需要宣告和註冊我們要測試的型別:

using testing::Types;
// The list of types we want to test.
typedef Types<int, char> Implementations;

TYPED_TEST_CASE(QueueTest, Implementations);

這裡又用到了一個新的巨集:TYPED_TEST_CASE(TestCaseName, TypeList),第一個引數為測試用例名稱,第二個引數為型別列表。最後我們使用TYPED_TEST巨集編寫檢測程式碼:

// 檢測物件生成後,queue的大小是否為0
TYPED_TEST(QueueTest, DefaultConstructor) {
EXPECT_EQ(0u, this->queue->Size());
}

編譯、執行該測試程式,得到測試結果如下:

Running main() from gtest_main.cc
[==========] Running 2 tests from 2 test cases.
[----------] Global test environment set-up.
[----------] 1 test from QueueTest/0, where TypeParam = int
[ RUN ] QueueTest/0.DefaultConstructor
[ OK ] QueueTest/0.DefaultConstructor (0 ms)
[----------] 1 test from QueueTest/0 (1 ms total)

[----------] 1 test from QueueTest/1, where TypeParam = char
[ RUN ] QueueTest/1.DefaultConstructor
[ OK ] QueueTest/1.DefaultConstructor (0 ms)
[----------] 1 test from QueueTest/1 (0 ms total)

[----------] Global test environment tear-down
[==========] 2 tests from 2 test cases ran. (1 ms total)
[ PASSED ] 2 tests.


方法二:未知型別

在編寫測試案例的時候,我們可能並不知道該Queue類會被哪些型別例項化、被測型別是什麼,如此是否就無法編寫測試案例?!安心する,Gtest為我們提供了方法,可讓我們先寫檢測案例,具體要測的型別可以後期補上,這種方法如下:

與方法一相同,我們需要使用生成Queue的工廠方法,並定義韌體類别範本,這裡圖方便,繼承了以上QueueTest模板類。

template <class T>
class QueueTest2:public QueueTest<T>{
};

接下來,使用巨集TYPED_TEST_CASE_P宣告測試用例,其引數為韌體模板類的名稱。

TYPED_TEST_CASE_P(QueueTest2);

然後我們就可以使用巨集TYPED_TEST_P編寫測試案例了:

// 檢測物件生成後,queue的大小是否為0
TYPED_TEST_P(QueueTest2, DefaultConstructor) {
EXPECT_EQ(0u, this->queue->Size());
}

相比方法一,該方法還多了一步:註冊測試例項。這裡需要使用到REGISTER_TYPED_TEST_CASE_P巨集:

REGISTER_TYPED_TEST_CASE_P(QueueTest2, DefaultConstructor);

通過以上基本,我們大部分的未知型別測試程式碼都編寫完成了,但我們並沒有一個真正意義上的測試例項,因為我們還沒有指定測試型別。通常我們將以上測試程式碼寫進一個.h標頭檔案中,任何想要使用具體型別例項化的程式碼都可以#include該標頭檔案。
假設我們要測試int和char型別,我們可以在一個.cc檔案中編寫以下程式碼:

typedef Types<int, char> Implementations;

INSTANTIATE_TYPED_TEST_CASE_P(QueueInt_Char, QueueTest2, Implementations);

同樣,我們需要使用Types列出被測型別,注意到這裡列出被測型別是在使用TYPED_TEST_P編寫測試例項之後。


小結

這一節我們學習瞭如何使用Gtest值引數化的方法簡化函式測試,如何使用已知型別引數化、未知型別引數化的方法簡化對模板類的測試。Gtest project中給了我們一個值和型別均為自定義類的例子,感興趣的話可以猛擊這裡

Reference:googletest project

             《玩轉Google開源C++單元測試框架Google Test系列(gtest)》by CoderZh

相關文章