詳解c++種gmock單元測(cè)試框架
隨著微服務(wù)和CI的流行,在目前的軟件工程領(lǐng)域中單元測(cè)試可以說(shuō)是必不可少的一個(gè)環(huán)節(jié),在TDD中,單元測(cè)試更是被提高到了一個(gè)新的高度。但是很多公司由于很多不同的原因,沒(méi)有能持續(xù)維護(hù),或者干脆就從來(lái)沒(méi)有寫(xiě)過(guò)單元測(cè)試,確實(shí),單元測(cè)試在初期和代碼維護(hù)期會(huì)需要花一些投入,但是,如果一個(gè)項(xiàng)目是需要長(zhǎng)期維護(hù)和更新的,那么單元測(cè)試的作用,相對(duì)于投入來(lái)說(shuō)就根本不算什么。見(jiàn)過(guò)很多人寫(xiě)的單元測(cè)試,雖然也可以運(yùn)行,也有覆蓋率,但是稍微分析一下就會(huì)看出來(lái),那根本就不是單元測(cè)試,而已經(jīng)是集成測(cè)試,比如有人竟然要在單元測(cè)試中訪問(wèn)網(wǎng)絡(luò),寫(xiě)文件,甚至讀寫(xiě)數(shù)據(jù)庫(kù)。。
那么什么樣的數(shù)據(jù)庫(kù)是好的單元測(cè)試呢,根據(jù)筆者的經(jīng)驗(yàn),以下幾點(diǎn)可能是必須的:
1. 運(yùn)行速度快,對(duì)于一個(gè)有幾百個(gè)單元測(cè)試用例的測(cè)試來(lái)說(shuō),我期待1-2分鐘內(nèi)可以運(yùn)行完成,應(yīng)為如果我在重構(gòu)代碼,這可以讓我在很快的時(shí)間內(nèi)得到反饋。
2. 不要依賴(lài)外部因素,單元測(cè)試只針對(duì)單一函數(shù)功能測(cè)試
3. 一個(gè)用例只測(cè)試一個(gè)函數(shù)
對(duì)于其中的第二點(diǎn),可能是比較麻煩的,因?yàn)?,如果一個(gè)函數(shù)是類(lèi)型的成員函數(shù),那么很可能會(huì)依賴(lài)很多內(nèi)部的成員變量,這種情況就是mock出場(chǎng)的時(shí)候了,因?yàn)槭褂胢ock才能讓我們專(zhuān)注于自己函數(shù)一業(yè)務(wù)邏輯的測(cè)試,而將依賴(lài)隔離開(kāi)。筆者使用過(guò)很多種語(yǔ)言的mock庫(kù),用的最順手的還是Java的mokito, 當(dāng)然c++ 語(yǔ)言也有很多類(lèi)似的產(chǎn)品,比如gmock, fake it, 但是其局限性確實(shí)比較多,如果不在代碼開(kāi)始階段了解,并且做好計(jì)劃,后期想加入單元測(cè)試,并且使用gmock的時(shí)候可能就會(huì)追悔莫及,大動(dòng)干戈,下面我們來(lái)分場(chǎng)景分析一下這些局限性。
場(chǎng)景1:
class TurtleReal {
public:
void PenUp()
{
}
void PenDown()
{
}
};
class MockTurtleReal : public TurtleReal {
public:
MOCK_METHOD0(PenUp, void());
MOCK_METHOD0(PenDown, void());
};
class PainterdReal
{
TurtleReal* turtle;
public:
PainterdReal(TurtleReal* turtle)
: turtle(turtle) {}
bool DrawCircle(int, int, int) {
turtle->PenDown();
return true;
}
};
TEST(PainterTest, ChildRealCanDrawSomething) {
MockTurtleReal turtle;
EXPECT_CALL(turtle, PenDown())
.Times(AtLeast(1));
PainterdReal painter(&turtle);
EXPECT_TRUE(painter.DrawCircle(0, 0, 10));
}
結(jié)果1:

結(jié)論一:
為什么用例會(huì)失敗呢,gmock 依賴(lài)C++多態(tài)機(jī)制進(jìn)行工作,只有虛函數(shù)才能被mock, 非虛函數(shù)不能被mock, 這一點(diǎn)告訴我們,如果想要在代碼中使用gmock類(lèi)的設(shè)計(jì)中,最好采用接口隔離,對(duì)于c++來(lái)說(shuō)也就是采用純虛類(lèi)型,因?yàn)閏++本身沒(méi)有接口類(lèi)型。
場(chǎng)景2:
class Turtle {
public:
virtual ~Turtle() {}
virtual void PenUp() = 0;
virtual void PenDown() = 0;
};
class MockTurtle : public Turtle {
public:
MOCK_METHOD0(PenUp, void());
MOCK_METHOD0(PenDown, void());
};
class Painter
{
Turtle* turtle;
public:
Painter(Turtle* turtle)
: turtle(turtle) {}
bool DrawCircle(int, int, int) {
turtle->PenDown();
return true;
}
};
TEST(PainterTest, CanDrawSomething) {
MockTurtle turtle;
EXPECT_CALL(turtle, PenDown())
.Times(AtLeast(1));
Painter painter(&turtle);
EXPECT_TRUE(painter.DrawCircle(0, 0, 10));
}
結(jié)果2:

結(jié)論二:
將函數(shù)改為虛函數(shù),測(cè)試用例通過(guò)
場(chǎng)景3:
class TurtleChild: Turtle {
public:
void PenUp()
{
int a = 0;
};
void PenDown()
{
int b = 0;
};
};
class MockTurtleChild : public TurtleChild {
public:
MOCK_METHOD0(PenUp, void());
MOCK_METHOD0(PenDown, void());
};
class PainterChildRef
{
TurtleChild turtle;
public:
PainterChildRef(TurtleChild& turtle)
: turtle(turtle) {}
bool DrawCircle(int, int, int) {
turtle.PenDown();
return true;
}
};
TEST(PainterTest, ChildCanDrawSomething) {
MockTurtleChild turtle;
EXPECT_CALL(turtle, PenDown())
.Times(AtLeast(1));
PainterChild painter(&turtle);
EXPECT_TRUE(painter.DrawCircle(0, 0, 10));
}
結(jié)果3:

結(jié)論三:
測(cè)試用例通過(guò),派生類(lèi)中的同名函數(shù)仍然是虛函數(shù),同樣支持多態(tài),支持gomck
場(chǎng)景4:
class Turtle {
public:
virtual ~Turtle() {}
virtual void PenUp() = 0;
virtual void PenDown() = 0;
};
class TurtleChild: Turtle {
public:
void PenUp()
{
int a = 0;
};
void PenDown()
{
int b = 0;
};
};
class MockTurtleChild : public TurtleChild {
public:
MOCK_METHOD0(PenUp, void());
MOCK_METHOD0(PenDown, void());
};
class PainterChildRef
{
TurtleChild turtle;
public:
PainterChildRef(TurtleChild& turtle)
: turtle(turtle) {}
bool DrawCircle(int, int, int) {
turtle.PenDown();
return true;
}
};
TEST(PainterTest, ChildRefCanDrawSomething) {
MockTurtleChild turtle;
EXPECT_CALL(turtle, PenDown())
.Times(AtLeast(1));
PainterChildRef painter(turtle);
EXPECT_TRUE(painter.DrawCircle(0, 0, 10));
}
結(jié)果4:

結(jié)論四:
測(cè)試用例失敗,以引用類(lèi)型傳入的成員變量本身不具備多態(tài)特性,因此gmock不支持
結(jié)論
本文通過(guò)四個(gè)場(chǎng)景,層層遞進(jìn),深入的剖析了gmock的使用,希望大家在寫(xiě)代碼之前早做打算,避免大動(dòng)干戈,返工重來(lái)。但是從另一個(gè)方面來(lái)說(shuō),接口隔離, p-impl 慣用法等技術(shù),應(yīng)該是一個(gè)c++老鳥(niǎo)的必備法寶,可見(jiàn)好多東西都是有其道理的,前期不了解,后期只能花更多的精力取彌補(bǔ),要么推翻重構(gòu),要么直接放棄,無(wú)知者無(wú)畏,no zuo, no die..
相關(guān)文章
C++數(shù)據(jù)結(jié)構(gòu)之文件壓縮(哈夫曼樹(shù))實(shí)例詳解
這篇文章主要介紹了C++數(shù)據(jù)結(jié)構(gòu)之文件壓縮(哈夫曼樹(shù))實(shí)例詳解的相關(guān)資料,利用哈夫曼編碼的方式對(duì)文件進(jìn)行壓縮,并且對(duì)壓縮文件可以解壓,需要的朋友可以參考下2017-07-07
C++之IO類(lèi),文件輸入輸出,string流練習(xí)題
這篇文章主要介紹了C++實(shí)現(xiàn)IO類(lèi)的幾道數(shù)組練習(xí)題,本篇文章通過(guò)簡(jiǎn)要的案例,講解了該項(xiàng)技術(shù)的了解與使用,以下就是詳細(xì)內(nèi)容,需要的朋友可以參考下2021-09-09
C++?數(shù)據(jù)結(jié)構(gòu)超詳細(xì)講解單鏈表
這篇文章主要介紹了C++數(shù)據(jù)結(jié)構(gòu)之單鏈表,鏈表是由一個(gè)個(gè)結(jié)點(diǎn)鏈結(jié)成的。結(jié)點(diǎn)包括數(shù)據(jù)域和指針域兩部分,數(shù)據(jù)域用來(lái)存儲(chǔ)數(shù)據(jù)元素的信息,指針域用來(lái)存儲(chǔ)下一個(gè)結(jié)點(diǎn)的地址,更詳細(xì)內(nèi)容請(qǐng)需要的小伙伴參考下面文章內(nèi)容2022-03-03
C++類(lèi)和對(duì)象之默認(rèn)成員函數(shù)的使用解讀
這篇文章主要介紹了C++類(lèi)和對(duì)象之默認(rèn)成員函數(shù)的使用方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2025-06-06
C語(yǔ)言實(shí)現(xiàn)哈希搜索算法及原理詳解
本文主要介紹了C語(yǔ)言實(shí)現(xiàn)哈希搜索算法及原理詳解,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2023-06-06
LeetCode 單調(diào)棧內(nèi)容小結(jié)
這篇文章主要介紹了LeetCode 單調(diào)棧內(nèi)容小結(jié),本篇文章通過(guò)簡(jiǎn)要的案例,講解了該項(xiàng)技術(shù)的了解與使用,以下就是詳細(xì)內(nèi)容,需要的朋友可以參考下2021-07-07

