玩转Google开源C++单元测试框架Google Test系列(gtest)之四 - 参数化
一、前言
在設計測試案例時,經常需要考慮給被測函數傳入不同的值的情況。我們之前的做法通常是寫一個通用方法,然后編寫在測試案例調用它。即使使用了通用方法,這樣的工作也是有很多重復性的,程序員都懶,都希望能夠少寫代碼,多復用代碼。Google的程序員也一樣,他們考慮到了這個問題,并且提供了一個靈活的參數化測試的方案。
二、舊的方案
為了對比,我還是把舊的方案提一下。首先我先把被測函數IsPrime帖過來(在gtest的example1.cc中),這個函數是用來判斷傳入的數值是否為質數的。
//?Returns?true?iff?n?is?a?prime?number.bool?IsPrime(int?n)
{
????//?Trivial?case?1:?small?numbers
????if?(n?<=?1)?return?false;
????//?Trivial?case?2:?even?numbers
????if?(n?%?2?==?0)?return?n?==?2;
????//?Now,?we?have?that?n?is?odd?and?n?>=?3.
????//?Try?to?divide?n?by?every?odd?number?i,?starting?from?3
????for?(int?i?=?3;?;?i?+=?2)?{
????????//?We?only?have?to?try?i?up?to?the?squre?root?of?n
????????if?(i?>?n/i)?break;
????????//?Now,?we?have?i?<=?n/i?<?n.
????????//?If?n?is?divisible?by?i,?n?is?not?prime.
????????if?(n?%?i?==?0)?return?false;
????}
????//?n?has?no?integer?factor?in?the?range?(1,?n),?and?thus?is?prime.
????return?true;
}
?
假如我要編寫判斷結果為True的測試案例,我需要傳入一系列數值讓函數IsPrime去判斷是否為True(當然,即使傳入再多值也無法確保函數正確,呵呵),因此我需要這樣編寫如下的測試案例:
TEST(IsPrimeTest,?HandleTrueReturn){
????EXPECT_TRUE(IsPrime(3));
????EXPECT_TRUE(IsPrime(5));
????EXPECT_TRUE(IsPrime(11));
????EXPECT_TRUE(IsPrime(23));
????EXPECT_TRUE(IsPrime(17));
}
?
我們注意到,在這個測試案例中,我至少復制粘貼了4次,假如參數有50個,100個,怎么辦?同時,上面的寫法產生的是1個測試案例,里面有5個檢查點,假如我要把5個檢查變成5個單獨的案例,將會更加累人。
接下來,就來看看gtest是如何為我們解決這些問題的。?
三、使用參數化后的方案
1. 告訴gtest你的參數類型是什么
你必須添加一個類,繼承testing::TestWithParam<T>,其中T就是你需要參數化的參數類型,比如上面的例子,我需要參數化一個int型的參數
class?IsPrimeParamTest?:?public::testing::TestWithParam<int>{
};
?
2. 告訴gtest你拿到參數的值后,具體做些什么樣的測試
這里,我們要使用一個新的宏(嗯,挺興奮的):TEST_P,關于這個"P"的含義,Google給出的答案非常幽默,就是說你可以理解為”parameterized" 或者 "pattern"。我更傾向于?”parameterized"的解釋,呵呵。在TEST_P宏里,使用GetParam()獲取當前的參數的具體值。
TEST_P(IsPrimeParamTest,?HandleTrueReturn){
????int?n?=??GetParam();
????EXPECT_TRUE(IsPrime(n));
}
?
嗯,非常的簡潔!
3. 告訴gtest你想要測試的參數范圍是什么
?使用INSTANTIATE_TEST_CASE_P這宏來告訴gtest你要測試的參數范圍:
INSTANTIATE_TEST_CASE_P(TrueReturn,?IsPrimeParamTest,?testing::Values(3,?5,?11,?23,?17));?
第一個參數是測試案例的前綴,可以任意取。?
第二個參數是測試案例的名稱,需要和之前定義的參數化的類的名稱相同,如:IsPrimeParamTest?
第三個參數是可以理解為參數生成器,上面的例子使用test::Values表示使用括號內的參數。Google提供了一系列的參數生成的函數:
| Range(begin, end[, step]) | 范圍在begin~end之間,步長為step,不包括end |
| Values(v1, v2, ..., vN) | v1,v2到vN的值 |
| ValuesIn(container)and?ValuesIn(begin, end) | 從一個C類型的數組或是STL容器,或是迭代器中取值 |
| Bool() | 取false 和 true 兩個值 |
| Combine(g1, g2, ..., gN) | 這個比較強悍,它將g1,g2,...gN進行排列組合,g1,g2,...gN本身是一個參數生成器,每次分別從g1,g2,..gN中各取出一個值,組合成一個元組(Tuple)作為一個參數。 說明:這個功能只在提供了<tr1/tuple>頭的系統中有效。gtest會自動去判斷是否支持tr/tuple,如果你的系統確實支持,而gtest判斷錯誤的話,你可以重新定義宏GTEST_HAS_TR1_TUPLE=1。 |
?
四、參數化后的測試案例名
因為使用了參數化的方式執行案例,我非常想知道運行案例時,每個案例名稱是如何命名的。我執行了上面的代碼,輸出如下:
從上面的框框中的案例名稱大概能夠看出案例的命名規則,對于需要了解每個案例的名稱的我來說,這非常重要。 命名規則大概為:
prefix/test_case_name.test.name/index?
五、類型參數化?
gtest還提供了應付各種不同類型的數據時的方案,以及參數化類型的方案。我個人感覺這個方案有些復雜。首先要了解一下類型化測試,就用gtest里的例子了。
首先定義一個模版類,繼承testing::Test:
template?<typename?T>class?FooTest?:?public?testing::Test?{
?public:
??
??typedef?std::list<T>?List;
??static?T?shared_;
??T?value_;
};
?
接著我們定義需要測試到的具體數據類型,比如下面定義了需要測試char,int和unsigned int :
TYPED_TEST_CASE(FooTest,?MyTypes);
?
又是一個新的宏,來完成我們的測試案例,在聲明模版的數據類型時,使用TypeParam?
??//?Inside?a?test,?refer?to?the?special?name?TypeParam?to?get?the?type
??//?parameter.??Since?we?are?inside?a?derived?class?template,?C++?requires
??//?us?to?visit?the?members?of?FooTest?via?'this'.
??TypeParam?n?=?this->value_;
??//?To?visit?static?members?of?the?fixture,?add?the?'TestFixture::'
??//?prefix.
??n?+=?TestFixture::shared_;
??//?To?refer?to?typedefs?in?the?fixture,?add?the?'typename?TestFixture::'
??//?prefix.??The?'typename'?is?required?to?satisfy?the?compiler.
??typename?TestFixture::List?values;
??values.push_back(n);
??
}
上面的例子看上去也像是類型的參數化,但是還不夠靈活,因為需要事先知道類型的列表。gtest還提供一種更加靈活的類型參數化的方式,允許你在完成測試的邏輯代碼之后再去考慮需要參數化的類型列表,并且還可以重復的使用這個類型列表。下面也是官方的例子:
class?FooTest?:?public?testing::Test?{
??
};
TYPED_TEST_CASE_P(FooTest);
?
接著又是一個新的宏TYPED_TEST_P類完成我們的測試案例:
TYPED_TEST_P(FooTest,?DoesBlah)?{??//?Inside?a?test,?refer?to?TypeParam?to?get?the?type?parameter.
??TypeParam?n?=?0;
??
}
TYPED_TEST_P(FooTest,?HasPropertyA)?{??}
接著,我們需要我們上面的案例,使用REGISTER_TYPED_TEST_CASE_P宏,第一個參數是testcase的名稱,后面的參數是test的名稱
REGISTER_TYPED_TEST_CASE_P(FooTest,?DoesBlah,?HasPropertyA);
接著指定需要的類型列表:
typedef?testing::Types<char,?int,?unsigned?int>?MyTypes;INSTANTIATE_TYPED_TEST_CASE_P(My,?FooTest,?MyTypes);
這種方案相比之前的方案提供更加好的靈活度,當然,框架越靈活,復雜度也會隨之增加。?
六、總結?
gtest為我們提供的參數化測試的功能給我們的測試帶來了極大的方便,使得我們可以寫更少更優美的代碼,完成多種參數類型的測試案例。?
總結
以上是生活随笔為你收集整理的玩转Google开源C++单元测试框架Google Test系列(gtest)之四 - 参数化的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 玩转Google开源C++单元测试框架G
- 下一篇: 玩转Google开源C++单元测试框架G