Google 單元測試框架

Gtest Github
使用 gtest(gmock) 方便我們編寫組織 c++ 單元測試。

編譯 lib

到 github 拉取代碼或者下載某個版本的 zip 包到本地目錄嗡善,參考 gtest 中的 README.md 如何編譯庫和編譯自己的代碼碱璃,下面簡單介紹下編譯方法

手動編譯

$ g++ -isystem ${GTEST_DIR}/include -I${GTEST_DIR} \
    -pthread -c ${GTEST_DIR}/src/gtest-all.cc
$ ar -rv libgtest.a gtest-all.o

cmake 編譯

gtest 已經(jīng)提供了 cmakelist,可以直接使用cmake 生成 makefile勾效, 編譯庫和 sample

$ mkdir mybuild       # Create a directory to hold the build output.
$ cd mybuild
$ cmake ${GTEST_DIR}  # Generate native build scripts.
$ make

然后就可以在編譯自己的測試程序時鏈接 gtest 了嘹悼。

$ g++ -isystem ${GTEST_DIR}/include -pthread path/to/your_test.cc libgtest.a -o your_test

跟多詳細內(nèi)容參考 readme 和代碼中提供的例子(samples ; make 目錄下),比如如何解決重復定義宏等問題层宫。

gtest 測試程序

通過 編程參考源碼中 sample 目錄下的示例杨伙,我們可以很快上手 gtest。gtest 定義了宏供我們寫斷言語句萌腿,一個或者多個斷言組成我們的測試用例 case限匣,多個測試用例有時候需要共享一些通用對象,可以把這些用例放在同一個 fixture 中毁菱。

斷言和 case

gtest 斷言提供兩個版本

  • ASSERT_* 版本斷言米死,在同一個 case 中(測試函數(shù))中,ASSERT_* 失敗就會終止當前用例贮庞,開始其他 case 峦筒;
  • EXPECT_*版本,當斷言失敗時窗慎,會報錯物喷,但是會繼續(xù)執(zhí)行剩余語句。

完整的 宏定義遮斥, 或見源碼 include/gtest/gtest.h

使用哪種語句斷言取決自己用例場景峦失,如當前語句失敗時后續(xù)語句沒有繼續(xù)執(zhí)行意義,則可以直接使用 ASSERT 終止伏伐,否則使用 EXPECT 可以發(fā)現(xiàn)更多錯誤宠进。

如果用例之間不需要什么公用資源,相互獨立藐翎,可以使用如下方式定義每一個 case

TEST(套件名材蹬,用例名)
{
    //套件名和用例名自定義
    //斷言語句
    //如一般的c++ 函數(shù)实幕,不 return value 
}

進入目錄 sample 中, 以 sample1_unittest.cc 為例子

#include "sample1.h"  // 測試對象頭文件,接口
#include "gtest/gtest.h"  // gtest 頭文件

TEST(IsPrimeTest, Negative) {
    EXPECT_FALSE(IsPrime(-1)) << "這樣子失敗時打印自己的信息"; 
    EXPECT_FALSE(IsPrime(-2)); // 如果此斷言失敗堤器,還會繼續(xù)執(zhí)行下一個
    EXPECT_FALSE(IsPrime(INT_MIN));
  }

TEST(IsPrimeTest, Negative) {
    EXPECT_FALSE(IsPrime(-1));
    ASSERT_FALSE(IsPrime(-2)); // 如果此斷言失敗昆庇,下一條不執(zhí)行,這個case 結(jié)束
    EXPECT_FALSE(IsPrime(INT_MIN));
  }

編譯修改的測試代碼闸溃,其中 libgtest.a 是 gtest 的庫整吆。

g++ -isystem ../include/ ./sample1.cc  ./sample1_unittest.cc -pthread ../libgtest.a  ../libgtest_main.a 

鏈接 libgtest_main.a 是為了使用 src/gtest_main.cc中定義 main 函數(shù),執(zhí)行所用測試用例辉川,否者表蝙,也可以自己定義 main。

#include <stdio.h>
#include "gtest/gtest.h"
int main(int argc, char **argv) {
  printf("Running main() from gtest_main.cc\n");
  testing::InitGoogleTest(&argc, argv);
  return RUN_ALL_TESTS();
}

編譯后執(zhí)行輸出 bin 直接運行便運行所有用例乓旗,可以使用 -h 查看可選的執(zhí)行參數(shù)府蛇,如--gtest_filter=IsPrimeTest.Negative 指定執(zhí)行 套件和 case ; --gtest_output=xml[:DIRECTORY_PATH/|:FILE_PATH]生成報告等。

Fixture

多個用例需要使用相同的數(shù)據(jù)屿愚,每次都在用例中準備顯得很重復麻煩汇跨,這時候,可以使用 Fixture 來構(gòu)建用例妆距,使多個用例共用相同的數(shù)據(jù)對象配置穷遂。
使用 Fiture 第一部是定義一個繼承自::testing::Test 的類,在類中定義初始化函數(shù)娱据,清理函數(shù)和聲明需要使用的對象蚪黑。

class QueueTest : public ::testing::Test { // 定義套件名,繼承自 Test
 protected:   // 建議中剩,子類可用成員
  //定義setup 函數(shù)祠锣,在每個用例執(zhí)行前調(diào)用
  void SetUp() override {
     q1_.Enqueue(1);
     q2_.Enqueue(2);
     q2_.Enqueue(3);
  }
  // 定義清理函數(shù),在每個用例執(zhí)行后調(diào)用
  // void TearDown() override {}
  // 定義需要用到的變量
  Queue<int> q0_;
  Queue<int> q1_;
  Queue<int> q2_;
};

//寫用例咽安,套件名(上面定義的類名),用例名
TEST_F(QueueTest, IsEmptyInitially) {
  EXPECT_EQ(q0_.size(), 0); //直接使用成員變量
}

以上我們定義了一個套件 QueueTest 蓬推, 當我們執(zhí)行該套件用例時妆棒,

  1. gtest 構(gòu)建 QueueTest 實例 qt1;
  2. 調(diào)用 qt1.SetUp() 初始化
  3. 執(zhí)行一個用例
  4. 調(diào)用 qt1.TearDown() 清理
  5. 析構(gòu) qt1 對象
  6. 回到1沸伏,執(zhí)行下一個用例

從步驟可知糕珊,不同用例之間,數(shù)據(jù)實際都是獨占的毅糟,不會相互影響红选。

使用 fixture 編寫用例后,同單獨測試用例 TEST 一樣姆另,需要編寫 main 喇肋,然后編譯連接坟乾,執(zhí)行測試。

使用 gmock

gmock 現(xiàn)在已經(jīng)和入 gtest 的代碼庫, 1.8 和之后的版本直接在 gtest github 主頁中獲取蝶防,低版本仍然在原 github主頁甚侣。

gmock 需要依賴 gtest 使用,在測試中间学,當我們測試的對象需要依賴其他模塊殷费、接口,但是往往受條件限制無法使用真實依賴的對象低葫,通過 mock 對象來模擬我們需要依賴详羡,以協(xié)助測試本模塊,mock 對象具有和真實對象一樣的接口嘿悬,但是我們可以在運行時指定他的行為实柠,如何被使用,使用多少次鹊漠、參數(shù)主到,使用時返回什么等。

編譯

編譯說明
gmock 編譯需要依賴 gtest躯概, 準備好 gtest 和 gmock (同一個版本)后登钥,手動編譯的方法如下:
設置好 gtest 和 gmock 的工程路徑,或者在下面命令中直接替換源路徑娶靡。

g++ -isystem ${GTEST_DIR}/include -I${GTEST_DIR} \
        -isystem ${GMOCK_DIR}/include -I${GMOCK_DIR} \
        -pthread -c ${GTEST_DIR}/src/gtest-all.cc
g++ -isystem ${GTEST_DIR}/include -I${GTEST_DIR} \
         -isystem ${GMOCK_DIR}/include -I${GMOCK_DIR} \
         -pthread -c ${GMOCK_DIR}/src/gmock-all.cc
ar -rv libgmock.a gtest-all.o gmock-all.o

由命令可知牧牢,libgmock.a 包含了 libgtest.a,所有實際編譯測試程序時姿锭,只需要鏈接 libglmock.a 就好了塔鳍。

使用 cmake編譯庫,進入 gmock 目錄(此處 gtest 已經(jīng)準備并且與 gmock 同級目錄)

$ cd ./googlemock/; mkdir build
$ cd ./build; cmake ..
$ make

生成 libgmock.a 庫在 build 目錄下呻此, 同時生成 libgtest.a gtest/ 下轮纫, 與上面手動編譯把 gtest 和 gmock 打在一個 libgmock.a 不同,使用這種編譯程序需要同時指定 鏈接 libgmock.alibgtest.a焚鲜, 否則會報各種 undefine 的錯誤 掌唾。

編譯測試程序 :

g++ -isystem ${GTEST_DIR}/include \
    -isystem ${GMOCK_DIR}/include \
    -pthread path/to/your_test.cc libgmock.a -o your_test 

測試時,我鏈接 cmake 編譯出來的庫時報錯忿磅,查看庫中很多符號沒有糯彬,原因就是 cmake 輸出的 libmock.a 不包含 gtest,需要指定鏈接 libgtest.a

gmock 測試程序

參考 gmock 編程指導codebook

gmock mock 對象葱她,可以定義函數(shù)期望行為撩扒,如被調(diào)用時返回的值,期望被調(diào)用的次數(shù)吨些,參數(shù)等搓谆,如果不滿足就會報錯炒辉。
定義 gmock 對象的基本步驟:

  1. 創(chuàng)建 mock 對象繼承自原對象,并用框架提供的宏 MOCK_METHODn(); (or MOCK_CONST_METHODn(); 描述需要模擬的接口
  2. 寫用例挽拔,在用例中使用宏定義期望接口的行為辆脸,如果定義的行為執(zhí)行用例時不滿足,就會報錯

借用主頁提供的例子改寫螃诅,簡單學習下如何使用 mock

比如你測試的對象依賴的接口定義如下啡氢,

class Turtle {
      public:
      virtual ~Turtle() {}
      virtual void PenUp() = 0;
      virtual void PenDown() = 0;
      virtual void Forward(int distance) = 0;
      virtual void Turn(int degrees) = 0;
      virtual void GoTo(int x, int y) = 0;
      virtual int GetX() const = 0;
      virtual int GetY() const = 0;
 };

此時通過繼承這個對象,定義了 mock 對象术裸,在對象中通過宏描述需要 mock 的接口倘是,這樣,就完成了對象的 mock 操作袭艺。

#include "gmock/gmock.h"
#include "gtest/gtest.h

class MockTurtle: public Turtle {
public:
      // MOCK_METHOD[參數(shù)個數(shù)](接口名搀崭,接口定義格式);
      MOCK_METHOD0(PenUp, void());
      MOCK_METHOD0(PenDown, void());
      MOCK_METHOD1(Forward, void(int distance));
      MOCK_METHOD1(Turn, void(int degrees));
      MOCK_METHOD2(GoTo, void(int x, int y));
      MOCK_CONST_METHOD0(GetX, int());
      MOCK_CONST_METHOD0(GetY, int());
  };

定義了 mock 對象后,就可以在測試用例使用 mock 對象替代原依賴對象猾编,執(zhí)行測試了瘤睹。

  using ::testing::AtLeast;
  TEST(PainterTest, PenDownCall) {
      MockTurtle turtle;
      EXPECT_CALL(turtle, PenDown())
      ┊   .Times(AtLeast(2));
      // 期望這個函數(shù)在本次測試需要至少被調(diào)用2次
      // 否則報錯
      turtle.PenDown();
      turtle.PenDown();
  }
  
  using ::testing::Return;
  TEST(PainterTest, GetX) {
      MockTurtle turtle;
      EXPECT_CALL(turtle, GetX())
      ┊   .Times(4)
      ┊   .WillOnce(Return(100))
      ┊   .WillOnce(Return(150))
      ┊   .WillRepeatedly(Return(200));
      // 期望這個函數(shù)在本次測試需要被調(diào)用4次
      // 否則報錯
      // 第一次調(diào)用返回100, 第二次150答倡,之后都是200
      EXPECT_EQ(turtle.GetX(), 100);
      EXPECT_EQ(turtle.GetX(), 150);
      EXPECT_EQ(turtle.GetX(), 200);
      EXPECT_EQ(turtle.GetX(), 200);
  }
  
  using ::testing::_;
  TEST(PainterTest, GoTo) {
      MockTurtle turtle;
      EXPECT_CALL(turtle, GoTo(_, 100));
      // 期望調(diào)用參數(shù)轰传,第一個任意,第一個必須為 100
      turtle.GoTo(1, 100);
  
      EXPECT_CALL(turtle, GoTo(_, 101));
      turtle.GoTo(2, 101);
  }

gmock 使用宏設置期望是粘性的瘪撇,意思是當我們調(diào)用達到期望后获茬,這些設置的期望仍然保持活性。
舉個例子倔既,mock 一個接口 a(int)恕曲,我們設置第一個期望: a 調(diào)用傳入?yún)?shù)任意,調(diào)用次數(shù)任意渤涌;然后設置第二個期望: a 調(diào)用傳入?yún)?shù)必須為1佩谣, 調(diào)用次數(shù)為2;當我們調(diào)用 a(1) 兩次后实蓬,達到了第二個期望上邊界(此時第二個期望并不會失效)稿存,這時候,第三次調(diào)用 a(1) 就會報錯瞳秽,因為匹配到第二個期望說調(diào)用超過2次。(總是匹配最后一個期望
如果想設置多個期望率翅,并按順序執(zhí)行练俐,可以如下實現(xiàn)

 //sticky
  TEST(PainterTest, GetY) {
      //設置調(diào)用按照期望設置順序,定義一個 sq 對象冕臭,名隨意
      using ::testing::InSequence;
      InSequence dummyObj;
  
      MockTurtle turtle;
      EXPECT_CALL(turtle, GetY())
      ┊   .Times(2)
      ┊   .WillOnce(Return(100))
      ┊   .WillOnce(Return(150))
      ┊   .RetiresOnSaturation(); // 指定匹配后不再生效腺晾,退休
  
      EXPECT_CALL(turtle, GetY())
      ┊   .Times(1)
      ┊   .WillOnce(Return(200))
      ┊   .RetiresOnSaturation();
  
      EXPECT_EQ(turtle.GetY(), 100);
      EXPECT_EQ(turtle.GetY(), 150);
  
      EXPECT_EQ(turtle.GetY(), 200);
  }

最后燕锥,和 gtest 中一樣,可以自己編寫 main 函數(shù)完成調(diào)用悯蝉,不過注意到归形,調(diào)用的 init 函數(shù)不同,之后便可以按前面提到的編譯命令執(zhí)行編譯鼻由,運行測試了暇榴。

int main(int argc, char** argv) {
      //初始化 gtest 和 gmock
      ::testing::InitGoogleMock(&argc, argv);
      return RUN_ALL_TESTS();
  }       

參考

我的博客即將搬運同步至騰訊云+社區(qū),邀請大家一同入駐:https://cloud.tencent.com/developer/support-plan?invite_code=38q7yly61twk8

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末蕉世,一起剝皮案震驚了整個濱河市蔼紧,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌狠轻,老刑警劉巖奸例,帶你破解...
    沈念sama閱讀 216,324評論 6 498
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異向楼,居然都是意外死亡查吊,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,356評論 3 392
  • 文/潘曉璐 我一進店門湖蜕,熙熙樓的掌柜王于貴愁眉苦臉地迎上來逻卖,“玉大人,你說我怎么就攤上這事重荠〖祝” “怎么了?”我有些...
    開封第一講書人閱讀 162,328評論 0 353
  • 文/不壞的土叔 我叫張陵戈鲁,是天一觀的道長仇参。 經(jīng)常有香客問我,道長婆殿,這世上最難降的妖魔是什么诈乒? 我笑而不...
    開封第一講書人閱讀 58,147評論 1 292
  • 正文 為了忘掉前任,我火速辦了婚禮婆芦,結(jié)果婚禮上怕磨,老公的妹妹穿的比我還像新娘。我一直安慰自己消约,他們只是感情好肠鲫,可當我...
    茶點故事閱讀 67,160評論 6 388
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著或粮,像睡著了一般导饲。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,115評論 1 296
  • 那天渣锦,我揣著相機與錄音硝岗,去河邊找鬼。 笑死袋毙,一個胖子當著我的面吹牛型檀,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播听盖,決...
    沈念sama閱讀 40,025評論 3 417
  • 文/蒼蘭香墨 我猛地睜開眼胀溺,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了媳溺?” 一聲冷哼從身側(cè)響起月幌,我...
    開封第一講書人閱讀 38,867評論 0 274
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎悬蔽,沒想到半個月后扯躺,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,307評論 1 310
  • 正文 獨居荒郊野嶺守林人離奇死亡蝎困,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,528評論 2 332
  • 正文 我和宋清朗相戀三年录语,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片禾乘。...
    茶點故事閱讀 39,688評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡澎埠,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出始藕,到底是詐尸還是另有隱情蒲稳,我是刑警寧澤,帶...
    沈念sama閱讀 35,409評論 5 343
  • 正文 年R本政府宣布伍派,位于F島的核電站江耀,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏诉植。R本人自食惡果不足惜祥国,卻給世界環(huán)境...
    茶點故事閱讀 41,001評論 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望晾腔。 院中可真熱鬧舌稀,春花似錦、人聲如沸灼擂。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,657評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽剔应。三九已至睡腿,卻和暖如春康谆,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背嫉到。 一陣腳步聲響...
    開封第一講書人閱讀 32,811評論 1 268
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留月洛,地道東北人何恶。 一個月前我還...
    沈念sama閱讀 47,685評論 2 368
  • 正文 我出身青樓,卻偏偏與公主長得像嚼黔,于是被迫代替她去往敵國和親细层。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 44,573評論 2 353

推薦閱讀更多精彩內(nèi)容

  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見模式的工具(例如配置管理唬涧,服務發(fā)現(xiàn)疫赎,斷路器,智...
    卡卡羅2017閱讀 134,651評論 18 139
  • Android 自定義View的各種姿勢1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 172,070評論 25 707
  • 用了大約20分鐘,完成了課后21天加強訓練的第一張思維導圖狮荔,規(guī)劃了一周的主要行程胎撇,也不知道這樣用的對不對,忐忑一下殖氏!
    李大鵬_365閱讀 213評論 0 0
  • 日常腦子里有很多念頭晚树,都是一閃而過,一些好的點雅采,就隨手記在了手機上爵憎。以下是我最近是一段時間,有關(guān)于廣告行業(yè)的小思考...
    賈桃閱讀 814評論 0 2
  • 最完美的婚姻一定是勢均力敵的。 ??? “我養(yǎng)你”闰渔,才是世界上最毒的情話 這兩天心情一直不算好席函,于是刷遍了網(wǎng)絡綜藝...
    歐陽茜茜閱讀 500評論 0 1