玩转Google开源C++单元测试框架
玩轉(zhuǎn)Google開(kāi)源C++單元測(cè)試框架Google Test系列(gtest)(總)
前段時(shí)間學(xué)習(xí)和了解了下Google的開(kāi)源C++單元測(cè)試框架Google Test,簡(jiǎn)稱(chēng)gtest,非常的不錯(cuò)。 我們?cè)瓉?lái)使用的是自己實(shí)現(xiàn)的一套單元測(cè)試框架,在使用過(guò)程中,發(fā)現(xiàn)越來(lái)越多使用不便之處,而這樣不便之處,gtest恰恰很好的解決了。
其實(shí)gtest本身的實(shí)現(xiàn)并不復(fù)雜,我們完全可以模仿gtest,不斷的完善我們的測(cè)試框架, 但最后我們還是決定使用gtest取代掉原來(lái)的自己的測(cè)試框架,原因是:
1.不斷完善我們的測(cè)試框架之后就會(huì)發(fā)覺(jué)相當(dāng)于把gtest重新做了一遍,雖然輪子造的很爽,但是不是必要的。
2.使用gtest可以免去維護(hù)測(cè)試框架的麻煩,讓我們有更多精力投入到案例設(shè)計(jì)上。
3.gtest提高了非常完善的功能,并且簡(jiǎn)單易用,極大的提高了編寫(xiě)測(cè)試案例的效率。
gtest的官方網(wǎng)站是:
http://code.google.com/p/googletest/
從官方的使用文檔里,你幾乎可以獲得你想要的所有東西
http://code.google.com/p/googletest/wiki/GoogleTestPrimer
http://code.google.com/p/googletest/wiki/GoogleTestAdvancedGuide
如果還想對(duì)gtest內(nèi)部探個(gè)究竟,就把它的代碼下載下來(lái)研究吧,這就是開(kāi)源的好處,哈!
官方已經(jīng)有如此完備的文檔了,為什么我還要寫(xiě)呢?一方面是自己記記筆記,好記性不如爛筆頭,以后自己想查查一些用法也可以直接在這里查到,一方面是對(duì)于不想去看一大堆英文文檔的朋友,在我這里可以快速的找到gtest相關(guān)的內(nèi)容。
玩轉(zhuǎn)Google開(kāi)源C++單元測(cè)試框架Google Test系列(gtest)之一 - 初識(shí)gtest
一、前言
本篇將介紹一些gtest的基本使用,包括下載,安裝,編譯,建立我們第一個(gè)測(cè)試Demo工程,以及編寫(xiě)一個(gè)最簡(jiǎn)單的測(cè)試案例。
二、下載
如果不記得網(wǎng)址, 直接在google里搜gtest,第一個(gè)就是。目前gtest的最新版本為1.3.0,從下列地址可以下載到該最新版本:
http://googletest.googlecode.com/files/gtest-1.3.0.zip
http://googletest.googlecode.com/files/gtest-1.3.0.tar.gz
http://googletest.googlecode.com/files/gtest-1.3.0.tar.bz2
三、編譯
下載解壓后, 里面有個(gè)msvc目錄:
使用VS的同學(xué)可以直接打開(kāi)msvc里面的工程文件, 如果你在使用的是VS2005或是VS2008,打開(kāi)后會(huì)提示你升級(jí),升完級(jí)后,我們直接編譯里面的“gtest”工程,可以直接編過(guò)的。
這里要提醒一下的是,如果你升級(jí)為VS2008的工程,那么你的測(cè)試Demo最好也是VS2008工程,不然你會(huì)發(fā)現(xiàn)很郁悶,你的Demo怎么也編不過(guò),我也曾折騰了好久,當(dāng)時(shí)我升級(jí)為了VS2008工程,結(jié)果我使用VS2005工程建Demo,死活編不過(guò)。(這里有人誤解了,并不是說(shuō)只能在VS2008中編譯,在VS2005中同樣可以。如果要編譯VS2005版本,最好保證gtest和你的測(cè)試工程都使用VS2005工程。)
編譯之后,在msvc里面的Debug或是Release目錄里看到編譯出來(lái)的gtestd.lib或是gtest.lib文件。
四、第一個(gè)Demo
下面我們開(kāi)始建立我們的第一個(gè)Demo了,假如之前使用的VS2008編譯的gtest,那么,我們?cè)赩S2008中,新建一個(gè)Win32 Console Application。接著就是設(shè)置工程屬性,總結(jié)如下:
1.設(shè)置gtest頭文件路徑
2.設(shè)置gtest.lib路徑
3.Runtime Library設(shè)置
如果是Release版本,Runtime Library設(shè)為/MT。當(dāng)然,其實(shí)你也可以選擇動(dòng)態(tài)鏈接(/MD),前提是你之前編譯的gtest也使用了同樣是/MD選項(xiàng)。
工程設(shè)置后了后,我們來(lái)編寫(xiě)一個(gè)最簡(jiǎn)單測(cè)試案例試試,我們先來(lái)寫(xiě)一個(gè)被測(cè)試函數(shù):
上面可以看到,編寫(xiě)一個(gè)測(cè)試案例是多么的簡(jiǎn)單。 我們使用了TEST這個(gè)宏,它有兩個(gè)參數(shù),官方的對(duì)這兩個(gè)參數(shù)的解釋為:[TestCaseName,TestName],而我對(duì)這兩個(gè)參數(shù)的定義是:[TestSuiteName,TestCaseName],在下一篇我們?cè)賮?lái)看為什么這樣定義。
對(duì)檢查點(diǎn)的檢查,我們上面使用到了EXPECT_EQ這個(gè)宏,這個(gè)宏用來(lái)比較兩個(gè)數(shù)字是否相等。Google還包裝了一系列EXPECT_* 和ASSERT_的宏,而EXPECT系列和ASSERT系列的區(qū)別是:
1. EXPECT_ 失敗時(shí),案例繼續(xù)往下執(zhí)行。
2. ASSERT_* 失敗時(shí),直接在當(dāng)前函數(shù)中返回,當(dāng)前函數(shù)中ASSERT_*后面的語(yǔ)句將不會(huì)執(zhí)行。
在下一篇,我們?cè)賮?lái)具體討論這些斷言宏。為了讓我們的案例運(yùn)行起來(lái),我們還需要在main函數(shù)中添加如下代碼:
“testing::InitGoogleTest(&argc, argv);” :gtest的測(cè)試案例允許接收一系列的命令行參數(shù),因此,我們將命令行參數(shù)傳遞給gtest,進(jìn)行一些初始化操作。gtest的命令行參數(shù)非常豐富,在后面我們也會(huì)詳細(xì)了解到。
“RUN_ALL_TESTS()” :運(yùn)行所有測(cè)試案例
OK,一切就緒了,我們直接運(yùn)行案例試試(一片綠色,非常爽):
五、總結(jié)
本篇內(nèi)容確實(shí)是非常的初級(jí),目的是讓從來(lái)沒(méi)有接觸過(guò)gtest的同學(xué)了解gtest最基本的使用。gtest還有很多更高級(jí)的使用方法,我們將會(huì)在后面討論。總結(jié)本篇的內(nèi)容的話(huà):
1. 使用VS編譯gtest.lib文件
2. 設(shè)置測(cè)試工程的屬性(頭文件,lib文件,/MT參數(shù)(和編譯gtest時(shí)使用一樣的參數(shù)就行了))
3. 使用TEST宏開(kāi)始一個(gè)測(cè)試案例,使用EXPECT_*,ASSER_*系列設(shè)置檢查點(diǎn)。
4. 在Main函數(shù)中初始化環(huán)境,再使用RUN_ALL_TEST()宏運(yùn)行測(cè)試案例。
優(yōu)點(diǎn):
1. 我們的測(cè)試案例本身就是一個(gè)exe工程,編譯之后可以直接運(yùn)行,非常的方便。
2. 編寫(xiě)測(cè)試案例變的非常簡(jiǎn)單(使用一些簡(jiǎn)單的宏如TEST),讓我們將更多精力花在案例的設(shè)計(jì)和編寫(xiě)上。
3. 提供了強(qiáng)大豐富的斷言的宏,用于對(duì)各種不同檢查點(diǎn)的檢查。
4. 提高了豐富的命令行參數(shù)對(duì)案例運(yùn)行進(jìn)行一系列的設(shè)置。
玩轉(zhuǎn)Google開(kāi)源C++單元測(cè)試框架Google Test系列(gtest)之二 - 斷言
一、前言
這篇文章主要總結(jié)gtest中的所有斷言相關(guān)的宏。 gtest中,斷言的宏可以理解為分為兩類(lèi),一類(lèi)是ASSERT系列,一類(lèi)是EXPECT系列。一個(gè)直觀的解釋就是:
二、示例
// int型比較,預(yù)期值:3,實(shí)際值:Add(1, 2)
EXPECT_EQ(3, Add(1, 2))
//
假如你的Add(1, 2) 結(jié)果為4的話(huà),會(huì)在結(jié)果中輸出:
g:\myproject\c++\gtestdemo\gtestdemo\gtestdemo.cpp(16): error: Value of: Add(1, 2)
Actual: 4
Expected:3
如果是將結(jié)果輸出到xml里的話(huà),將輸出:(關(guān)于將結(jié)果輸出為xml,見(jiàn):http://www.cnblogs.com/coderzh/archive/2009/04/10/1432789.html)
<![CDATA[g:\myproject\c++\gtestdemo\gtestdemo\gtestdemo.cpp:16 Value of: Add(1, 2) Actual: 4 Expected: 3]]>
如果你對(duì)自動(dòng)輸出的出錯(cuò)信息不滿(mǎn)意的話(huà),你還可以通過(guò)操作符<<將一些自定義的信息輸出,通常,這對(duì)于調(diào)試或是對(duì)一些檢查點(diǎn)的補(bǔ)充說(shuō)明來(lái)說(shuō),非常有用!
下面舉個(gè)例子:
如果不使用<<操作符自定義輸出的話(huà):
for (int i = 0; i < x.size(); ++i) {EXPECT_EQ(x[i], y[i]); }看到的結(jié)果將是這樣的,你根本不知道出錯(cuò)時(shí) i 等于幾:
g:\myproject\c++\gtestdemo\gtestdemo\gtestdemo.cpp(25): error: Value of: y[i]
Actual: 4
Expected: x[i]
Which is: 3
如果使用<<操作符將一些重要信息輸出的話(huà):
for (int i = 0; i < x.size(); ++i) {EXPECT_EQ(x[i], y[i]) << "Vectors x and y differ at index " << i; }從輸出結(jié)果中就可以定位到在 i = 2 時(shí)出現(xiàn)了錯(cuò)誤。這樣的輸出結(jié)果看起來(lái)更加有用,容易理解:
g:\myproject\c++\gtestdemo\gtestdemo\gtestdemo.cpp(25): error: Value of: y[i]
Actual: 4
Expected: x[i]
Which is: 3
Vectors x and y differ at index 2
三、布爾值檢查
Fatal assertion Nonfatal assertion Verifies
ASSERT_TRUE(condition); EXPECT_TRUE(condition); condition is true
ASSERT_FALSE(condition); EXPECT_FALSE(condition); condition is false
四、數(shù)值型數(shù)據(jù)檢查
Fatal assertion Nonfatal assertion Verifies
ASSERT_EQ(expected, actual); EXPECT_EQ(expected, actual); expected == actual
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
五、字符串檢查
Fatal assertion Nonfatal assertion Verifies
ASSERT_STREQ(expected_str, actual_str); EXPECT_STREQ(expected_str, actual_str); the two C strings have the same content
ASSERT_STRNE(str1, str2); EXPECT_STRNE(str1, str2); the two C strings have different content
ASSERT_STRCASEEQ(expected_str, actual_str); EXPECT_STRCASEEQ(expected_str, actual_str); the two C strings have the same content, ignoring case
ASSERT_STRCASENE(str1, str2); EXPECT_STRCASENE(str1, str2); the two C strings have different content, ignoring case
STREQ和STRNE同時(shí)支持char和wchar_t類(lèi)型的,STRCASEEQ和STRCASENE卻只接收char*,估計(jì)是不常用吧。下面是幾個(gè)例子:
TEST(StringCmpTest, Demo) {char* pszCoderZh = "CoderZh";wchar_t* wszCoderZh = L"CoderZh";std::string strCoderZh = "CoderZh";std::wstring wstrCoderZh = L"CoderZh";EXPECT_STREQ("CoderZh", pszCoderZh);EXPECT_STREQ(L"CoderZh", wszCoderZh);EXPECT_STRNE("CnBlogs", pszCoderZh);EXPECT_STRNE(L"CnBlogs", wszCoderZh);EXPECT_STRCASEEQ("coderzh", pszCoderZh);//EXPECT_STRCASEEQ(L"coderzh", wszCoderZh); 不支持EXPECT_STREQ("CoderZh", strCoderZh.c_str());EXPECT_STREQ(L"CoderZh", wstrCoderZh.c_str()); }六、顯示返回成功或失敗
直接返回成功:SUCCEED();
返回失敗:
Fatal assertion Nonfatal assertion
FAIL(); ADD_FAILURE();
七、異常檢查
Fatal assertion Nonfatal assertion Verifies
ASSERT_THROW(statement, exception_type); EXPECT_THROW(statement, exception_type); statement throws an exception of the given type
ASSERT_ANY_THROW(statement); EXPECT_ANY_THROW(statement); statement throws an exception of any type
ASSERT_NO_THROW(statement); EXPECT_NO_THROW(statement); statement doesn’t throw any exception
例如:
int Foo(int a, int b) {if (a == 0 || b == 0){throw "don't do that";}int c = a % b;if (c == 0)return b;return Foo(b, c); }TEST(FooTest, HandleZeroInput) {EXPECT_ANY_THROW(Foo(10, 0));EXPECT_THROW(Foo(0, 5), char*); }八、Predicate Assertions
在使用EXPECT_TRUE或ASSERT_TRUE時(shí),有時(shí)希望能夠輸出更加詳細(xì)的信息,比如檢查一個(gè)函數(shù)的返回值TRUE還是FALSE時(shí),希望能夠輸出傳入的參數(shù)是什么,以便失敗后好跟蹤。因此提供了如下的斷言:
Fatal assertion Nonfatal assertion Verifies
ASSERT_PRED1(pred1, val1); EXPECT_PRED1(pred1, val1); pred1(val1) returns true
ASSERT_PRED2(pred2, val1, val2); EXPECT_PRED2(pred2, val1, val2); pred2(val1, val2) returns true
… … …
Google人說(shuō)了,他們只提供<=5個(gè)參數(shù)的,如果需要測(cè)試更多的參數(shù),直接告訴他們。下面看看這個(gè)東西怎么用。
bool MutuallyPrime(int m, int n) {return Foo(m , n) > 1; }TEST(PredicateAssertionTest, Demo) {int m = 5, n = 6;EXPECT_PRED2(MutuallyPrime, m, n); }當(dāng)失敗時(shí),返回錯(cuò)誤信息:
error: MutuallyPrime(m, n) evaluates to false, where
m evaluates to 5
n evaluates to 6
如果對(duì)這樣的輸出不滿(mǎn)意的話(huà),還可以自定義輸出格式,通過(guò)如下:
Fatal assertion Nonfatal assertion Verifies
ASSERT_PRED_FORMAT1(pred_format1, val1);` EXPECT_PRED_FORMAT1(pred_format1, val1); pred_format1(val1) is successful
ASSERT_PRED_FORMAT2(pred_format2, val1, val2); EXPECT_PRED_FORMAT2(pred_format2, val1, val2); pred_format2(val1, val2) is successful
… …
用法示例:
testing::AssertionResult AssertFoo(const char* m_expr, const char* n_expr, const char* k_expr, int m, int n, int k) {if (Foo(m, n) == k)return testing::AssertionSuccess();testing::Message msg;msg << m_expr << " 和 " << n_expr << " 的最大公約數(shù)應(yīng)該是:" << Foo(m, n) << " 而不是:" << k_expr;return testing::AssertionFailure(msg); }TEST(AssertFooTest, HandleFail) {EXPECT_PRED_FORMAT3(AssertFoo, 3, 6, 2); }失敗時(shí),輸出信息:
error: 3 和 6 的最大公約數(shù)應(yīng)該是:3 而不是:2
是不是更溫馨呢,呵呵。
九、浮點(diǎn)型檢查
Fatal assertion Nonfatal assertion Verifies
ASSERT_FLOAT_EQ(expected, actual); EXPECT_FLOAT_EQ(expected, actual); the two float values are almost equal
ASSERT_DOUBLE_EQ(expected, actual); EXPECT_DOUBLE_EQ(expected, actual); the two double values are almost equal
對(duì)相近的兩個(gè)數(shù)比較:
Fatal assertion Nonfatal assertion Verifies
ASSERT_NEAR(val1, val2, abs_error); EXPECT_NEAR(val1, val2, abs_error); the difference between val1 and val2 doesn’t exceed the given absolute error
同時(shí),還可以使用:
EXPECT_PRED_FORMAT2(testing::FloatLE, val1, val2);
EXPECT_PRED_FORMAT2(testing::DoubleLE, val1, val2);
十、Windows HRESULT assertions
Fatal assertion Nonfatal assertion Verifies
ASSERT_HRESULT_SUCCEEDED(expression); EXPECT_HRESULT_SUCCEEDED(expression); expression is a success HRESULT
ASSERT_HRESULT_FAILED(expression); EXPECT_HRESULT_FAILED(expression); expression is a failure HRESULT
例如:
CComPtr shell;
ASSERT_HRESULT_SUCCEEDED(shell.CoCreateInstance(L"Shell.Application"));
CComVariant empty;
ASSERT_HRESULT_SUCCEEDED(shell->ShellExecute(CComBSTR(url), empty, empty, empty, empty));
十一、類(lèi)型檢查
類(lèi)型檢查失敗時(shí),直接導(dǎo)致代碼編不過(guò),難得用處就在這?看下面的例子:
玩轉(zhuǎn)Google開(kāi)源C++單元測(cè)試框架Google Test系列(gtest)之三 - 事件機(jī)制
一、前言
gtest提供了多種事件機(jī)制,非常方便我們?cè)诎咐盎蛑笞鲆恍┎僮鳌?偨Y(jié)一下gtest的事件一共有3種:
二、全局事件
要實(shí)現(xiàn)全局事件,必須寫(xiě)一個(gè)類(lèi),繼承testing::Environment類(lèi),實(shí)現(xiàn)里面的SetUp和TearDown方法。
當(dāng)然,這樣還不夠,我們還需要告訴gtest添加這個(gè)全局事件,我們需要在main函數(shù)中通過(guò)testing::AddGlobalTestEnvironment方法將事件掛進(jìn)來(lái),也就是說(shuō),我們可以寫(xiě)很多個(gè)這樣的類(lèi),然后將他們的事件都掛上去。
int _tmain(int argc, _TCHAR* argv[])
{
testing::AddGlobalTestEnvironment(new FooEnvironment);
testing::InitGoogleTest(&argc, argv);
return RUN_ALL_TESTS();
}
三、TestSuite事件
我們需要寫(xiě)一個(gè)類(lèi),繼承testing::Test,然后實(shí)現(xiàn)兩個(gè)靜態(tài)方法
SetUpTestCase() 方法在第一個(gè)TestCase之前執(zhí)行
TearDownTestCase() 方法在最后一個(gè)TestCase之后執(zhí)行
在編寫(xiě)測(cè)試案例時(shí),我們需要使用TEST_F這個(gè)宏,第一個(gè)參數(shù)必須是我們上面類(lèi)的名字,代表一個(gè)TestSuite。
TEST_F(FooTest, Test1){// you can refer to shared_resource here } TEST_F(FooTest, Test2){// you can refer to shared_resource here }四、TestCase事件
TestCase事件是掛在每個(gè)案例執(zhí)行前后的,實(shí)現(xiàn)方式和上面的幾乎一樣,不過(guò)需要實(shí)現(xiàn)的是SetUp方法和TearDown方法:
五、總結(jié)
gtest提供的這三種事件機(jī)制還是非常的簡(jiǎn)單和靈活的。同時(shí),通過(guò)繼承Test類(lèi),使用TEST_F宏,我們可以在案例之間共享一些通用方法,共享資源。使得我們的案例更加的簡(jiǎn)潔,清晰。
玩轉(zhuǎn)Google開(kāi)源C++單元測(cè)試框架Google Test系列(gtest)之四 - 參數(shù)化
一、前言
在設(shè)計(jì)測(cè)試案例時(shí),經(jīng)常需要考慮給被測(cè)函數(shù)傳入不同的值的情況。我們之前的做法通常是寫(xiě)一個(gè)通用方法,然后編寫(xiě)在測(cè)試案例調(diào)用它。即使使用了通用方法,這樣的工作也是有很多重復(fù)性的,程序員都懶,都希望能夠少寫(xiě)代碼,多復(fù)用代碼。Google的程序員也一樣,他們考慮到了這個(gè)問(wèn)題,并且提供了一個(gè)靈活的參數(shù)化測(cè)試的方案。
二、舊的方案
為了對(duì)比,我還是把舊的方案提一下。首先我先把被測(cè)函數(shù)IsPrime帖過(guò)來(lái)(在gtest的example1.cc中),這個(gè)函數(shù)是用來(lái)判斷傳入的數(shù)值是否為質(zhì)數(shù)的。
// Returns true iff n is a prime number.
bool IsPrime(int n) {// Trivial case 1: small numbersif (n <= 1) return false;// Trivial case 2: even numbersif (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 3for (int i = 3; ; i += 2) {// We only have to try i up to the squre root of nif (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; }假如我要編寫(xiě)判斷結(jié)果為T(mén)rue的測(cè)試案例,我需要傳入一系列數(shù)值讓函數(shù)IsPrime去判斷是否為T(mén)rue(當(dāng)然,即使傳入再多值也無(wú)法確保函數(shù)正確,呵呵),因此我需要這樣編寫(xiě)如下的測(cè)試案例:
TEST(IsPrimeTest, HandleTrueReturn) {EXPECT_TRUE(IsPrime(3));EXPECT_TRUE(IsPrime(5));EXPECT_TRUE(IsPrime(11));EXPECT_TRUE(IsPrime(23));EXPECT_TRUE(IsPrime(17)); }我們注意到,在這個(gè)測(cè)試案例中,我至少?gòu)?fù)制粘貼了4次,假如參數(shù)有50個(gè),100個(gè),怎么辦?同時(shí),上面的寫(xiě)法產(chǎn)生的是1個(gè)測(cè)試案例,里面有5個(gè)檢查點(diǎn),假如我要把5個(gè)檢查變成5個(gè)單獨(dú)的案例,將會(huì)更加累人。
接下來(lái),就來(lái)看看gtest是如何為我們解決這些問(wèn)題的。
三、使用參數(shù)化后的方案
你必須添加一個(gè)類(lèi),繼承testing::TestWithParam,其中T就是你需要參數(shù)化的參數(shù)類(lèi)型,比如上面的例子,我需要參數(shù)化一個(gè)int型的參數(shù)
class IsPrimeParamTest : public::testing::TestWithParam
{
};
這里,我們要使用一個(gè)新的宏(嗯,挺興奮的):TEST_P,關(guān)于這個(gè)"P"的含義,Google給出的答案非常幽默,就是說(shuō)你可以理解為”parameterized" 或者 “pattern”。我更傾向于 ”parameterized"的解釋,呵呵。在TEST_P宏里,使用GetParam()獲取當(dāng)前的參數(shù)的具體值。
嗯,非常的簡(jiǎn)潔!
3. 告訴gtest你想要測(cè)試的參數(shù)范圍是什么
使用INSTANTIATE_TEST_CASE_P這宏來(lái)告訴gtest你要測(cè)試的參數(shù)范圍:
INSTANTIATE_TEST_CASE_P(TrueReturn, IsPrimeParamTest, testing::Values(3, 5, 11, 23, 17));
第一個(gè)參數(shù)是測(cè)試案例的前綴,可以任意取。
第二個(gè)參數(shù)是測(cè)試案例的名稱(chēng),需要和之前定義的參數(shù)化的類(lèi)的名稱(chēng)相同,如:IsPrimeParamTest
第三個(gè)參數(shù)是可以理解為參數(shù)生成器,上面的例子使用test::Values表示使用括號(hào)內(nèi)的參數(shù)。Google提供了一系列的參數(shù)生成的函數(shù):
Range(begin, end[, step]) 范圍在begin~end之間,步長(zhǎng)為step,不包括end
Values(v1, v2, …, vN) v1,v2到vN的值
ValuesIn(container) andValuesIn(begin, end) 從一個(gè)C類(lèi)型的數(shù)組或是STL容器,或是迭代器中取值
Bool() 取false 和 true 兩個(gè)值
Combine(g1, g2, …, gN) 這個(gè)比較強(qiáng)悍,它將g1,g2,…gN進(jìn)行排列組合,g1,g2,…gN本身是一個(gè)參數(shù)生成器,每次分別從g1,g2,…gN中各取出一個(gè)值,組合成一個(gè)元組(Tuple)作為一個(gè)參數(shù)。
說(shuō)明:這個(gè)功能只在提供了<tr1/tuple>頭的系統(tǒng)中有效。gtest會(huì)自動(dòng)去判斷是否支持tr/tuple,如果你的系統(tǒng)確實(shí)支持,而gtest判斷錯(cuò)誤的話(huà),你可以重新定義宏GTEST_HAS_TR1_TUPLE=1。
四、參數(shù)化后的測(cè)試案例名
因?yàn)槭褂昧藚?shù)化的方式執(zhí)行案例,我非常想知道運(yùn)行案例時(shí),每個(gè)案例名稱(chēng)是如何命名的。我執(zhí)行了上面的代碼,輸出如下:
從上面的框框中的案例名稱(chēng)大概能夠看出案例的命名規(guī)則,對(duì)于需要了解每個(gè)案例的名稱(chēng)的我來(lái)說(shuō),這非常重要。 命名規(guī)則大概為:
prefix/test_case_name.test.name/index
五、類(lèi)型參數(shù)化
gtest還提供了應(yīng)付各種不同類(lèi)型的數(shù)據(jù)時(shí)的方案,以及參數(shù)化類(lèi)型的方案。我個(gè)人感覺(jué)這個(gè)方案有些復(fù)雜。首先要了解一下類(lèi)型化測(cè)試,就用gtest里的例子了。
首先定義一個(gè)模版類(lèi),繼承testing::Test:
接著我們定義需要測(cè)試到的具體數(shù)據(jù)類(lèi)型,比如下面定義了需要測(cè)試char,int和unsigned int :
typedef testing::Types<char, int, unsigned int> MyTypes;
TYPED_TEST_CASE(FooTest, MyTypes);
又是一個(gè)新的宏,來(lái)完成我們的測(cè)試案例,在聲明模版的數(shù)據(jù)類(lèi)型時(shí),使用TypeParam
TYPED_TEST(FooTest, DoesBlah) {// 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);}上面的例子看上去也像是類(lèi)型的參數(shù)化,但是還不夠靈活,因?yàn)樾枰孪戎李?lèi)型的列表。gtest還提供一種更加靈活的類(lèi)型參數(shù)化的方式,允許你在完成測(cè)試的邏輯代碼之后再去考慮需要參數(shù)化的類(lèi)型列表,并且還可以重復(fù)的使用這個(gè)類(lèi)型列表。下面也是官方的例子:
template
class FooTest : public testing::Test {
};
TYPED_TEST_CASE_P(FooTest);
接著又是一個(gè)新的宏TYPED_TEST_P類(lèi)完成我們的測(cè)試案例:
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宏,第一個(gè)參數(shù)是testcase的名稱(chēng),后面的參數(shù)是test的名稱(chēng)
REGISTER_TYPED_TEST_CASE_P(FooTest, DoesBlah, HasPropertyA);
接著指定需要的類(lèi)型列表:
typedef testing::Types<char, int, unsigned int> MyTypes;
INSTANTIATE_TYPED_TEST_CASE_P(My, FooTest, MyTypes);
這種方案相比之前的方案提供更加好的靈活度,當(dāng)然,框架越靈活,復(fù)雜度也會(huì)隨之增加。
六、總結(jié)
gtest為我們提供的參數(shù)化測(cè)試的功能給我們的測(cè)試帶來(lái)了極大的方便,使得我們可以寫(xiě)更少更優(yōu)美的代碼,完成多種參數(shù)類(lèi)型的測(cè)試案例。
玩轉(zhuǎn)Google開(kāi)源C++單元測(cè)試框架Google Test系列(gtest)之五 - 死亡測(cè)試
一、前言
“死亡測(cè)試”名字比較恐怖,這里的“死亡”指的的是程序的崩潰。通常在測(cè)試過(guò)程中,我們需要考慮各種各樣的輸入,有的輸入可能直接導(dǎo)致程序崩潰,這時(shí)我們就需要檢查程序是否按照預(yù)期的方式掛掉,這也就是所謂的“死亡測(cè)試”。gtest的死亡測(cè)試能做到在一個(gè)安全的環(huán)境下執(zhí)行崩潰的測(cè)試案例,同時(shí)又對(duì)崩潰結(jié)果進(jìn)行驗(yàn)證。
二、使用的宏
Fatal assertion Nonfatal assertion Verifies
ASSERT_DEATH(statement, regex); EXPECT_DEATH(statement, regex); statement crashes with the given error
ASSERT_EXIT(statement, predicate, regex); EXPECT_EXIT(statement, predicate, regex); statement exits with the given error and its exit code matches predicate
由于有些異常只在Debug下拋出,因此還提供了*_DEBUG_DEATH,用來(lái)處理Debug和Realease下的不同。
三、*_DEATH(statement, regex`)
如下面的例子:
重要:編寫(xiě)死亡測(cè)試案例時(shí),TEST的第一個(gè)參數(shù),即testcase_name,請(qǐng)使用DeathTest后綴。原因是gtest會(huì)優(yōu)先運(yùn)行死亡測(cè)試案例,應(yīng)該是為線(xiàn)程安全考慮。
四、*_EXIT(statement, predicate, regex`)
testing::ExitedWithCode(exit_code)
如果程序正常退出并且退出碼與exit_code相同則返回 true
testing::KilledBySignal(signal_number) // Windows下不支持
如果程序被signal_number信號(hào)kill的話(huà)就返回true
3. regex是一個(gè)正則表達(dá)式,用來(lái)匹配異常時(shí)在stderr中輸出的內(nèi)容
這里, 要說(shuō)明的是,_DEATH其實(shí)是對(duì)_EXIT進(jìn)行的一次包裝,*_DEATH的predicate判斷進(jìn)程是否以非0退出碼退出或被一個(gè)信號(hào)殺死。
例子:
五、*_DEBUG_DEATH
先來(lái)看定義:
可以看到,在Debug版和Release版本下, *_DEBUG_DEATH的定義不一樣。因?yàn)楹芏喈惓V粫?huì)在Debug版本下拋出,而在Realease版本下不會(huì)拋出,所以針對(duì)Debug和Release分別做了不同的處理。看gtest里自帶的例子就明白了:
int DieInDebugElse12(int* sideeffect) {if (sideeffect) *sideeffect = 12; #ifndef NDEBUGGTEST_LOG_(FATAL, "debug death inside DieInDebugElse12()"); #endif // NDEBUGreturn 12; }TEST(TestCase, TestDieOr12WorksInDgbAndOpt) {int sideeffect = 0;// Only asserts in dbg.EXPECT_DEBUG_DEATH(DieInDebugElse12(&sideeffect), "death");#ifdef NDEBUG// opt-mode has sideeffect visible.EXPECT_EQ(12, sideeffect);#else// dbg-mode no visible sideeffect.EXPECT_EQ(0, sideeffect);#endif }六、關(guān)于正則表達(dá)式
在POSIX系統(tǒng)(Linux, Cygwin, 和 Mac)中,gtest的死亡測(cè)試中使用的是POSIX風(fēng)格的正則表達(dá)式,想了解POSIX風(fēng)格表達(dá)式可參考:
在Windows系統(tǒng)中,gtest的死亡測(cè)試中使用的是gtest自己實(shí)現(xiàn)的簡(jiǎn)單的正則表達(dá)式語(yǔ)法。 相比POSIX風(fēng)格,gtest的簡(jiǎn)單正則表達(dá)式少了很多內(nèi)容,比如 (“x|y”), ("(xy)"), ("[xy]") 和(“x{5,7}”)都不支持。
下面是簡(jiǎn)單正則表達(dá)式支持的一些內(nèi)容:
matches any literal character c
\d matches any decimal digit
\D matches any character that’s not a decimal digit
\f matches \f
\n matches \n
\r matches \r
\s matches any ASCII whitespace, including \n
\S matches any character that’s not a whitespace
\t matches \t
\v matches \v
\w matches any letter, _, or decimal digit
\W matches any character that \w doesn’t match
\c matches any literal character c, which must be a punctuation
. matches any single character except \n
A? matches 0 or 1 occurrences of A
A* matches 0 or many occurrences of A
A+ matches 1 or many occurrences of A
^ matches the beginning of a string (not that of each line)
$ matches the end of a string (not that of each line)
xy matches x followed by y
gtest定義兩個(gè)宏,用來(lái)表示當(dāng)前系統(tǒng)支持哪套正則表達(dá)式風(fēng)格:
七、死亡測(cè)試運(yùn)行方式
testing::FLAGS_gtest_death_test_style = “fast”;
testing::FLAGS_gtest_death_test_style = “threadsafe”;
你可以在 main() 里為所有的死亡測(cè)試設(shè)置測(cè)試形式,也可以為某次測(cè)試單獨(dú)設(shè)置。Google Test會(huì)在每次測(cè)試之前保存這個(gè)標(biāo)記并在測(cè)試完成后恢復(fù),所以你不需要去管這部分工作 。如:
TEST(MyDeathTest, TestOne) {testing::FLAGS_gtest_death_test_style = "threadsafe";// This test is run in the "threadsafe" style:ASSERT_DEATH(ThisShouldDie(), ""); }TEST(MyDeathTest, TestTwo) {// This test is run in the "fast" style:ASSERT_DEATH(ThisShouldDie(), ""); }int main(int argc, char** argv) {testing::InitGoogleTest(&argc, argv);testing::FLAGS_gtest_death_test_style = "fast";return RUN_ALL_TESTS(); }八、注意事項(xiàng)
九、總結(jié)
關(guān)于死亡測(cè)試,gtest官方的文檔已經(jīng)很詳細(xì)了,同時(shí)在源碼中也有大量的示例。如想了解更多的請(qǐng)參考官方的文檔,或是直接看gtest源碼。
簡(jiǎn)單來(lái)說(shuō),通過(guò)*_DEATH(statement, regex)和*_EXIT(statement, predicate, regex),我們可以非常方便的編寫(xiě)導(dǎo)致崩潰的測(cè)試案例,并且在不影響其他案例執(zhí)行的情況下,對(duì)崩潰案例的結(jié)果進(jìn)行檢查。
玩轉(zhuǎn)Google開(kāi)源C++單元測(cè)試框架Google Test系列(gtest)之六 - 運(yùn)行參數(shù)
一、前言
使用gtest編寫(xiě)的測(cè)試案例通常本身就是一個(gè)可執(zhí)行文件,因此運(yùn)行起來(lái)非常方便。同時(shí),gtest也為我們提供了一系列的運(yùn)行參數(shù)(環(huán)境變量、命令行參數(shù)或代碼里指定),使得我們可以對(duì)案例的執(zhí)行進(jìn)行一些有效的控制。
二、基本介紹
前面提到,對(duì)于運(yùn)行參數(shù),gtest提供了三種設(shè)置的途徑:
因?yàn)樘峁┝巳N途徑,就會(huì)有優(yōu)先級(jí)的問(wèn)題, 有一個(gè)原則是,最后設(shè)置的那個(gè)會(huì)生效。不過(guò)總結(jié)一下,通常情況下,比較理想的優(yōu)先級(jí)為:
命令行參數(shù) > 代碼中指定FLAG > 系統(tǒng)環(huán)境變量
為什么我們編寫(xiě)的測(cè)試案例能夠處理這些命令行參數(shù)呢?是因?yàn)槲覀冊(cè)趍ain函數(shù)中,將命令行參數(shù)交給了gtest,由gtest來(lái)搞定命令行參數(shù)的問(wèn)題。
int _tmain(int argc, _TCHAR* argv[])
{
testing::InitGoogleTest(&argc, argv);
return RUN_ALL_TESTS();
}
這樣,我們就擁有了接收和響應(yīng)gtest命令行參數(shù)的能力。如果需要在代碼中指定FLAG,可以使用testing::GTEST_FLAG這個(gè)宏來(lái)設(shè)置。比如相對(duì)于命令行參數(shù)–gtest_output,可以使用testing::GTEST_FLAG(output) = “xml:”;來(lái)設(shè)置。注意到了,不需要加–gtest前綴了。同時(shí),推薦將這句放置InitGoogleTest之前,這樣就可以使得對(duì)于同樣的參數(shù),命令行參數(shù)優(yōu)先級(jí)高于代碼中指定。
int _tmain(int argc, _TCHAR* argv[]) {testing::GTEST_FLAG(output) = "xml:";testing::InitGoogleTest(&argc, argv);return RUN_ALL_TESTS(); }最后再來(lái)說(shuō)下第一種設(shè)置方式-系統(tǒng)環(huán)境變量。如果需要gtest的設(shè)置系統(tǒng)環(huán)境變量,必須注意的是:
三、參數(shù)列表
了解了上面的內(nèi)容,我這里就直接將所有命令行參數(shù)總結(jié)和羅列一下。如果想要獲得詳細(xì)的命令行說(shuō)明,直接運(yùn)行你的案例,輸入命令行參數(shù):/? 或 --help 或 -help
命令行參數(shù) 說(shuō)明
–gtest_list_tests 使用這個(gè)參數(shù)時(shí),將不會(huì)執(zhí)行里面的測(cè)試案例,而是輸出一個(gè)案例的列表。
–gtest_filter 對(duì)執(zhí)行的測(cè)試案例進(jìn)行過(guò)濾,支持通配符
? 單個(gè)字符
- 任意字符
- 排除,如,-a 表示除了a
: 取或,如,a:b 表示a或b
比如下面的例子:
./foo_test 沒(méi)有指定過(guò)濾條件,運(yùn)行所有案例
./foo_test --gtest_filter=* 使用通配符*,表示運(yùn)行所有案例
./foo_test --gtest_filter=FooTest.* 運(yùn)行所有“測(cè)試案例名稱(chēng)(testcase_name)”為FooTest的案例
./foo_test --gtest_filter=Null:Constructor 運(yùn)行所有“測(cè)試案例名稱(chēng)(testcase_name)”或“測(cè)試名稱(chēng)(test_name)”包含Null或Constructor的案例。
./foo_test --gtest_filter=-DeathTest. 運(yùn)行所有非死亡測(cè)試案例。
./foo_test --gtest_filter=FooTest.*-FooTest.Bar 運(yùn)行所有“測(cè)試案例名稱(chēng)(testcase_name)”為FooTest的案例,但是除了FooTest.Bar這個(gè)案例
–gtest_also_run_disabled_tests 執(zhí)行案例時(shí),同時(shí)也執(zhí)行被置為無(wú)效的測(cè)試案例。關(guān)于設(shè)置測(cè)試案例無(wú)效的方法為:
在測(cè)試案例名稱(chēng)或測(cè)試名稱(chēng)中添加DISABLED前綴,比如:
// Tests that Foo does Abc.
TEST(FooTest, DISABLED_DoesAbc) { }
class DISABLED_BarTest : public testing::Test { };
// Tests that Bar does Xyz.
TEST_F(DISABLED_BarTest, DoesXyz) { }
–gtest_repeat=[COUNT] 設(shè)置案例重復(fù)運(yùn)行次數(shù),非常棒的功能!比如:
–gtest_repeat=1000 重復(fù)執(zhí)行1000次,即使中途出現(xiàn)錯(cuò)誤。
–gtest_repeat=-1 無(wú)限次數(shù)執(zhí)行。。。。
–gtest_repeat=1000 --gtest_break_on_failure 重復(fù)執(zhí)行1000次,并且在第一個(gè)錯(cuò)誤發(fā)生時(shí)立即停止。這個(gè)功能對(duì)調(diào)試非常有用。
–gtest_repeat=1000 --gtest_filter=FooBar 重復(fù)執(zhí)行1000次測(cè)試案例名稱(chēng)為FooBar的案例。
測(cè)試案例輸出
命令行參數(shù) 說(shuō)明
–gtest_color=(yes|no|auto) 輸出命令行時(shí)是否使用一些五顏六色的顏色。默認(rèn)是auto。
–gtest_print_time 輸出命令行時(shí)是否打印每個(gè)測(cè)試案例的執(zhí)行時(shí)間。默認(rèn)是不打印的。
–gtest_output=xml[:DIRECTORY_PATH|:FILE_PATH] 將測(cè)試結(jié)果輸出到一個(gè)xml中。
1.–gtest_output=xml: 不指定輸出路徑時(shí),默認(rèn)為案例當(dāng)前路徑。
2.–gtest_output=xml:d:\ 指定輸出到某個(gè)目錄
3.–gtest_output=xml:d:\foo.xml 指定輸出到d:\foo.xml
如果不是指定了特定的文件路徑,gtest每次輸出的報(bào)告不會(huì)覆蓋,而會(huì)以數(shù)字后綴的方式創(chuàng)建。xml的輸出內(nèi)容后面介紹吧。
對(duì)案例的異常處理
命令行參數(shù) 說(shuō)明
–gtest_break_on_failure 調(diào)試模式下,當(dāng)案例失敗時(shí)停止,方便調(diào)試
–gtest_throw_on_failure 當(dāng)案例失敗時(shí)以C++異常的方式拋出
–gtest_catch_exceptions 是否捕捉異常。gtest默認(rèn)是不捕捉異常的,因此假如你的測(cè)試案例拋了一個(gè)異常,很可能會(huì)彈出一個(gè)對(duì)話(huà)框,這非常的不友好,同時(shí)也阻礙了測(cè)試案例的運(yùn)行。如果想不彈這個(gè)框,可以通過(guò)設(shè)置這個(gè)參數(shù)來(lái)實(shí)現(xiàn)。如將–gtest_catch_exceptions設(shè)置為一個(gè)非零的數(shù)。
注意:這個(gè)參數(shù)只在Windows下有效。
四、XML報(bào)告輸出格式
<?xml version="1.0" encoding="UTF-8"?>從報(bào)告里可以看出,我們之前在TEST等宏中定義的測(cè)試案例名稱(chēng)(testcase_name)在xml測(cè)試報(bào)告中其實(shí)是一個(gè)testsuite name,而宏中的測(cè)試名稱(chēng)(test_name)在xml測(cè)試報(bào)告中是一個(gè)testcase name,概念上似乎有點(diǎn)混淆,就看你怎么看吧。
當(dāng)檢查點(diǎn)通過(guò)時(shí),不會(huì)輸出任何檢查點(diǎn)的信息。當(dāng)檢查點(diǎn)失敗時(shí),會(huì)有詳細(xì)的失敗信息輸出來(lái)failure節(jié)點(diǎn)。
在我使用過(guò)程中發(fā)現(xiàn)一個(gè)問(wèn)題,當(dāng)我同時(shí)設(shè)置了–gtest_filter參數(shù)時(shí),輸出的xml報(bào)告中還是會(huì)包含所有測(cè)試案例的信息,只不過(guò)那些不被執(zhí)行的測(cè)試案例的status值為“notrun”。而我之前認(rèn)為的輸出的xml報(bào)告應(yīng)該只包含我需要運(yùn)行的測(cè)試案例的信息。不知是否可提供一個(gè)只輸出需要執(zhí)行的測(cè)試案例的xml報(bào)告。因?yàn)楫?dāng)我需要在1000個(gè)案例中執(zhí)行其中1個(gè)案例時(shí),在報(bào)告中很難找到我運(yùn)行的那個(gè)案例,雖然可以查找,但還是很麻煩。
五、總結(jié)
本篇主要介紹了gtest案例執(zhí)行時(shí)提供的一些參數(shù)的使用方法,這些參數(shù)都非常有用。在實(shí)際編寫(xiě)gtest測(cè)試案例時(shí)肯定會(huì)需要用到的時(shí)候。至少我現(xiàn)在比較常用的就是:
最后再總結(jié)一下我使用過(guò)程中遇到的幾個(gè)問(wèn)題:
玩轉(zhuǎn)Google開(kāi)源C++單元測(cè)試框架Google Test系列(gtest)之七 - 深入解析gtest
一、前言
“深入解析”對(duì)我來(lái)說(shuō)的確有些難度,所以我盡量將我學(xué)習(xí)到和觀察到的gtest內(nèi)部實(shí)現(xiàn)介紹給大家。本文算是拋磚引玉吧,只能是對(duì)gtest的整體結(jié)構(gòu)的一些介紹,想要了解更多細(xì)節(jié)最好的辦法還是看gtest源碼,如果你看過(guò)gtest源碼,你會(huì)發(fā)現(xiàn)里面的注釋非常的詳細(xì)!好了,下面就開(kāi)始了解gtest吧。
二、從TEST宏開(kāi)始
前面的文章已經(jīng)介紹過(guò)TEST宏的用法了,通過(guò)TEST宏,我們可以非法簡(jiǎn)單、方便的編寫(xiě)測(cè)試案例,比如:
TEST(FooTest, Demo)
{
EXPECT_EQ(1, 1);
}
我們先不去看TEST宏的定義,而是先使用/P參數(shù)將TEST展開(kāi)。如果使用的是Vistual Studio的話(huà):
編譯過(guò)后,會(huì)在源代碼目錄生成一個(gè)后綴為.i的文件,比如我對(duì)上面的代碼進(jìn)行展開(kāi),展開(kāi)后的內(nèi)容為:
展開(kāi)后,我們觀察到:
如下圖:
上面關(guān)鍵的方法就是MakeAndRegisterTestInfo了,我們跳到MakeAndRegisterTestInfo函數(shù)中:
// 創(chuàng)建一個(gè) TestInfo 對(duì)象并注冊(cè)到 Google Test;
// 返回創(chuàng)建的TestInfo對(duì)象
//
// 參數(shù):
//
// test_case_name: 測(cè)試案例的名稱(chēng)
// name: 測(cè)試的名稱(chēng)
// test_case_comment: 測(cè)試案例的注釋信息
// comment: 測(cè)試的注釋信息
// fixture_class_id: test fixture類(lèi)的ID
// set_up_tc: 事件函數(shù)SetUpTestCases的函數(shù)地址
// tear_down_tc: 事件函數(shù)TearDownTestCases的函數(shù)地址
// factory: 工廠(chǎng)對(duì)象,用于創(chuàng)建測(cè)試對(duì)象(Test)
TestInfo* MakeAndRegisterTestInfo(
const char* test_case_name, const char* name,
const char* test_case_comment, const char* comment,
TypeId fixture_class_id,
SetUpTestCaseFunc set_up_tc,
TearDownTestCaseFunc tear_down_tc,
TestFactoryBase* factory) {
TestInfo* const test_info =
new TestInfo(test_case_name, name, test_case_comment, comment,
fixture_class_id, factory);
GetUnitTestImpl()->AddTestInfo(set_up_tc, tear_down_tc, test_info);
return test_info;
}
我們看到,上面創(chuàng)建了一個(gè)TestInfo對(duì)象,然后通過(guò)AddTestInfo注冊(cè)了這個(gè)對(duì)象。TestInfo對(duì)象到底是一個(gè)什么樣的東西呢?
TestInfo對(duì)象主要用于包含如下信息:
我們還看到,TestInfo的構(gòu)造函數(shù)中,非常重要的一個(gè)參數(shù)就是工廠(chǎng)對(duì)象,它主要負(fù)責(zé)在運(yùn)行測(cè)試案例時(shí)創(chuàng)建出Test對(duì)象。我們看到我們上面的例子的factory為:
new ::testing::internal::TestFactoryImpl< FooTest_Demo_Test>
我們明白了,Test對(duì)象原來(lái)就是TEST宏展開(kāi)后的那個(gè)類(lèi)的對(duì)象(FooTest_Demo_Test),再看看TestFactoryImpl的實(shí)現(xiàn):
template
class TestFactoryImpl : public TestFactoryBase {
public:
virtual Test* CreateTest() { return new TestClass; }
};
這個(gè)對(duì)象工廠(chǎng)夠簡(jiǎn)單吧,嗯,Simple is better。當(dāng)我們需要?jiǎng)?chuàng)建一個(gè)測(cè)試對(duì)象(Test)時(shí),調(diào)用factory的CreateTest()方法就可以了。
創(chuàng)建了TestInfo對(duì)象后,再通過(guò)下面的方法對(duì)TestInfo對(duì)象進(jìn)行注冊(cè):
GetUnitTestImpl()->AddTestInfo(set_up_tc, tear_down_tc, test_info);
GetUnitTestImpl()是獲取UnitTestImpl對(duì)象:
inline UnitTestImpl* GetUnitTestImpl() {
return UnitTest::GetInstance()->impl();
}
其中UnitTest是一個(gè)單件(Singleton),整個(gè)進(jìn)程空間只有一個(gè)實(shí)例,通過(guò)UnitTest::GetInstance()獲取單件的實(shí)例。上面的代碼看到,UnitTestImpl對(duì)象是最終是從UnitTest對(duì)象中獲取的。那么UnitTestImpl到底是一個(gè)什么樣的東西呢?可以這樣理解:
UnitTestImpl是一個(gè)在UnitTest內(nèi)部使用的,為執(zhí)行單元測(cè)試案例而提供了一系列實(shí)現(xiàn)的那么一個(gè)類(lèi)。(自己歸納的,可能不準(zhǔn)確)
我們上面的AddTestInfo就是其中的一個(gè)實(shí)現(xiàn),負(fù)責(zé)注冊(cè)TestInfo實(shí)例:
我們看到,TestCase對(duì)象出來(lái)了,并通過(guò)AddTestInfo添加了一個(gè)TestInfo對(duì)象。這時(shí),似乎豁然開(kāi)朗了:
我們來(lái)看看TestCase的創(chuàng)建過(guò)程(UnitTestImpl::GetTestCase):
// 查找并返回一個(gè)指定名稱(chēng)的TestCase對(duì)象。如果對(duì)象不存在,則創(chuàng)建一個(gè)并返回
//
// 參數(shù):
//
// test_case_name: 測(cè)試案例名稱(chēng)
// set_up_tc: 事件函數(shù)SetUpTestCases的函數(shù)地址
// tear_down_tc: 事件函數(shù)TearDownTestCases的函數(shù)地址
三、回過(guò)頭看看TEST宏的定義
#define TEST(test_case_name, test_name)
GTEST_TEST_(test_case_name, test_name,
::testing::Test, ::testing::internal::GetTestTypeId())
同時(shí)也看看TEST_F宏
#define TEST_F(test_fixture, test_name)
GTEST_TEST_(test_fixture, test_name, test_fixture,
::testing::internal::GetTypeId<test_fixture>())
都是使用了GTEST_TEST_宏,在看看這個(gè)宏如何定義的:
#define GTEST_TEST_(test_case_name, test_name, parent_class, parent_id)
class GTEST_TEST_CLASS_NAME_(test_case_name, test_name) : public parent_class {
public:
GTEST_TEST_CLASS_NAME_(test_case_name, test_name)() {}
private:
virtual void TestBody();
static ::testing::TestInfo* const test_info_;
GTEST_DISALLOW_COPY_AND_ASSIGN_(
GTEST_TEST_CLASS_NAME_(test_case_name, test_name));
};
::testing::TestInfo* const GTEST_TEST_CLASS_NAME_(test_case_name, test_name)
::test_info_ =
::testing::internal::MakeAndRegisterTestInfo(
#test_case_name, #test_name, “”, “”,
(parent_id),
parent_class::SetUpTestCase,
parent_class::TearDownTestCase,
new ::testing::internal::TestFactoryImpl<
GTEST_TEST_CLASS_NAME_(test_case_name, test_name)>);
void GTEST_TEST_CLASS_NAME_(test_case_name, test_name)::TestBody()
不需要多解釋了,和我們上面展開(kāi)看到的差不多,不過(guò)這里比較明確的看到了,我們?cè)赥EST宏里寫(xiě)的就是TestBody里的東西。這里再補(bǔ)充說(shuō)明一下里面的GTEST_DISALLOW_COPY_AND_ASSIGN_宏,我們上面的例子看出,這個(gè)宏展開(kāi)后:
FooTest_Demo_Test(const FooTest_Demo_Test &);
void operator=(const FooTest_Demo_Test &);
正如這個(gè)宏的名字一樣,它是用于防止對(duì)對(duì)象進(jìn)行拷貝和賦值操作的。
四、再來(lái)了解RUN_ALL_TESTS宏
我們的測(cè)試案例的運(yùn)行就是通過(guò)這個(gè)宏發(fā)起的。RUN_ALL_TEST的定義非常簡(jiǎn)單:
#define RUN_ALL_TESTS()
(::testing::UnitTest::GetInstance()->Run())
我們又看到了熟悉的::testing::UnitTest::GetInstance(),看來(lái)案例的執(zhí)行時(shí)從UnitTest的Run方法開(kāi)始的,我提取了一些Run中的關(guān)鍵代碼,如下:
int UnitTest::Run() {__try {return impl_->RunAllTests();} __except(internal::UnitTestOptions::GTestShouldProcessSEH(GetExceptionCode())) {printf("Exception thrown with code 0x%x.\nFAIL\n", GetExceptionCode());fflush(stdout);return 1;}return impl_->RunAllTests(); }我們又看到了熟悉的impl(UnitTestImpl),具體案例該怎么執(zhí)行,還是得靠UnitTestImpl。
int UnitTestImpl::RunAllTests() {// ...printer->OnUnitTestStart(parent_);// 計(jì)時(shí)const TimeInMillis start = GetTimeInMillis();printer->OnGlobalSetUpStart(parent_);// 執(zhí)行全局的SetUp事件environments_.ForEach(SetUpEnvironment);printer->OnGlobalSetUpEnd(parent_);// 全局的SetUp事件執(zhí)行成功的話(huà)if (!Test::HasFatalFailure()) {// 執(zhí)行每個(gè)測(cè)試案例test_cases_.ForEach(TestCase::RunTestCase);}// 執(zhí)行全局的TearDown事件printer->OnGlobalTearDownStart(parent_);environments_in_reverse_order_.ForEach(TearDownEnvironment);printer->OnGlobalTearDownEnd(parent_);elapsed_time_ = GetTimeInMillis() - start;// 執(zhí)行完成printer->OnUnitTestEnd(parent_);// Gets the result and clears it.if (!Passed()) {failed = true;}ClearResult();// 返回測(cè)試結(jié)果return failed ? 1 : 0; }上面,我們很開(kāi)心的看到了我們前面講到的全局事件的調(diào)用。environments_是一個(gè)Environment的鏈表結(jié)構(gòu)(List),它的內(nèi)容是我們?cè)趍ain中通過(guò):
testing::AddGlobalTestEnvironment(new FooEnvironment);
添加進(jìn)去的。test_cases_我們之前也了解過(guò)了,是一個(gè)TestCase的鏈表結(jié)構(gòu)(List)。gtest實(shí)現(xiàn)了一個(gè)鏈表,并且提供了一個(gè)Foreach方法,迭代調(diào)用某個(gè)函數(shù),并將里面的元素作為函數(shù)的參數(shù):
template <typename F> // F is the type of the function/functor void ForEach(F functor) const {for ( const ListNode<E> * node = Head();node != NULL;node = node->next() ) {functor(node->element());} }因此,我們關(guān)注一下:environments_.ForEach(SetUpEnvironment),其實(shí)是迭代調(diào)用了SetUpEnvironment函數(shù):
static void SetUpEnvironment(Environment* env) { env->SetUp(); }
最終調(diào)用了我們定義的SetUp()函數(shù)。
再看看test_cases_.ForEach(TestCase::RunTestCase)的TestCase::RunTestCase實(shí)現(xiàn):
static void RunTestCase(TestCase * test_case) { test_case->Run(); }
再看TestCase的Run實(shí)現(xiàn):
void TestCase::Run() {if (!should_run_) return;internal::UnitTestImpl* const impl = internal::GetUnitTestImpl();impl->set_current_test_case(this);UnitTestEventListenerInterface * const result_printer =impl->result_printer();result_printer->OnTestCaseStart(this);impl->os_stack_trace_getter()->UponLeavingGTest();// 哈!SetUpTestCases事件在這里調(diào)用set_up_tc_();const internal::TimeInMillis start = internal::GetTimeInMillis();// 嗯,前面分析的一個(gè)TestCase對(duì)應(yīng)多個(gè)TestInfo,因此,在這里迭代對(duì)TestInfo調(diào)用RunTest方法test_info_list_->ForEach(internal::TestInfoImpl::RunTest);elapsed_time_ = internal::GetTimeInMillis() - start;impl->os_stack_trace_getter()->UponLeavingGTest();// TearDownTestCases事件在這里調(diào)用tear_down_tc_();result_printer->OnTestCaseEnd(this);impl->set_current_test_case(NULL); }第二種事件機(jī)制又浮出我們眼前,非常興奮。可以看出,SetUpTestCases和TearDownTestCaess是在一個(gè)TestCase之前和之后調(diào)用的。接著看test_info_list_->ForEach(internal::TestInfoImpl::RunTest):
static void RunTest(TestInfo * test_info) {
test_info->impl()->Run();
}
哦?TestInfo也有一個(gè)impl?看來(lái)我們之前漏掉了點(diǎn)東西,和UnitTest很類(lèi)似,TestInfo內(nèi)部也有一個(gè)主管各種實(shí)現(xiàn)的類(lèi),那就是TestInfoImpl,它在TestInfo的構(gòu)造函數(shù)中創(chuàng)建了出來(lái)(還記得前面講的TestInfo的創(chuàng)建過(guò)程嗎?):
因此,案例的執(zhí)行還得看TestInfoImpl的Run()方法,同樣,我簡(jiǎn)化一下,只列出關(guān)鍵部分的代碼:
void TestInfoImpl::Run() {// ...UnitTestEventListenerInterface* const result_printer =impl->result_printer();result_printer->OnTestStart(parent_);// 開(kāi)始計(jì)時(shí)const TimeInMillis start = GetTimeInMillis();Test* test = NULL;__try {// 我們的對(duì)象工廠(chǎng),使用CreateTest()生成Test對(duì)象test = factory_->CreateTest();} __except(internal::UnitTestOptions::GTestShouldProcessSEH(GetExceptionCode())) {AddExceptionThrownFailure(GetExceptionCode(),"the test fixture's constructor");return;}// 如果Test對(duì)象創(chuàng)建成功if (!Test::HasFatalFailure()) {// 調(diào)用Test對(duì)象的Run()方法,執(zhí)行測(cè)試案例 test->Run();}// 執(zhí)行完畢,刪除Test對(duì)象impl->os_stack_trace_getter()->UponLeavingGTest();delete test;test = NULL;// 停止計(jì)時(shí)result_.set_elapsed_time(GetTimeInMillis() - start);result_printer->OnTestEnd(parent_);}上面看到了我們前面講到的對(duì)象工廠(chǎng)fatory,通過(guò)fatory的CreateTest()方法,創(chuàng)建Test對(duì)象,然后執(zhí)行案例又是通過(guò)Test對(duì)象的Run()方法:
void Test::Run() {if (!HasSameFixtureClass()) return;internal::UnitTestImpl* const impl = internal::GetUnitTestImpl();impl->os_stack_trace_getter()->UponLeavingGTest();__try {// Yeah!每個(gè)案例的SetUp事件在這里調(diào)用SetUp();} __except(internal::UnitTestOptions::GTestShouldProcessSEH(GetExceptionCode())) {AddExceptionThrownFailure(GetExceptionCode(), "SetUp()");}// We will run the test only if SetUp() had no fatal failure.if (!HasFatalFailure()) {impl->os_stack_trace_getter()->UponLeavingGTest();__try {// 哈哈!!千辛萬(wàn)苦,我們定義在TEST宏里的東西終于被調(diào)用了!TestBody();} __except(internal::UnitTestOptions::GTestShouldProcessSEH(GetExceptionCode())) {AddExceptionThrownFailure(GetExceptionCode(), "the test body");}}impl->os_stack_trace_getter()->UponLeavingGTest();__try {// 每個(gè)案例的TearDown事件在這里調(diào)用TearDown();} __except(internal::UnitTestOptions::GTestShouldProcessSEH(GetExceptionCode())) {AddExceptionThrownFailure(GetExceptionCode(), "TearDown()");} }上面的代碼里非常極其以及特別的興奮的看到了執(zhí)行測(cè)試案例的前后事件,測(cè)試案例執(zhí)行TestBody()的代碼。仿佛整個(gè)gtest的流程在眼前一目了然了。
四、總結(jié)
本文通過(guò)分析TEST宏和RUN_ALL_TEST宏,了解到了整個(gè)gtest運(yùn)作過(guò)程,可以說(shuō)整個(gè)過(guò)程簡(jiǎn)潔而優(yōu)美。之前讀《代碼之美》,感觸頗深,現(xiàn)在讀過(guò)gtest代碼,再次讓我感觸深刻。記得很早前,我對(duì)設(shè)計(jì)的理解是“功能越強(qiáng)大越好,設(shè)計(jì)越復(fù)雜越好,那樣才顯得牛”,漸漸得,我才發(fā)現(xiàn),簡(jiǎn)單才是最好。我曾總結(jié)過(guò)自己寫(xiě)代碼的設(shè)計(jì)原則:功能明確,設(shè)計(jì)簡(jiǎn)單。了解了gtest代碼后,猛然發(fā)現(xiàn)gtest不就是這樣嗎,同時(shí)gtest也給了我很多驚喜,因此,我對(duì)gtest的評(píng)價(jià)是:功能強(qiáng)大,設(shè)計(jì)簡(jiǎn)單,使用方便。
總結(jié)一下gtest里的幾個(gè)關(guān)鍵的對(duì)象:
本文還有很多gtest的細(xì)節(jié)沒(méi)有分析到,比如運(yùn)行參數(shù),死亡測(cè)試,跨平臺(tái)處理,斷言的宏等等,希望讀者自己把源碼下載下來(lái)慢慢研究。如本文有錯(cuò)誤之處,也請(qǐng)大家指出,謝謝!
玩轉(zhuǎn)Google開(kāi)源C++單元測(cè)試框架Google Test系列(gtest)之八 - 打造自己的單元測(cè)試框架
一、前言
上一篇我們分析了gtest的一些內(nèi)部實(shí)現(xiàn),總的來(lái)說(shuō)整體的流程并不復(fù)雜。本篇我們就嘗試編寫(xiě)一個(gè)精簡(jiǎn)版本的C++單元測(cè)試框架:nancytest ,通過(guò)編寫(xiě)這個(gè)簡(jiǎn)單的測(cè)試框架,將有助于我們理解gtest。
二、整體設(shè)計(jì)
使用最精簡(jiǎn)的設(shè)計(jì),我們就用兩個(gè)類(lèi),夠簡(jiǎn)單吧:
包含單個(gè)測(cè)試案例的信息。
負(fù)責(zé)所有測(cè)試案例的執(zhí)行,管理。
三、TestCase類(lèi)
TestCase類(lèi)包含一個(gè)測(cè)試案例的基本信息,包括:測(cè)試案例名稱(chēng),測(cè)試案例執(zhí)行結(jié)果,同時(shí)還提供了測(cè)試案例執(zhí)行的方法。我們編寫(xiě)的測(cè)試案例都繼承自TestCase類(lèi)。
四、UnitTest類(lèi)
我們的UnitTest類(lèi)和gtest的一樣,是一個(gè)單件。我們的UnitTest類(lèi)的邏輯非常簡(jiǎn)單:
下面是UnitTest類(lèi)的實(shí)現(xiàn):
UnitTest* UnitTest::GetInstance() {static UnitTest instance;return &instance; }TestCase* UnitTest::RegisterTestCase(TestCase* testcase) {testcases_.push_back(testcase);return testcase; }int UnitTest::Run() {nTestResult = 1;for (std::vector<TestCase*>::iterator it = testcases_.begin();it != testcases_.end(); ++it){TestCase* testcase = *it;CurrentTestCase = testcase;std::cout << green << "======================================" << std::endl;std::cout << green << "Run TestCase:" << testcase->testcase_name << std::endl;testcase->Run();std::cout << green << "End TestCase:" << testcase->testcase_name << std::endl;if (testcase->nTestResult){nPassed++;}else{nFailed++;nTestResult = 0;}}std::cout << green << "======================================" << std::endl;std::cout << green << "Total TestCase : " << nPassed + nFailed << std::endl;std::cout << green << "Passed : " << nPassed << std::endl;std::cout << red << "Failed : " << nFailed << std::endl;return nTestResult; }五、NTEST宏
接下來(lái)定一個(gè)宏NTEST,方便我們寫(xiě)我們的測(cè)試案例的類(lèi)。
六、RUN_ALL_TEST宏
然后是執(zhí)行所有測(cè)試案例的一個(gè)宏:
#define RUN_ALL_TESTS()
UnitTest::GetInstance()->Run();
七、斷言的宏EXPECT_EQ
這里,我只寫(xiě)一個(gè)簡(jiǎn)單的EXPECT_EQ :
#define EXPECT_EQ(m, n)
if (m != n)
{
UnitTest::GetInstance()->CurrentTestCase->nTestResult = 0;
std::cout << red << “Failed” << std::endl;
std::cout << red << “Expect:” << m << std::endl;
std::cout << red << “Actual:” << n << std::endl;
}
八、案例Demo
夠簡(jiǎn)單吧,再來(lái)看看案例怎么寫(xiě):
整個(gè)一山寨版gtest,呵。執(zhí)行一下,看看結(jié)果怎么樣:
九、總結(jié)
本篇介紹性的文字比較少,主要是我們?cè)谏弦黄钊虢馕鰃test時(shí)已經(jīng)將整個(gè)流程弄清楚了,而現(xiàn)在編寫(xiě)的nancytest又是其非常的精簡(jiǎn)版本,所有直接看代碼就可以完全理解。希望通過(guò)這個(gè)Demo,能夠讓大家對(duì)gtest有更加直觀的了解。回到開(kāi)篇時(shí)所說(shuō)的,我們沒(méi)有必要每個(gè)人都造一個(gè)輪子,因?yàn)間test已經(jīng)非常出色的為我們做好了這一切。如果我們每個(gè)人都寫(xiě)一個(gè)自己的框架的話(huà),一方面我們要付出大量的維護(hù)成本,一方面,這個(gè)框架也許只能對(duì)你有用,無(wú)法讓大家從中受益。
gtest正是這么一個(gè)優(yōu)秀C++單元測(cè)試框架,它完全開(kāi)源,允許我們一起為其貢獻(xiàn)力量,并能讓更多人從中受益。如果你在使用gtest過(guò)程中發(fā)現(xiàn)gtest不能滿(mǎn)足你的需求時(shí)(或發(fā)現(xiàn)BUG),gtest的開(kāi)發(fā)人員非常急切的想知道他們哪來(lái)沒(méi)做好,或者是gtest其實(shí)有這個(gè)功能,但是很多用戶(hù)都不知道。所以你可以直接聯(lián)系gtest的開(kāi)發(fā)人員,或者你直接在這里回帖,我會(huì)將您的意見(jiàn)轉(zhuǎn)告給gtest的主要開(kāi)發(fā)人員。
如果你是gtest的超級(jí)粉絲,原意為gtest貢獻(xiàn)代碼的話(huà),加入他們吧。
本Demo代碼下載:/Files/coderzh/Code/nancytest.rar
本篇是該系列最后一篇,其實(shí)gtest還有更多東西值得我們?nèi)ヌ剿?#xff0c;本系列也不可能將gtest介紹完全,還是那句話(huà),想了解更多gtest相關(guān)的內(nèi)容的話(huà):
訪(fǎng)問(wèn)官方主頁(yè):http://code.google.com/p/googletest/
下載gtest源碼: http://code.google.com/p/googletest/downloads/list
gtest中如何跳出當(dāng)前測(cè)試案例
在前面的玩轉(zhuǎn)gtest - 斷言中, 我們提到了ASSERT_*系列的斷言只是在當(dāng)前函數(shù)返回,并非退出當(dāng)前測(cè)試案例,因?yàn)锳SSERT_*系列是通過(guò)return來(lái)實(shí)現(xiàn)的(因此 ASSERT_*系列不能在返回值不為void的函數(shù)內(nèi)出現(xiàn))。要退出當(dāng)前測(cè)試案例,一個(gè)最簡(jiǎn)單的方法就是通過(guò)拋異常,然后讓gtest捕獲這一異常。示例如下:
(上面的兩個(gè)printf函數(shù)都不會(huì)執(zhí)行。)
要退出當(dāng)前測(cè)試案例,你只需要兩步:
1.設(shè)置catch_exception標(biāo)志,在main函數(shù)或是在你的測(cè)試案例前都可以。
2.要跳出測(cè)試案例時(shí),只需要通過(guò)throw拋出任意異常即可。
為何通過(guò)這種方法可以跳出當(dāng)前測(cè)試案例,請(qǐng)參考玩轉(zhuǎn)gtest - 深入解析gtest。
需要注意的是:假如使用的是TEST_F宏,跳出當(dāng)前測(cè)試案例后,會(huì)執(zhí)行TearDown(),因此不必當(dāng)心TearDown中釋放資源的操作不會(huì)執(zhí)行。
我認(rèn)為一個(gè)好的測(cè)試案例,應(yīng)該是在你的測(cè)試函數(shù)中,比如TEST宏內(nèi),清晰的表達(dá)出你要測(cè)試的對(duì)象,以及預(yù)期的測(cè)試結(jié)果。因此,通常情況下,EXPECT_*和 ASSERT_*應(yīng)該盡量在測(cè)試函數(shù)中出現(xiàn),而不是在測(cè)試函數(shù)內(nèi)調(diào)用的另外函數(shù)或是里面很多層的函數(shù)內(nèi)才出現(xiàn)。(比如上面的Func函數(shù)中的 EXPECT_EQ)。
編寫(xiě)優(yōu)美的GTest測(cè)試案例
使用gtest也有很長(zhǎng)一段時(shí)間了,這期間也積累了一些經(jīng)驗(yàn),所以分享一下。GTest為我們提供了便捷的測(cè)試框架,讓我們只需要關(guān)注案例本身。如何在GTest框架下寫(xiě)出優(yōu)美的測(cè)試案例,我覺(jué)得必須要做到:
1.案例的層次結(jié)構(gòu)一定要清晰
2.案例的檢查點(diǎn)一定要明確
3.案例失敗時(shí)一定要能精確的定位問(wèn)題
4.案例執(zhí)行結(jié)果一定要穩(wěn)定
5.案例執(zhí)行的時(shí)間一定不能太長(zhǎng)
6.案例一定不能對(duì)測(cè)試環(huán)境造成破壞
7.案例一定獨(dú)立,不能與其他案例有先后關(guān)系的依賴(lài)
8.案例的命名一定清晰,容易理解
案例的可維護(hù)性也是非常重要,如果做到上面的8點(diǎn),自然也就做到了可維護(hù)性。下面來(lái)分享一下我對(duì)于上面8點(diǎn)的經(jīng)驗(yàn):
所謂層次結(jié)構(gòu),至少要讓人一眼就能分辨出被測(cè)代碼和測(cè)試代碼。簡(jiǎn)單的說(shuō),就是知道你在測(cè)什么。由于是進(jìn)行接口測(cè)試,我已經(jīng)習(xí)慣了如下的案例層次:
DataDefine
我會(huì)將測(cè)試案例所需要的數(shù)據(jù),以及數(shù)據(jù)之間的聯(lián)系全部在預(yù)先定義好。測(cè)試數(shù)據(jù)與案例邏輯的分離,有利于維護(hù)和擴(kuò)展測(cè)試案例。同時(shí),GTest先天就支持測(cè)試數(shù)據(jù)參數(shù)化,為測(cè)試數(shù)據(jù)的分離提供了進(jìn)一步的便捷。什么是測(cè)試數(shù)據(jù)參數(shù)化?就是你可以預(yù)先定義好一批各種各樣的數(shù)據(jù),而你只需要編寫(xiě)一個(gè)測(cè)試案例的邏輯代碼,gtest會(huì)將定義好的數(shù)據(jù)逐個(gè)套入測(cè)試案例中進(jìn)行執(zhí)行。具體的做法請(qǐng)見(jiàn):玩轉(zhuǎn)Google開(kāi)源C++單元測(cè)試框架Google Test系列(gtest)之四 - 參數(shù)化
SUT
SUT,即system under test,表明你的測(cè)試對(duì)象是什么,它可以是一個(gè)類(lèi)(CUT),對(duì)象(OUT),函數(shù)(MUT),甚至可以是整個(gè)應(yīng)用程序(AUT)。我單獨(dú)將這個(gè)層次劃分出來(lái),主要有兩個(gè)目的:
?明確的表示出你的測(cè)試對(duì)象是什么
?為復(fù)雜調(diào)用對(duì)象包裝簡(jiǎn)單調(diào)用接口
明確表示測(cè)試對(duì)象是什么,便于之后對(duì)測(cè)試案例的維護(hù)和對(duì)測(cè)試案例的理解。同時(shí),對(duì)于一些被測(cè)對(duì)象,你想要調(diào)用它需要經(jīng)過(guò)一系列煩瑣的過(guò)程,這時(shí),就需要將這一煩瑣的調(diào)用過(guò)程隱藏起來(lái),而只關(guān)注被測(cè)對(duì)象的輸入和輸出。
TestCase
測(cè)試工程中,必須非常明確的表示出哪些是測(cè)試案例,哪些是其他的輔助文件。通常,我們會(huì)在測(cè)試案例的文件名加上Test前綴(或者后綴)。我建議,將所有的測(cè)試案例文件或代碼放在最顯眼的地方,讓所有看到你的測(cè)試工程的人,第一眼看到的就是測(cè)試案例,這很重要。
Checker
對(duì)于一個(gè)復(fù)雜系統(tǒng)的接口測(cè)試,僅僅堅(jiān)持輸入和輸出是遠(yuǎn)遠(yuǎn)不夠的。比如測(cè)試一個(gè)寫(xiě)數(shù)據(jù)庫(kù)的函數(shù),函數(shù)的返回值告訴你數(shù)據(jù)已經(jīng)成功寫(xiě)入是遠(yuǎn)遠(yuǎn)不夠的,你必須親身去數(shù)據(jù)庫(kù)中查個(gè)究竟才行。因此,對(duì)于某一類(lèi)的測(cè)試案例,我們可以抽象出一些通用的檢查點(diǎn)代碼。
如果做到上面的分層,那么一個(gè)測(cè)試案例寫(xiě)出來(lái)的結(jié)構(gòu)應(yīng)該會(huì)是這個(gè)樣子:
TEST(TestFoo, JustDemo)
{
GetTestData(); // 獲取測(cè)試數(shù)據(jù)
}
這樣的測(cè)試案例,一目了然。
2. 案例的檢查點(diǎn)一定要明確
一定要明確案例的檢查點(diǎn)是什么,并且讓檢查點(diǎn)盡量集中。有一個(gè)不好的習(xí)慣就是核心的檢查點(diǎn)在分布在多個(gè)函數(shù)中,需要不斷的跳轉(zhuǎn)才能了解到這個(gè)案例檢查了些什么。好的做法應(yīng)該是盡量讓檢查點(diǎn)集中,能夠非常清晰的分辨出案例對(duì)被測(cè)代碼做了哪些檢查。所以,盡量讓Gtest的ASSERT_和EXPECT_系列的宏放在明顯和正確的地方。
3. 案例失敗時(shí)一定要能精確的定位問(wèn)題
測(cè)試案例失敗時(shí),我們通常手忙腳亂。如果一個(gè)測(cè)試案例Failed,卻不能立即推斷是被測(cè)代碼的Bug的話(huà),這個(gè)測(cè)試案例也有待改進(jìn)。我們可以在一些復(fù)雜的檢查點(diǎn)斷言中加入一些輔助信息,方便我們定位問(wèn)題。比如下面這個(gè)測(cè)試案例:
int n = -1;
bool actualResult = Foo::Dosometing(n);
ASSERT_TRUE(actualResult)
如果測(cè)試案例失敗了,會(huì)得到下面的信息:
Value of: actualResult
Actual: false
Expected:true
這樣的結(jié)果對(duì)于我們來(lái)說(shuō),幾乎沒(méi)有什么用。因?yàn)槲覀兏静恢繿ctualResult是什么,以及在什么情況下才會(huì)出現(xiàn)非預(yù)期值。因此,在斷言處多加入一些信息,將有助于定位問(wèn)題:
int n = -1;
bool actualResult = Foo::Dosometing(n);
ASSERT_TRUE(actualResult) << L"Call Foo::Dosometing(n) when n = " << n;
4. 案例執(zhí)行結(jié)果一定要穩(wěn)定
要保證測(cè)試案例在什么時(shí)候、什么情況下執(zhí)行的結(jié)果都是一樣的。一個(gè)一會(huì)成功一會(huì)失敗的案例是沒(méi)有意義的。要保證案例穩(wěn)定性的方法有很多,比如杜絕案例之間的影響,有時(shí)候,由于前一個(gè)案例執(zhí)行完后,將一些系統(tǒng)的環(huán)境破壞了,導(dǎo)致后面的案例執(zhí)行失敗。在測(cè)試某些本身就存在一定幾率或延時(shí)的系統(tǒng)時(shí),使用超時(shí)機(jī)制是比較簡(jiǎn)單的辦法。比如,你需要測(cè)試一個(gè)啟動(dòng)Windows服務(wù)的方法,如果我們?cè)谡{(diào)用了該方法后立即進(jìn)行檢查,很可能檢查點(diǎn)會(huì)失敗,有時(shí)候也許又是通過(guò)的。這是因?yàn)閃indows服務(wù)由Stop狀態(tài)到Running狀態(tài),中間還要經(jīng)過(guò)一個(gè)Padding狀態(tài)。所以,簡(jiǎn)單的做法是使用超時(shí)機(jī)制,隔斷時(shí)間檢查一次,直到超過(guò)某個(gè)最大忍受時(shí)間。
我們應(yīng)該盡量讓案例能夠快速的執(zhí)行,一方面,我們可以通過(guò)優(yōu)化我們的代碼來(lái)減少運(yùn)行時(shí)間,比如,減少對(duì)重復(fù)內(nèi)容的讀取。一方面,對(duì)于一些比較耗時(shí)的操作,比如文件系統(tǒng),網(wǎng)絡(luò)操作,我們可以使用Mock對(duì)象來(lái)替代真實(shí)的對(duì)象。使用GMock是一個(gè)不錯(cuò)的選擇。
有的案例需要在特定的環(huán)境下來(lái)能執(zhí)行,因此會(huì)在案例的初始化時(shí)對(duì)環(huán)境進(jìn)行一些修改。注意,不管對(duì)什么東西進(jìn)行了修改,一定要保證在案例執(zhí)行完成的TearDown中將這些環(huán)境都還原回來(lái)。否則有可能對(duì)后面的案例造成影響,或者出現(xiàn)一些莫名其妙的錯(cuò)誤。
任何一個(gè)案例都不依賴(lài)于其他測(cè)試案例,任何一個(gè)案例的執(zhí)行結(jié)果都不應(yīng)該影響到別的案例。任何一個(gè)案例都可以單獨(dú)拿出去正確的執(zhí)行。所以,不能寄希望于前一個(gè)案例所做的環(huán)境準(zhǔn)備,因?yàn)檫@是不對(duì)的。
案例的名字要規(guī)范,長(zhǎng)不要緊,一定要清晰的表達(dá)測(cè)試案例的用途。比如,下面的測(cè)試案例名稱(chēng)都是不好的:
TEST(TestFoo, Test)
TEST(TestFoo, Normal)
TEST(TestFoo, Alright)
比如像下面的案例名稱(chēng)就會(huì)好一點(diǎn):
TEST(TestFoo, Return_True_When_ParameterN_Larger_Then_Zero)
TEST(TestFoo, Return_False_When_ParameterN_Is_Zero)
gtest參數(shù)化測(cè)試代碼示例
1.在玩轉(zhuǎn) Google開(kāi)源C++單元測(cè)試框架Google Test系列(gtest)之四 - 參數(shù)化中已經(jīng)介紹過(guò)了如何使用gtest進(jìn)行參數(shù)化測(cè)試。在twitter上應(yīng) @xlinker 的要求,我在這里提供一個(gè)參數(shù)化的完整例子。這個(gè)例子也是我當(dāng)初了解gtest時(shí)寫(xiě)的,同時(shí)這個(gè)例子也在《玩轉(zhuǎn)》系列中出現(xiàn)過(guò)。最后,我再附上整個(gè)demo工程,里面有一些其他的示例,剛開(kāi)始上手的同學(xué)可以直接拿我的demo工程去試,有任何疑問(wèn)都?xì)g迎提出。以下是使用TEST_P宏進(jìn)行參數(shù)化測(cè)試的示例:
總結(jié)
以上是生活随笔為你收集整理的玩转Google开源C++单元测试框架的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: 蓝牙耳机的音质真的很差吗?2021商城高
- 下一篇: 开源:Taurus.MVC 框架