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í)行該套件用例時妆棒,
- gtest 構(gòu)建 QueueTest 實例 qt1;
- 調(diào)用 qt1.SetUp() 初始化
- 執(zhí)行一個用例
- 調(diào)用 qt1.TearDown() 清理
- 析構(gòu) qt1 對象
- 回到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.a 和 libgtest.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 mock 對象葱她,可以定義函數(shù)期望行為撩扒,如被調(diào)用時返回的值,期望被調(diào)用的次數(shù)吨些,參數(shù)等搓谆,如果不滿足就會報錯炒辉。
定義 gmock 對象的基本步驟:
- 創(chuàng)建 mock 對象繼承自原對象,并用框架提供的宏
MOCK_METHODn(); (or MOCK_CONST_METHODn();
描述需要模擬的接口 - 寫用例挽拔,在用例中使用宏定義期望接口的行為辆脸,如果定義的行為執(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