日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程语言 > c/c++ >内容正文

c/c++

深入理解gtest C/C++单元测试经验谈

發(fā)布時間:2024/2/28 c/c++ 36 豆豆
生活随笔 收集整理的這篇文章主要介紹了 深入理解gtest C/C++单元测试经验谈 小編覺得挺不錯的,現(xiàn)在分享給大家,幫大家做個參考.

深入理解gtest C/C++單元測試經(jīng)驗談

2011-08-22 13:57 楊玚 51CTO?字號:T?|?T

本文基于筆者的實際開發(fā)經(jīng)驗,言簡意賅地講解了C/C++單元測試框架gtest的主要使用方法和注意事項,并設(shè)計了若干可編譯的精簡示例,給出了運行效果圖。既可以用作gtest的入門教程,也適合作為工作中的快速參考。

ADWOT2015 互聯(lián)網(wǎng)運維與開發(fā)者大會熱銷搶票

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

優(yōu)秀的C/C++單元測試框架并不算少,相比之下gtest仍具有明顯優(yōu)勢。與CppUnit比,gtest需要使用的頭文件和函數(shù)宏更集中,并支持測試用例的自動注冊。與CxxUnit比,gtest不要求Python等外部工具的存在。與Boost.Test比,gtest更簡潔容易上手,實用性也并不遜色。Wikipedia給出了各種編程語言的單元測試框架列表(http://en.wikipedia.org/wiki/List_of_unit_testing_frameworks)。

一、基本用法

gtest當前的版本是1.5.0,如果使用Visual C++編譯,要求編譯器版本不低于7.1(Visual C++ 2003)。如下圖所示,它的msvc文件夾包含Visual C++工程和項目文件,samples文件夾包含10個使用范例。

一般情況下,我們的單元測試代碼只需要包含頭文件gtest.h。gtest中常用的所有結(jié)構(gòu)體、類、函數(shù)、常量等,都通過命名空間testing訪問,不過gtest已經(jīng)把最簡單常用的單元測試功能包裝成了一些帶參數(shù)宏,因此在簡單的測試中常常可以忽略命名空間的存在。

按照gtest的叫法,宏TEST為特定的測試用例(Test Case)定義了一個可執(zhí)行的測試(Test)。它接受用戶指定的測試用例名(一般取被測對象名)和測試名作為參數(shù),并劃出了一個作用域供填充測試宏語句和普通的C++代碼。一系列TEST的集合就構(gòu)成一個簡單的測試程序。

常用的測試宏如下表所示。以ASSERT_開頭和以EXPECT_開頭的宏的區(qū)別是,前者在測試失敗時會給出報告并立即終止測試程序,后者在報告后繼續(xù)執(zhí)行測試程序

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

單精度浮點值相等

ASSERT_DOUBLE_EQ

EXPECT_DOUBLE_EQ

雙精度浮點值相等

ASSERT_NEAR

EXPECT_NEAR

浮點值接近(第3個參數(shù)為誤差閾值)

ASSERT_STREQ

EXPECT_STREQ

C字符串相等

ASSERT_STRNE

EXPECT_STRNE

C字符串不等

ASSERT_STRCASEEQ

EXPECT_STRCASEEQ

C字符串相等(忽略大小寫)

ASSERT_STRCASENE

EXPECT_STRCASENE

C字符串不等(忽略大小寫)

ASSERT_PRED1

EXPECT_PRED1

自定義謂詞函數(shù),(pred, arg1)(還有_PRED2, ...,?_PRED5

?

寫個簡單的測試試一下。假設(shè)我們實現(xiàn)了一個加法函數(shù):

1.? //?add.h ?

2.? #pragma?once ?

3.? inline?int?Add(int?i,?int?j)?{?return?i+j;?}?

對應(yīng)的單元測試程序可以這樣寫:

1.? //?add_unittest.cpp ?

2.? #include?"add.h" ?

3.? #include?<gtest/gtest.h> ?

4.? ? ?

5.? TEST(Add,?負數(shù))?{ ?

6.? ?EXPECT_EQ(Add(-1,-2),?-3); ?

7.? ?EXPECT_GT(Add(-4,-5),?-6);?//?故意的?

8.? } ?

9.? ? ?

10. TEST(Add,?正數(shù))?{ ?

11. ?EXPECT_EQ(Add(1,2),?3); ?

12. ?EXPECT_GT(Add(4,5),?6); ?

13. }?

代碼中,測試用例Add包含兩個測試,正數(shù)和負數(shù)(這里利用了Visual C++ 2005以上允許標識符包含Unicode字符的特性)。編譯運行效果如下:

控制臺界面中,通過的測試用綠色表示,失敗的測試用紅色表示。雙橫線分隔了不同的測試用例,其中包含的每個測試的啟動與結(jié)果用單橫線和RUN ... OK或RUN ... FAILED標出。失敗的測試會打印出代碼行和原因,測試程序最后為所有用例和測試顯示統(tǒng)計結(jié)果。建議讀者試一下?lián)Q成ASSERT_宏的不同之處。

每個測試宏還可以使用<<運算符在測試失敗時輸出自定義信息,如:

1.? ASSERT_EQ(M[i],?N[j])?<<?"i?=?"?<<?i?<<?",?j?=?"?<<?j;?

編譯命令行中,gtest_mt.lib和gtest_main_mt.lib就是前面使用VC項目文件生成的靜態(tài)庫。有意思的是,測試代碼不需要注冊測試用例,也不需要定義main函數(shù),這是gtest通過后一個靜態(tài)庫自動完成的,它的實現(xiàn)代碼如下:

1.? //?gtest-main.cc ?

2.? int?main(int?argc,?char?**argv)?{ ?

3.? ?std::cout?<<?"Running?main()?from?gtest_main.cc\n"; ?

4.? ?testing::InitGoogleTest(&argc,?argv);?

5.? ?return?RUN_ALL_TESTS(); ?

6.? }?

其中,函數(shù)InitGoogleTest負責注冊需要運行的所有測試用例宏RUN_ALL_TEST負責執(zhí)行所有測試,如果全部成功則返回0,否則返回1。當然,我們也可以僅鏈接gtest_mt.lib,自己提供main函數(shù)。

二、測試固件

很多時候,我們想在不同的測試執(zhí)行前創(chuàng)建相同的配置環(huán)境,在測試執(zhí)行結(jié)束后執(zhí)行相應(yīng)的清理工作,測試固件(Test Fixture)為這種需求提供了方便。“Fixture”是一個漢語中不易直接對應(yīng)的詞,《美國傳統(tǒng)詞典》對它的解釋是“(作為附屬物的)固定裝置;被固定的狀態(tài)”。在單元測試中,Fixture的作用是為測試創(chuàng)建輔助性的上下文環(huán)境,實現(xiàn)測試的初始化和終結(jié)與測試過程本身的分離,便于不同測試使用相同代碼來搭建固定的配置環(huán)境。用體操比賽的說法,測試過程體現(xiàn)了特定測試的自選動作,測試固件則體現(xiàn)了對一系列測試(在開始和結(jié)束時)的規(guī)定動作。有些講單元測試的書籍直接把測試固件稱為Scaffolding(腳手架)。

使用測試固件比單純調(diào)用TEST宏稍微麻煩一些:

1.?????????從gtest的testing::Test類派生一個類,用public或protected定義以下所有成員。

2.?????????(可選)建立環(huán)境:使用默認構(gòu)造函數(shù),或定義一個虛成員函數(shù)virtualvoid SetUp()

3.?????????(可選)銷毀環(huán)境:使用析構(gòu)函數(shù),或定義一個虛成員函數(shù)virtual void TearDown()

4.?????????用TEST_F定義測試,寫法與TEST相同,但測試用例名必須為上面定義的類名。

每個帶固件的測試的執(zhí)行順序是:

1.?????????調(diào)用默認構(gòu)造函數(shù)創(chuàng)建一個新的帶固件對象。

2.?????????立即調(diào)用SetUp函數(shù)。

3.?????????運行TEST_F體。

4.?????????立即調(diào)用TearDown函數(shù)。

5.?????????調(diào)用析構(gòu)函數(shù)銷毀類對象。

從gtest的實現(xiàn)代碼可以看到,TEST_F又從用戶定義的類自動派生了一個類,因此要求public或protected的訪問權(quán)限;大括號里的內(nèi)容被擴展成一個名為TestBody的虛成員函數(shù)的函數(shù)體,因此可以在其中直接訪問成員變量和成員函數(shù)。其實TEST也采用了相同的實現(xiàn)機制,只是它直接從gtest的testing::Test自動派生類,所以可以指定任意用例名。testing::Test類的SetUp和TearDown都是空函數(shù),所以它只執(zhí)行測試步驟,沒有環(huán)境的創(chuàng)建和銷毀。

借用上面Add函數(shù)寫個固件測試的例子:

1.? //?add_unittest2.cpp ?

2.? #include?"add.h" ?

3.? #include?<stdio.h> ?

4.? #include?<gtest/gtest.h> ?

5.? ? ?

6.? class?AddTest:?public?testing::Test ?

7.? { ?

8.? public: ?

9.? ?virtual?void?SetUp()????{?puts("SetUp()");?} ?

10. ?virtual?void?TearDown()?{?puts("TearDown()");?} ?

11. }; ?

12. ? ?

13. TEST_F(AddTest,?正數(shù))?{ ?

14. ?ASSERT_GT(Add(1,2),?3);?//?故意的?

15. ?ASSERT_EQ(Add(4,5),?6);?//?也是故意的?

16. }?

編譯運行效果如下:

必須強調(diào),每個TEST_F開始都創(chuàng)建了一個新的帶固件對象,因此每個測試都使用獨立的完全相同的初始環(huán)境,各測試可以按任意順序執(zhí)行(參見--gtest_shuffle命令行選項)。但在某些情況下,我們可能需要在各個測試間共享一個相同的環(huán)境來保存和傳遞狀態(tài),或者環(huán)境的狀態(tài)是只讀的,可以只初始化一次,再或者創(chuàng)建環(huán)境的過程開銷很高,要求只初始化一次。共享某個固件環(huán)境的所有測試合稱為一個“測試套件”(Test Suite),gtest中利用靜態(tài)成員變量和靜態(tài)成員函數(shù)實現(xiàn)這個概念:

1.?????????(可選)在testing::Test的派生類中,定義若干靜態(tài)成員變量來維護套件的狀態(tài)。

2.?????????(可選)建立共享環(huán)境:定義一個靜態(tài)成員函數(shù)staticvoid SetUpTestCase()

3.?????????(可選)銷毀共享環(huán)境:定義一個靜態(tài)成員函數(shù)staticvoid TearDownCase()

另外,還可以使用gtest的Environment類來建立和銷毀所有測試共用的全局環(huán)境(對應(yīng)于上圖顯示的“Globaltest environment set-up”和“Global test environmenttear-down”):

1.? class?Environment?{ ?

2.? ?public: ?

3.? ?virtual?~Environment()?{} ?

4.? ?virtual?void?SetUp()?{} ?

5.? ?virtual?void?TearDown()?{} ?

6.? };?

gtest文檔建議測試程序自己定義main函數(shù)并在其中創(chuàng)建和注冊全局環(huán)境對象:

1.? Environment*?AddGlobalTestEnvironment(Environment*?env);?

三、異常測試

C程序中要返回出錯信息,可以利用特定的函數(shù)返回值、函數(shù)的輸出(outbound)參數(shù)、或者設(shè)置全局變量(如C標準庫定義的errno,WindowsAPI中的“上次錯誤”(last error)代碼,Winsock中與每個socket相關(guān)聯(lián)的錯誤代碼)。C++程序常用異常(exception)來返回出錯信息,gtest為異常測試提供了專用的測試宏:

ASSERT

EXPECT

功能

ASSERT_NO_THROW

EXPECT_NO_THROW

不拋出異常,參數(shù)為(statement)

ASSERT_ANY_THROW

EXPECT_ANY_THROW

拋出異常,參數(shù)為(statement)

ASSERT_THROW

EXPECT_THROW

拋出特定類型的異常,參數(shù)為(statement, type)

?

需要注意,這些測試宏都接受C/C++語句作為參數(shù),所以既可以像前面那樣傳遞表達式,也可以傳遞用大括號包起來的代碼塊。

借助下面的被測函數(shù):

1.? //?divide.h ?

2.? #pragma?once ?

3.? #include?<stdexcept> ?

4.? ? ?

5.? int?divide(int?dividend,?int?divisor)?{ ?

6.? ?if(!divisor)?{ ?

7.? ????throw?std::length_error("can't?be?divided?by?0");?//?故意的?

8.? ?} ?

9.? ?return?dividend?/?divisor; ?

10. }?

測試程序如下:

1.? //?divide-unittest.cpp ?

2.? #include?<gtest/gtest.h> ?

3.? #include?"./divide.h" ?

4.? ? ?

5.? TEST(Divide,?ByZero)?{ ?

6.? ?EXPECT_NO_THROW(divide(-1,?2));?

7.? ? ?

8.? ?EXPECT_ANY_THROW({ ?

9.? ????int?k?=?0; ?

10. ????divide(k,?k);?

11. ?}); ?

12. ? ?

13. ?EXPECT_THROW(divide(100000,?0),?std::invalid_argument);?

14. }?

編譯運行效果如下

容易想到,gtest的這些異常測試宏是用C++的try ... catch語句來實現(xiàn)的:

1.? try?{ ?

2.? ?statement; ?

3.? } ?

4.? catch(type?const&)?{ ?

5.? ?//?throw ?

6.? } ?

7.? catch(...)?{ ?

8.? ?//?any?throw ?

9.? } ?

10. //?no?throw?

如果把上圖中Visual C++的編譯選項/EHsc換成/EHa,try ...catch就可以同時支持C++風(fēng)格的異常和Windows系統(tǒng)的結(jié)構(gòu)化異常(SEH)。這樣,即使刪掉divide函數(shù)里的if判斷,測試代碼的EXPECT_ANY_THROW宏也會成功捕獲異常。

遺憾的是,目前僅使用這些測試宏無法得到獲得被拋出異常的詳細信息(如divide函數(shù)中的報錯文本),這和gtest自身不愿意使用C++異常有關(guān)。

四、值參數(shù)化測試

有些時候,我們需要對代碼實現(xiàn)的功能使用不同的參數(shù)進行測試,比如使用大量隨機值來檢驗算法實現(xiàn)的正確性,或者比較同一個接口的不同實現(xiàn)之間的差別。gtest把“集中輸入測試參數(shù)”的需求抽象出來提供支持,稱為值參數(shù)化測試(ValueParameterized Test)。

值參數(shù)化測試包括4個步驟:

1.?????????從gtest的TestWithParam模板類派生一個類(記為C),模板參數(shù)為需要輸入的測試參數(shù)的類型。由于TestWithParam本身是從Test派生的,所以C就成了一個測試固件類。

2.?????????在C中,可以實現(xiàn)諸如SetUp、TearDown等方法。特別地,測試參數(shù)由TestWithParam實現(xiàn)的GetParam()方法依次返回。

3.?????????使用TEST_P(而不是TEST_F)定義測試。

4.?????????使用INSTANTIATE_TEST_CASE_P宏集中輸入測試參數(shù),它接受3個參數(shù):任意的文本前綴,測試類名(這里即為C),以及測試參數(shù)值序列。gtest框架依次使用這些參數(shù)值生成測試固件類實例,并執(zhí)行用戶定義的測試。

gtest提供了專門的模板函數(shù)來生成參數(shù)值序列,如下表所示:

參數(shù)值序列生成函數(shù)

含義

Bool()

生成序列{false, true}

Range(begin, end[, step])

生成序列{begin, begin+step, begin+2*step,?...}?(不含end)step默認為1

Values(v1, v2,?...,?vN)

生成序列{v1, v2,?...,?vN}

ValuesIn(container),?ValuesIn(iter1, iter2)

枚舉STL?container,或枚舉迭代器范圍[iter1, iter2)

Combine(g1, g2,?...,?gN)

生成g1,?g2, ...,?gN的笛卡爾積,其中g1,?g2, ...,?gN均為參數(shù)值序列生成函數(shù)(要求C++0x的<tr1/tuple>)

?

寫個小程序試一下。假設(shè)我們實現(xiàn)了一種快速累加算法,希望使用另一種直觀算法進行正確性校驗。算法實現(xiàn)和測試代碼如下

1.? //?addupto.h ?

2.? ? ?

3.? #pragma?once ?

4.? ? ?

5.? inline?unsigned?NaiveAddUpTo(unsigned?n)?{?

6.? ????unsigned?sum?=?0;?

7.? ????for(unsigned?i?=?1;?i?<=?n;?++i)?sum?+=?i;?

8.? ????return?sum; ?

9.? } ?

10. ? ?

11. inline?unsigned?FastAddUpTo(unsigned?n)?{?

12. ????return?n*(n+1)/2; ?

13. }?

測試程序如下:

1.? //?addupto_test.cpp ?

2.? ? ?

3.? #include?<gtest/gtest.h> ?

4.? #include?"addupto.h" ?

5.? ? ?

6.? class?AddUpToTest?:?public?testing::TestWithParam<unsigned>?

7.? { ?

8.? public: ?

9.? ????AddUpToTest()?{?n_?=?GetParam();?}?

10. protected: ?

11. ????unsigned?n_;?

12. }; ?

13. ? ?

14. TEST_P(AddUpToTest,?Calibration)?{?

15. ????EXPECT_EQ(NaiveAddUpTo(n_),?FastAddUpTo(n_));?

16. } ?

17. ? ?

18. INSTANTIATE_TEST_CASE_P( ?

19. ????NaiveAndFast,?//?prefix ?

20. ????AddUpToTest,???//?test?case?name ?

21. ????testing::Range(1u,?1000u)?//?parameters ?

22. );?

?

注意TestWithParam的模板參數(shù)設(shè)置為unsigned類型,而在代碼倒數(shù)第2行,兩個常量值都加了u后綴來指定為unsigned類型。熟悉C++的讀者應(yīng)該知道,模板函數(shù)在進行類型推斷(deduction)時匹配相當嚴格,不像普通函數(shù)那樣允許類型提升(promotion)。如果上面省略u后綴,就會造成編譯錯誤。當然還可以顯式指定模板參數(shù):testing::Range<unsigned>(1,1000)。

運行效果如下,這里省略了開頭的大部分輸出(命令行窗口設(shè)置的緩沖區(qū)高度為3000行)。

作者簡介

楊玚,1980年生,2009年畢業(yè)于中國科學(xué)技術(shù)大學(xué),獲博士學(xué)位。2009年8月加入中國軟件評測中心重大專項測試部,任開發(fā)測試工程師,負責“軟件測試能力優(yōu)化升級” 項目工具研發(fā)。關(guān)注領(lǐng)域為網(wǎng)絡(luò)信息安全。

【編輯推薦】

1. 原生代碼卷土重來C++欲東山再起

2. C++程序運行時的異常處理

3. 介紹C++中堆內(nèi)存的概念和操作方法

4. 舉例介紹C++高質(zhì)量編程

5. 淺談C++指針直接調(diào)用類成員函數(shù)

6. 詳細介紹C++中指針的使用

?

總結(jié)

以上是生活随笔為你收集整理的深入理解gtest C/C++单元测试经验谈的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網(wǎng)站內(nèi)容還不錯,歡迎將生活随笔推薦給好友。