新聞中心
Google C++ Testing Framework(簡(jiǎn)稱(chēng)gtest,http://code.google.com/p/googletest/)是Google公司發(fā)布的一個(gè)開(kāi)源C/C++單元測(cè)試框架,已被應(yīng)用于多個(gè)開(kāi)源項(xiàng)目及Google內(nèi)部項(xiàng)目中,老牌的例子包括Chrome Web瀏覽器、LLVM編譯器架構(gòu)、Protocol Buffers數(shù)據(jù)交換格式及工具等。

優(yōu)秀的C/C++單元測(cè)試框架并不算少,相比之下gtest仍具有明顯優(yōu)勢(shì)。與CppUnit比,gtest需要使用的頭文件和函數(shù)宏更集中,并支持測(cè)試用例的自動(dòng)注冊(cè)。與CxxUnit比,gtest不要求Python等外部工具的存在。與Boost.Test比,gtest更簡(jiǎn)潔容易上手,實(shí)用性也并不遜色。Wikipedia給出了各種編程語(yǔ)言的單元測(cè)試框架列表(http://en.wikipedia.org/wiki/List_of_unit_testing_frameworks)。
一、基本用法
gtest當(dāng)前的版本是1.5.0,如果使用Visual C++編譯,要求編譯器版本不低于7.1(Visual C++ 2003)。如下圖所示,它的msvc文件夾包含Visual C++工程和項(xiàng)目文件,samples文件夾包含10個(gè)使用范例。
一般情況下,我們的單元測(cè)試代碼只需要包含頭文件gtest.h。gtest中常用的所有結(jié)構(gòu)體、類(lèi)、函數(shù)、常量等,都通過(guò)命名空間testing訪問(wèn),不過(guò)gtest已經(jīng)把最簡(jiǎn)單常用的單元測(cè)試功能包裝成了一些帶參數(shù)宏,因此在簡(jiǎn)單的測(cè)試中常常可以忽略命名空間的存在。
按照gtest的叫法,宏
TEST為特定的測(cè)試用例(Test Case)定義了一個(gè)可執(zhí)行的測(cè)試(Test)。它接受用戶(hù)指定的測(cè)試用例名(一般取被測(cè)對(duì)象名)和測(cè)試名作為參數(shù),并劃出了一個(gè)作用域供填充測(cè)試宏語(yǔ)句和普通的C++代碼。一系列TEST的集合就構(gòu)成一個(gè)簡(jiǎn)單的測(cè)試程序。 常用的測(cè)試宏如下表所示。以
ASSERT_開(kāi)頭和以
EXPECT_開(kāi)頭的宏的區(qū)別是,前者在測(cè)試失敗時(shí)會(huì)給出報(bào)告并立即終止測(cè)試程序,后者在報(bào)告后繼續(xù)執(zhí)行測(cè)試程序。
| ASSERT 宏 | EXPECT 宏 | 功能 |
| ASSERT_TRUE | EXPECT_TRUE | 判真 |
| ASSERT_FALSE | EXPECT_FALSE | 判假 |
| ASSERT_EQ | EXPECT_EQ | 相等 |
| ASSERT_NE | EXPECT_NE | 不等 |
| ASSERT_GT | EXPECT_GT | 大于 |
| ASSERT_LT | EXPECT_LT | 小于 |
| ASSERT_GE | EXPECT_GE | 大于或等于 |
| ASSERT_LE | EXPECT_LE | 小于或等于 |
| ASSERT_FLOAT_EQ | EXPECT_FLOAT_EQ | 單精度浮點(diǎn)值相等 |
| ASSERT_DOUBLE_EQ | EXPECT_DOUBLE_EQ | 雙精度浮點(diǎn)值相等 |
| ASSERT_NEAR | EXPECT_NEAR | 浮點(diǎn)值接近(第3個(gè)參數(shù)為誤差閾值) |
| ASSERT_STREQ | EXPECT_STREQ | C字符串相等 |
| ASSERT_STRNE | EXPECT_STRNE | C字符串不等 |
| ASSERT_STRCASEEQ | EXPECT_STRCASEEQ | C字符串相等(忽略大小寫(xiě)) |
| ASSERT_STRCASENE | EXPECT_STRCASENE | C字符串不等(忽略大小寫(xiě)) |
| ASSERT_PRED1 | EXPECT_PRED1 | 自定義謂詞函數(shù), (pred, arg1)(還有_PRED2, ..., _PRED5) |
寫(xiě)個(gè)簡(jiǎn)單的測(cè)試試一下。假設(shè)我們實(shí)現(xiàn)了一個(gè)加法函數(shù):
- // add.h
- #pragma once
- inline int Add(int i, int j) { return i+j; }
對(duì)應(yīng)的單元測(cè)試程序可以這樣寫(xiě):
- // add_unittest.cpp
- #include "add.h"
- #include
- TEST(Add, 負(fù)數(shù)) {
- EXPECT_EQ(Add(-1,-2), -3);
- EXPECT_GT(Add(-4,-5), -6); // 故意的
- }
- TEST(Add, 正數(shù)) {
- EXPECT_EQ(Add(1,2), 3);
- EXPECT_GT(Add(4,5), 6);
- }
代碼中,測(cè)試用例
Add包含兩個(gè)測(cè)試,正數(shù)和負(fù)數(shù)(這里利用了Visual C++ 2005以上允許標(biāo)識(shí)符包含Unicode字符的特性)。編譯運(yùn)行效果如下: 在控制臺(tái)界面中,通過(guò)的測(cè)試用綠色表示,失敗的測(cè)試用紅色表示。雙橫線(xiàn)分隔了不同的測(cè)試用例,其中包含的每個(gè)測(cè)試的啟動(dòng)與結(jié)果用單橫線(xiàn)和RUN ... OK或RUN ... FAILED標(biāo)出。失敗的測(cè)試會(huì)打印出代碼行和原因,測(cè)試程序***為所有用例和測(cè)試顯示統(tǒng)計(jì)結(jié)果。建議讀者試一下?lián)Q成
ASSERT_宏的不同之處。 每個(gè)測(cè)試宏還可以使用
<<運(yùn)算符在測(cè)試失敗時(shí)輸出自定義信息,如:
- ASSERT_EQ(M[i], N[j]) << "i = " << i << ", j = " << j;
編譯命令行中,gtest_mt.lib和gtest_main_mt.lib就是前面使用VC項(xiàng)目文件生成的靜態(tài)庫(kù)。有意思的是,測(cè)試代碼不需要注冊(cè)測(cè)試用例,也不需要定義
main函數(shù),這是gtest通過(guò)后一個(gè)靜態(tài)庫(kù)自動(dòng)完成的,它的實(shí)現(xiàn)代碼如下:
- // gtest-main.cc
- int main(int argc, char **argv) {
- std::cout << "Running main() from gtest_main.cc\n";
- testing::InitGoogleTest(&argc, argv);
- return RUN_ALL_TESTS();
- }
其中,函數(shù)
InitGoogleTest負(fù)責(zé)注冊(cè)需要運(yùn)行的所有測(cè)試用例,宏
RUN_ALL_TEST負(fù)責(zé)執(zhí)行所有測(cè)試,如果全部成功則返回0,否則返回1。當(dāng)然,我們也可以?xún)H鏈接gtest_mt.lib,自己提供
main函數(shù)。
二、測(cè)試固件很多時(shí)候,我們想在不同的測(cè)試執(zhí)行前創(chuàng)建相同的配置環(huán)境,在測(cè)試執(zhí)行結(jié)束后執(zhí)行相應(yīng)的清理工作,測(cè)試固件(Test Fixture)為這種需求提供了方便?!癋ixture”是一個(gè)漢語(yǔ)中不易直接對(duì)應(yīng)的詞,《美國(guó)傳統(tǒng)詞典》對(duì)它的解釋是“(作為附屬物的)固定裝置;被固定的狀態(tài)”。在單元測(cè)試中,F(xiàn)ixture的作用是為測(cè)試創(chuàng)建輔助性的上下文環(huán)境,實(shí)現(xiàn)測(cè)試的初始化和終結(jié)與測(cè)試過(guò)程本身的分離,便于不同測(cè)試使用相同代碼來(lái)搭建固定的配置環(huán)境。用體操比賽的說(shuō)法,測(cè)試過(guò)程體現(xiàn)了特定測(cè)試的自選動(dòng)作,測(cè)試固件則體現(xiàn)了對(duì)一系列測(cè)試(在開(kāi)始和結(jié)束時(shí))的規(guī)定動(dòng)作。有些講單元測(cè)試的書(shū)籍直接把測(cè)試固件稱(chēng)為Scaffolding(腳手架)。 使用測(cè)試固件比單純調(diào)用
TEST宏稍微麻煩一些:
1. 從gtest的
testing::Test類(lèi)派生一個(gè)類(lèi),用
public或
protected定義以下所有成員。
2. (可選)建立環(huán)境:使用默認(rèn)構(gòu)造函數(shù),或定義一個(gè)虛成員函數(shù)
virtual void SetUp()。
3. (可選)銷(xiāo)毀環(huán)境:使用析構(gòu)函數(shù),或定義一個(gè)虛成員函數(shù)
virtual void TearDown()。
4. 用
TEST_F定義測(cè)試,寫(xiě)法與
TEST相同,但測(cè)試用例名必須為上面定義的類(lèi)名。 每個(gè)帶固件的測(cè)試的執(zhí)行順序是:
1. 調(diào)用默認(rèn)構(gòu)造函數(shù)創(chuàng)建一個(gè)新的帶固件對(duì)象。
2. 立即調(diào)用
SetUp函數(shù)。
3. 運(yùn)行
TEST_F體。
4. 立即調(diào)用
TearDown函數(shù)。
5. 調(diào)用析構(gòu)函數(shù)銷(xiāo)毀類(lèi)對(duì)象。
從gtest的實(shí)現(xiàn)代碼可以看到,
TEST_F又從用戶(hù)定義的類(lèi)自動(dòng)派生了一個(gè)類(lèi),因此要求
public或
protected的訪問(wèn)權(quán)限;大括號(hào)里的內(nèi)容被擴(kuò)展成一個(gè)名為
TestBody的虛成員函數(shù)的函數(shù)體,因此可以在其中直接訪問(wèn)成員變量和成員函數(shù)。其實(shí)
TEST也采用了相同的實(shí)現(xiàn)機(jī)制,只是它直接從gtest的
testing::Test自動(dòng)派生類(lèi),所以可以指定任意用例名。
testing::Test類(lèi)的
SetUp和
TearDown都是空函數(shù),所以它只執(zhí)行測(cè)試步驟,沒(méi)有環(huán)境的創(chuàng)建和銷(xiāo)毀。 借用上面Add函數(shù)寫(xiě)個(gè)固件測(cè)試的例子:
- // add_unittest2.cpp
- #include "add.h"
- #include
- #include
- class AddTest: public testing::Test
- {
- public:
- virtual void SetUp() { puts("SetUp()"); }
- virtual void TearDown() { puts("TearDown()"); }
- };
- TEST_F(AddTest, 正數(shù)) {
- ASSERT_GT(Add(1,2), 3); // 故意的
- ASSERT_EQ(Add(4,5), 6); // 也是故意的
- }
編譯運(yùn)行效果如下: 必須強(qiáng)調(diào),每個(gè)
TEST_F開(kāi)始都創(chuàng)建了一個(gè)新的帶固件對(duì)象,因此每個(gè)測(cè)試都使用獨(dú)立的完全相同的初始環(huán)境,各測(cè)試可以按任意順序執(zhí)行(參見(jiàn)--gtest_shuffle命令行選項(xiàng))。但在某些情況下,我們可能需要在各個(gè)測(cè)試間共享一個(gè)相同的環(huán)境來(lái)保存和傳遞狀態(tài),或者環(huán)境的狀態(tài)是只讀的,可以只初始化一次,再或者創(chuàng)建環(huán)境的過(guò)程開(kāi)銷(xiāo)很高,要求只初始化一次。共享某個(gè)固件環(huán)境的所有測(cè)試合稱(chēng)為一個(gè)“測(cè)試套件”(Test Suite),gtest中利用靜態(tài)成員變量和靜態(tài)成員函數(shù)實(shí)現(xiàn)這個(gè)概念:
1. (可選)在
testing::Test的派生類(lèi)中,定義若干靜態(tài)成員變量來(lái)維護(hù)套件的狀態(tài)。
2. (可選)建立共享環(huán)境:定義一個(gè)靜態(tài)成員函數(shù)
static void SetUpTestCase()。
3. (可選)銷(xiāo)毀共享環(huán)境:定義一個(gè)靜態(tài)成員函數(shù)
static void TearDownCase()。 另外,還可以使用gtest的
Environment類(lèi)來(lái)建立和銷(xiāo)毀所有測(cè)試共用的全局環(huán)境(對(duì)應(yīng)于上圖顯示的“Global test environment set-up”和“Global test environment tear-down”):
- class Environment {
- public:
- virtual ~Environment() {}
- virtual void SetUp() {}
- virtual void TearDown() {}
- };
gtest文檔建議測(cè)試程序自己定義
main函數(shù)并在其中創(chuàng)建和注冊(cè)全局環(huán)境對(duì)象:
- Environment* AddGlobalTestEnvironment(Environment* env);
三、異常測(cè)試C程序中要返回出錯(cuò)信息,可以利用特定的函數(shù)返回值、函數(shù)的輸出(outbound)參數(shù)、或者設(shè)置全局變量(如C標(biāo)準(zhǔn)庫(kù)定義的
errno,Windows API中的“上次錯(cuò)誤”(last error)代碼,Winsock中與每個(gè)socket相關(guān)聯(lián)的錯(cuò)誤代碼)。C++程序常用異常(exception)來(lái)返回出錯(cuò)信息,gtest為異常測(cè)試提供了專(zhuān)用的測(cè)試宏:
| ASSERT 宏 | EXPECT 宏 | 功能 |
| ASSERT_NO_THROW | EXPECT_NO_THROW | 不拋出異常,參數(shù)為 (statement) |
| ASSERT_ANY_THROW | EXPECT_ANY_THROW | 拋出異常,參數(shù)為 (statement) |
| ASSERT_THROW | EXPECT_THROW | 拋出特定類(lèi)型的異常,參數(shù)為 (statement, type) |
需要注意,這些測(cè)試宏都接受C/C++語(yǔ)句作為參數(shù),所以既可以像前面那樣傳遞表達(dá)式,也可以傳遞用大括號(hào)包起來(lái)的代碼塊。 借助下面的被測(cè)函數(shù):
- // divide.h
- #pragma once
- #include
- int divide(int dividend, int divisor) {
- if(!divisor) {
- throw std::length_error("can't be divided by 0"); // 故意的
- }
- return dividend / divisor;
- }
測(cè)試程序如下:
- // divide-unittest.cpp
- #include
- #include "./divide.h"
- TEST(Divide, ByZero) {
- EXPECT_NO_THROW(divide(-1, 2));
- EXPECT_ANY_THROW({
- int k = 0;
- divide(k, k);
- });
- EXPECT_THROW(divide(100000, 0), std::invalid_argument);
- }
編譯運(yùn)行效果如下
容易想到,gtest的這些異常測(cè)試宏是用C++的
try ... catch語(yǔ)句來(lái)實(shí)現(xiàn)的:
- try {
- statement;
- }
- catch(type const&) {
- // throw
- }
- catch(...) {
- // any throw
- }
- // no throw
如果把上圖中Visual C++的編譯選項(xiàng)/EHsc換成/EHa,
try ... catch就可以同時(shí)支持C++風(fēng)格的異常和Windows系統(tǒng)的結(jié)構(gòu)化異常(SEH)。這樣,即使刪掉
divide函數(shù)里的
if判斷,測(cè)試代碼的
EXPECT_ANY_THROW宏也會(huì)成功捕獲異常。 遺憾的是,目前僅使用這些測(cè)試宏無(wú)法得到獲得被拋出異常的詳細(xì)信息(如
divide函數(shù)中的報(bào)錯(cuò)文本),這和gtest自身不愿意使用C++異常有關(guān)。
四、值參數(shù)化測(cè)試有些時(shí)候,我們需要對(duì)代碼實(shí)現(xiàn)的功能使用不同的參數(shù)進(jìn)行測(cè)試,比如使用大量隨機(jī)值來(lái)檢驗(yàn)算法實(shí)現(xiàn)的正確性,或者比較同一個(gè)接口的不同實(shí)現(xiàn)之間的差別。gtest把“集中輸入測(cè)試參數(shù)”的需求抽象出來(lái)提供支持,稱(chēng)為值參數(shù)化測(cè)試(Value Parameterized Test)。 值參數(shù)化測(cè)試包括4個(gè)步驟:
1. 從gtest的
TestWithParam模板類(lèi)派生一個(gè)類(lèi)(記為
C),模板參數(shù)為需要輸入的測(cè)試參數(shù)的類(lèi)型。由于
TestWithParam本身是從
Test派生的,所以
C就成了一個(gè)測(cè)試固件類(lèi)。
2. 在
C中,可以實(shí)現(xiàn)諸如
SetUp、
TearDown等方法。特別地,測(cè)試參數(shù)由
TestWithParam實(shí)現(xiàn)的
GetParam()方法依次返回。
3. 使用
TEST_P(而不是
TEST_F)定義測(cè)試。
4. 使用
INSTANTIATE_TEST_CASE_P宏集中輸入測(cè)試參數(shù),它接受3個(gè)參數(shù):任意的文本前綴,測(cè)試類(lèi)名(這里即為
C),以及測(cè)試參數(shù)值序列。gtest框架依次使用這些參數(shù)值生成測(cè)試固件類(lèi)實(shí)例,并執(zhí)行用戶(hù)定義的測(cè)試。 gtest提供了專(zhuān)門(mén)的模板函數(shù)來(lái)生成參數(shù)值序列,如下表所示:
| 參數(shù)值序列生成函數(shù) | 含義 |
| Bool() | 生成序列 {false, true} |
| Range(begin, end[, step]) | 生成序列 {begin, begin+step, begin+2*step, ...} (不含 end), step默認(rèn)為1 |
| Values(v1, v2, ..., vN) | 生成序列 {v1, v2, ..., vN} |
| ValuesIn(container), ValuesIn(iter1, iter2) | 枚舉STL container,或枚舉迭代器范圍 [iter1, iter2) |
| Combine(g1, g2, ..., gN) | 生成 g1, g2, ..., gN的笛卡爾積,其中g(shù)1, g2, ..., gN均為參數(shù)值序列生成函數(shù)(要求C++0x的 |
寫(xiě)個(gè)小程序試一下。假設(shè)我們實(shí)現(xiàn)了一種快速累加算法,希望使用另一種直觀算法進(jìn)行正確性校驗(yàn)。算法實(shí)現(xiàn)和測(cè)試代碼如下
- // addupto.h
- #pragma once
- inline unsigned NaiveAddUpTo(unsigned n) {
- unsigned sum = 0;
- for(unsigned i = 1; i <= n; ++i) sum += i;
- return sum;
- }
- inline unsigned FastAddUpTo(unsigned n) {
- return n*(n+1)/2;
- }
測(cè)試程序如下:
- // addupto_test.cpp
- #include
- #include "addupto.h"
- class AddUpToTest : public testing::TestWithParam
- {
- public:
- AddUpToTest() { n_ = GetParam(); }
- protected:
- unsigned n_;
- };
- TEST_P(AddUpToTest, Calibration) {
- EXPECT_EQ(NaiveAddUpTo(n_), FastAddUpTo(n_));
- }
- INSTANTIATE_TEST_CASE_P(
- NaiveAndFast, // prefix
- AddUpToTest, // test case name
- testing::Range(1u, 1000u) // parameters
- );
注意
TestWithParam的模板參數(shù)設(shè)置為
unsigned類(lèi)型,而在代碼倒數(shù)第2行,兩個(gè)常量值都加了
u后綴來(lái)指定為
unsigned類(lèi)型。熟悉C++的讀者應(yīng)該知道,模板函數(shù)在進(jìn)行類(lèi)型推斷(deduction)時(shí)匹配相當(dāng)嚴(yán)格,不像普通函數(shù)那樣允許類(lèi)型提升(promotion)。如果上面省略
u后綴,就會(huì)造成編譯錯(cuò)誤。當(dāng)然還可以顯式指定模板參數(shù):
testing::Range
作者簡(jiǎn)介 楊玚,1980年生,2009年畢業(yè)于中國(guó)科學(xué)技術(shù)大學(xué),獲博士學(xué)位。2009年8月加入中國(guó)軟件評(píng)測(cè)中心重大專(zhuān)項(xiàng)測(cè)試部,任開(kāi)發(fā)測(cè)試工程師,負(fù)責(zé)“軟件測(cè)試能力優(yōu)化升級(jí)” 項(xiàng)目工具研發(fā)。關(guān)注領(lǐng)域?yàn)榫W(wǎng)絡(luò)信息安全。
文章標(biāo)題:深入理解gtestC/C++單元測(cè)試經(jīng)驗(yàn)談
網(wǎng)頁(yè)地址:http://m.fisionsoft.com.cn/article/cciideo.html


咨詢(xún)
建站咨詢(xún)
