C++单元测试学习总结9
生活随笔
收集整理的這篇文章主要介紹了
C++单元测试学习总结9
小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.
C++單元測(cè)試一:并非看上去那么簡(jiǎn)單——幾個(gè)很實(shí)際的問(wèn)題
理想與現(xiàn)實(shí)
為Java和C#做單元測(cè)試,基本上都有比較統(tǒng)一的工具、模式可用,IDE的支持也非常到位;可是到
了C++這里,一切就變的那樣的“不走尋常路”,各種單元測(cè)試框架盛行,例如CppUnit,?
CppUnitLite, CxxUnit,Google Test等等,以及微軟在VS2012中也加入了對(duì)原生C++代碼單元測(cè)
試的支持MSTest。面對(duì)如此諸多的測(cè)試框架,選擇哪一個(gè)其實(shí)無(wú)所謂,只要順手就好,用習(xí)慣了
,什么都好;因?yàn)?#xff0c;單元測(cè)試的代碼總歸還是要自己來(lái)寫(xiě)——無(wú)論你用哪一個(gè)框架。
以前寫(xiě)過(guò)不少代碼,可是都沒(méi)怎么注意單元測(cè)試,現(xiàn)在終于認(rèn)真對(duì)待起來(lái),就開(kāi)始在網(wǎng)絡(luò)上搜尋資
料,看看各種框架下的單元測(cè)試如何寫(xiě)。幸運(yùn)的是,這方面的資料真不少,很多框架也都會(huì)帶有示
例或者文檔告訴你怎么寫(xiě);不幸的是,這些文檔或者示例都太遠(yuǎn)離工程實(shí)踐,他們一般遵循一個(gè)這
樣的模式:寫(xiě)出一個(gè)待測(cè)類(lèi)CMyClass,定義一定的成員變量和方法,然后給出針對(duì)CMyClass的
測(cè)試類(lèi)如CMyClassTest;呵呵,看起來(lái)示例是夠好了,可是很少涉及到這樣的實(shí)際問(wèn)題:?
工程實(shí)踐中,一個(gè)項(xiàng)目往往有很多類(lèi),而且相互之間,總有這或多或少的依賴(lài)、包含等關(guān)系;
?實(shí)際的測(cè)試項(xiàng)目,該如何組織被測(cè)代碼和測(cè)試代碼(注意,這里所說(shuō)的組織不是指單元測(cè)試數(shù)據(jù)
如何組織,而是指工程中的測(cè)試代碼和產(chǎn)品代碼的組織)
?被測(cè)代碼如何引入測(cè)試工程中
?一個(gè)測(cè)試工程如何測(cè)試多個(gè)被測(cè)類(lèi)
實(shí)際的代碼
好吧,我們來(lái)看一下一個(gè)“較為”實(shí)際的工程以及我在為該工程編寫(xiě)單元測(cè)試過(guò)程中所遇到的問(wèn)題
;該工程的代碼來(lái)自于boost.asio中的一個(gè)示例代碼:
http://www.boost.org/doc/libs/1_53_0/doc/html/boost_asio/example/chat/chat_server.cpp,
我把該cpp中的幾個(gè)類(lèi)分拆開(kāi)了,放在一個(gè)VisualStudio 2012工程中,代碼結(jié)構(gòu)看起來(lái)是:
其中幾個(gè)主要類(lèi)Message,ChatSession,ChatRoom之間的關(guān)系如下圖:
在我做單元測(cè)試過(guò)程中,首先從軟柿子Message入手,然后為ChatRoom寫(xiě)UT。所以,先把這兩
個(gè)類(lèi)的相關(guān)代碼貼上來(lái);其實(shí)貼不貼代碼無(wú)關(guān)緊要,上面的類(lèi)圖已經(jīng)可以說(shuō)明問(wèn)題,不過(guò)為了方便
較真,還是貼出來(lái)吧。
Message類(lèi)代碼
/// Message.h #pragma once /// class represents the message transferred between the client and the server /// the message consists of two part: header + message body /// the header part, 4 bytes, stores the length of the message body /// the max length of the message box is : 512 class Message { public: enum { HeaderLength = 4, MaxBodyLength = 511 }; public: Message(void); ~Message(void); public: void EncodeHeader(); bool DecodeHeader(); const char* Data() const; char* Data() ; const char* Body() const; char* Body() ; int SetData(const char* src, const int srclength); int Length() const; int BodyLength()const; void Reset(); private: void CheckBodyLength(); private: /// stores the whole message char Data_[HeaderLength + MaxBodyLength + 1]; /// the body length int BodyLength_; }; /// Message.cpp #include "Message.h" #include <cstdio> #include <boost/lexical_cast.hpp> #include <algorithm> #include "TraceLog.h" Message::Message(void) { Reset(); } Message::~Message(void) { } void Message::CheckBodyLength() { BodyLength_ = BodyLength_ > MaxBodyLength ? MaxBodyLength : BodyLength_; } void Message::EncodeHeader() { /// Check the body length CheckBodyLength(); /// wirte the body length to the message header /// we make sure that the buffer is enough after we call CheckBodyLength() ::_snprintf_s( Data_, HeaderLength, HeaderLength, "%d", BodyLength_ ); } bool Message::DecodeHeader() { int bodyLength = 0; bool ret = false; /// get the message body length from the message try { char buf[HeaderLength + 1] = ""; std::strncat( buf, Data_, HeaderLength ); bodyLength = boost::lexical_cast<int> (buf); if( bodyLength > MaxBodyLength ) { bodyLength = MaxBodyLength; } else { ret = true; } } catch(boost::bad_lexical_cast& e) { /// cast error happens bodyLength = 0; TraceLog::WriteLine("Message::DecodeHeader(),error:%s, orinal message:%s", e.what(), Data_ ); } /// set the value and return BodyLength_ = bodyLength; return ret; } char* Message::Data() { return Data_ ; } const char* Message::Data() const { return Data_ ; } char* Message::Body() { return Data_ + HeaderLength; } const char* Message::Body() const { return Data_ + HeaderLength; } int Message::SetData(const char* src, const int srclength) { /// check the length of source int length = srclength; if( length > MaxBodyLength ) { length = MaxBodyLength; } /// copy the data into the local buffer /// std::snprintf is unavailable in this c++ compiler int ret = ::_snprintf_s(Data_+HeaderLength, MaxBodyLength + 1, length, "%s", src ); /// set the length of the message body BodyLength_ = length; /// return the length of copied return ret; } int Message::Length() const { return BodyLength_ + HeaderLength; } int Message::BodyLength() const { return BodyLength_; } void Message::Reset() { BodyLength_ = 0; /// just for using the lamda std::for_each(Data_, Data_ + HeaderLength + MaxBodyLength + 1, [](char& p) { p = '\0'; } ); }
??
/// ChatRoom.h ?
#pragma once ?
#include "ChatSession.h" ?
#include "Message.h" ?
#include <set> ?
#include <queue> ?
? ?
/// class that manages the clients ?
/// deliver the messages from one client to the others ?
? ?
class ChatRoom ?
{ ?
public: ?
? ? ? ChatRoom(void); ?
? ? ? ~ChatRoom(void); ?
? ?
public: ?
? ? ? /// a client joins in the room ?
? ? ? void Join(ChatParticipantPtr participant); ?
? ?
? ? ? /// a client leaves the room ?
? ? ? void leave(ChatParticipantPtr participant); ?
? ?
? ? ? /// deliver the message from one client to all of the users in the room ?
? ? ? void Deliver(const Message& msg); ?
? ?
private: ?
? ? ? /// all of the participants are stored here ?
? ? ? std::set<ChatParticipantPtr> Participants_; ?
? ?
? ? ? /// recent messages ?
? ? ? /// questions, how to synchronize this object in threads ?
? ? ? typedef std::deque<Message> MessageQueue; ?
? ? ? MessageQueue RecentMessages_; ?
? ? ? enum { MaxRecentMsgs = 100 }; ?
}; ?
? ?? ?
/// ChatRoom.cpp ?
#include "ChatRoom.h" ?
#include <boost/bind.hpp> ?
#include <algorithm> ?
#include "TraceLog.h" ?
? ?
ChatRoom::ChatRoom(void) ?
{ ?
} ?? ?
? ?
ChatRoom::~ChatRoom(void) ?
{ ?
} ?
? ?
/// a client joins in the room ?
void ChatRoom::Join(ChatParticipantPtr participant) ?
{ ?
? ? ? TraceLog::WriteLine("ChatRoom::Join(), a new user joins in"); ?
? ?
? ? ? /// add into the queue ?
? ? ? Participants_.insert( participant ); ?
? ?
? ? ? /// sending the recent message to the client ?
? ? ? std::for_each(RecentMessages_.begin(), RecentMessages_.end(), ?
? ? ? ? ? ? boost::bind( &ChatParticipant::Deliver, participant, _1 ) ); ?
} ? ??
? ?
/// a client leaves the room ?
void ChatRoom::leave(ChatParticipantPtr participant) ?
{ ?
? ? ? TraceLog::WriteLine("ChatRoom::leave(), a user leaves"); ?
? ?
? ? ? /// remove it from the queue ?
? ? ? Participants_.erase( participant ); ?
} ? ??
? ?
/// deliver the message from one client to all of the users in the room ?
void ChatRoom::Deliver(const Message& msg) ?
{ ?
? ? ? TraceLog::WriteLine("ChatRoom::Deliver(), %s", msg.Body() ); ?
? ?
? ? ? /// add the msg to queue ?
? ? ? RecentMessages_.push_back( msg ); ?
? ?
? ? ? /// check the length ?
? ? ? while( RecentMessages_.size() > MaxRecentMsgs ) ?
? ? ? { ?
? ? ? ? ? ? RecentMessages_.pop_front(); ?
? ? ? } ?
? ?
? ? ? /// deliver the msg to clients ?
? ? ? std::for_each(Participants_.begin(), Participants_.end(), ?
? ? ? ? ? ? boost::bind( &ChatParticipant::Deliver, _1, boost::ref(msg) ) ); ?
} ?
開(kāi)始單元測(cè)試
由于到手了VisualStudio 2012,這貨已經(jīng)原始支持了C++Native代碼的單元測(cè)試,就用這貨開(kāi)始做
UT吧。
如何引入被測(cè)代碼
好了,我們開(kāi)始單元測(cè)試。首先創(chuàng)建一個(gè)C++單元測(cè)試的工程,這個(gè)很easy。接著我們就要讓測(cè)
試工程能夠“看到”被測(cè)的代碼,這如何搞呢?有這樣幾種方法:
如果被測(cè)代碼是靜態(tài)庫(kù)或者動(dòng)態(tài)庫(kù),包含對(duì)應(yīng)的.h文件,讓測(cè)試工程鏈接DLL及LIB,這樣測(cè)試工
程。
或者,讓測(cè)試工程鏈接對(duì)應(yīng)的obj文件,直接編譯進(jìn)測(cè)試工程
或者,直接把被測(cè)是的代碼,如上述的Message.h和Message.cpp包含進(jìn)測(cè)試工程(注意這里不
要拷貝一份Message.h和Message.cpp,用“Add->ExsitingItem”將他們包含進(jìn)去,這樣只保
留一份代碼)
?或者在單元測(cè)試代碼文件,如TestMessage.cpp中直接用#include把Message.h和Message.cpp
包含進(jìn)來(lái),如:?
? ? ? ? ? ? ? ?#include "../ChatroomServer/ChatRoom.h"
? ? ? ? ? ? ? ?#include "../ChatroomServer/ChatRoom.cpp"
上面這幾種方法,其實(shí)原理都是一樣的,反正就是讓測(cè)試工程能夠看到到被測(cè)的代碼,我們使用把
被測(cè)代碼引入測(cè)試工程的方法,這樣測(cè)試工程的代碼結(jié)構(gòu)看起來(lái)是這樣:
Ok,現(xiàn)在在測(cè)試工程里面,可以看到Message類(lèi)的聲明和定義了,然后你的單元測(cè)試代碼,該怎
么寫(xiě),就怎么寫(xiě)了。
一個(gè)測(cè)試工程只能測(cè)一個(gè)類(lèi)嗎?
使用VS2012中的單元測(cè)試框架,寫(xiě)完了對(duì)Message的的單元測(cè)試,在TestExplorer中RunAll,一
切正常;好了,至此,一切還算順利,那我們繼續(xù)吧,來(lái)對(duì)ChatRoom類(lèi)編寫(xiě)單元測(cè)試;
繼續(xù)按照前面的方法,我們很容易讓測(cè)試工程看到ChatRoom的被測(cè)代碼;然而從ChatRoom的
實(shí)現(xiàn)來(lái)看,這個(gè)類(lèi)和Message類(lèi)有著關(guān)聯(lián)關(guān)系,而且在ChatRoom的方法中,也的確使用了
Message類(lèi)的方法,從單元測(cè)試的角度來(lái)看,我們應(yīng)該將他們倆之間的關(guān)系隔斷,從而保證我們
只對(duì)ChatRoom進(jìn)行測(cè)試,那這樣,我們就需要Mock一份Message的實(shí)現(xiàn)。
可是問(wèn)題來(lái)了,由于之前我們?cè)跍y(cè)試Message類(lèi)的時(shí)候,已經(jīng)引入了Message.cpp文件,使得測(cè)
試工程中,已經(jīng)有了一份Message的實(shí)現(xiàn),那么我們?nèi)绾卧倌転镸essage提供一份“偽”實(shí)現(xiàn)呢
??(使用其他幾種引入方式,結(jié)果都是一樣的)
是的,慣用的DependencyInjection在這里不起作用。查了不少資料,也沒(méi)找到一個(gè)像樣的說(shuō)明
如何解決這個(gè)問(wèn)題;現(xiàn)在唯一可以采用的,就是在一個(gè)測(cè)試工程里面,只測(cè)試一個(gè)被測(cè)類(lèi),雖然可
以工作,但是,未免又過(guò)于繁瑣和“愚蠢”,那聰明的方法,又是什么呢?不瞞你說(shuō),這正是目前
困擾我的問(wèn)題之一。
追加一個(gè)后記:
其實(shí),在關(guān)于如何為Message提供一份“偽”實(shí)現(xiàn)的問(wèn)題上,原來(lái)想法是在測(cè)試工程中包含
Message的頭文件,然后在測(cè)試工程里面,直接寫(xiě)Message::Message()等方法,事實(shí)上是在測(cè)試
工程里面定義一個(gè)Message的實(shí)現(xiàn);這樣由于我們已經(jīng)引入了Message的真正實(shí)現(xiàn),從而必然導(dǎo)
致鏈接器報(bào)符號(hào)重復(fù)定義的錯(cuò)誤,因此,這種思路并不可行,故而強(qiáng)迫我去創(chuàng)建另外一個(gè)工程;后
來(lái)想一想,其實(shí)也不必真的去創(chuàng)建一個(gè)新工程,他們是可以在一個(gè)工程里面完成的,方法是新建一
個(gè)MockMessage類(lèi),讓他從Message繼承下來(lái),然后重新定義自己想Mock的方法就可以了。但
是,這種方法創(chuàng)建出來(lái)的Mock,還是真的Mock嗎,他首先已經(jīng)是一個(gè)“真”的Message了啊?
========
C/C++單元測(cè)試?yán)碚摼?#xff08;一)
http://blog.csdn.net/easytdd/article/details/5484405內(nèi)容介紹
? ? 本系列文章根據(jù)《單元測(cè)試與VU2.6應(yīng)用》視頻講座的理論部分整理而成,主要討論四個(gè)問(wèn)題
:為什么需要單元測(cè)試?怎樣征服可測(cè)性難題?怎樣才能高效率測(cè)試?怎樣保證測(cè)試效果?重點(diǎn)闡
述單元測(cè)試的關(guān)鍵問(wèn)題,不是一般概念,適合于對(duì)單元測(cè)試有一定了解的讀者。
? ? 在選擇工具和實(shí)施單元測(cè)試前,我們應(yīng)該對(duì)相關(guān)理論有一個(gè)系統(tǒng)的了解,特別是將會(huì)遇到哪些
難題,如何解決,要心里有數(shù),否則的話(huà),很可能勞民傷財(cái),半途而廢。如果只會(huì)測(cè)試加法函數(shù)或
者三角形函數(shù)之類(lèi)的獨(dú)立小程序,就以為可以做單元測(cè)試了,那就像一個(gè)人剛學(xué)會(huì)走路,就去長(zhǎng)途
跋涉。
? ? 本文介紹的是針對(duì)企業(yè)項(xiàng)目的單元測(cè)試。企業(yè)項(xiàng)目具有兩個(gè)特點(diǎn):項(xiàng)目復(fù)雜,時(shí)間緊張。項(xiàng)目
復(fù)雜,意味著測(cè)試時(shí)會(huì)遇到很多難題;時(shí)間緊張,要求我們不但要保證測(cè)試效果,還要盡可能高效
率。本文不是泛泛而談,而是針對(duì)企業(yè)項(xiàng)目的兩個(gè)特點(diǎn),努力揭示本質(zhì)性的問(wèn)題,并提出解決辦法
,對(duì)于常識(shí)性的問(wèn)題,將比較簡(jiǎn)略的帶過(guò)。使用的工具是Visual Unit 2.6,本文主要不是介紹工具
,而是介紹問(wèn)題所在和解決辦法,涉及到工具,只是為了具體的展示解決辦法,也為了說(shuō)明,這些
辦法都是可行的,并非空談。
第1章 為什么需要單元測(cè)試?
1.1 從代碼特性看單元測(cè)試的必要性
? ? 代碼有一個(gè)很基本的特性,是什么呢?對(duì)數(shù)據(jù)分類(lèi)處理。一個(gè)判定,就是一次分類(lèi)。如果判定
中又嵌套了判定的話(huà),分類(lèi)次數(shù)就會(huì)翻倍。循環(huán)判定也是一種分類(lèi)。
? ? 如果一個(gè)分類(lèi)遺漏的話(huà),也就是說(shuō),某種輸入沒(méi)有處理,會(huì)怎么樣呢?一個(gè)Bug誕生了!如果
一個(gè)分類(lèi)的處理不正確,又會(huì)怎么樣呢?又一個(gè)Bug誕生了!
? ? 一個(gè)函數(shù)要做到?jīng)]有錯(cuò)誤,要保證兩點(diǎn):分類(lèi)完整,也就是各類(lèi)可能輸入都要考慮到;
處理正確,也就是每一類(lèi)輸入都要進(jìn)行正確的處理。做到了這兩點(diǎn),就可以說(shuō),函數(shù)的功能邏輯是
正確的。函數(shù)的功能邏輯就是對(duì)數(shù)據(jù)的分類(lèi)以及處理。
? ? 那么,怎樣才能全面地檢測(cè)程序的功能邏輯呢?調(diào)試,是臨時(shí)的,不做記錄,另一方面,調(diào)試
的工作方式是攔截?cái)?shù)據(jù),并不是所有輸入分類(lèi)都能攔截得到的,如果一個(gè)函數(shù)有十類(lèi)輸入,調(diào)試能
覆蓋五六個(gè)就不錯(cuò)了。系統(tǒng)測(cè)試,不針對(duì)具體的函數(shù),無(wú)法做到對(duì)具體函數(shù)的輸入分類(lèi)覆蓋。要全
面地檢測(cè)程序的功能邏輯,必須把輸入分類(lèi)全部列出來(lái)。并檢測(cè)程序是否處理了這些輸入,處理是
否正確。這就是單元測(cè)試。
說(shuō)明
本系列文章根據(jù)《單元測(cè)試與VU2.6應(yīng)用》視頻講座的理論部分整理而成,PPT及視頻下載:
PPT下載:http://download.csdn.net/source/2246006
視頻part1: http://download.csdn.net/source/2246273
視頻part2: http://download.csdn.net/source/2246345
視頻part3: http://download.csdn.net/source/2246364
本系列文章及視頻講座介紹的是單元測(cè)試?yán)碚摰木糠?#xff0c;詳細(xì)內(nèi)容請(qǐng)閱讀《C/C++單元測(cè)試實(shí)
用教程》?
========
玩轉(zhuǎn)Google開(kāi)源C++單元測(cè)試框架Google Test系列(gtest)之八 - 打造自己的單元測(cè)試框架
http://www.cnblogs.com/coderzh/archive/2009/04/12/1434155.html一、前言
上一篇我們分析了gtest的一些內(nèi)部實(shí)現(xiàn),總的來(lái)說(shuō)整體的流程并不復(fù)雜。本篇我們就嘗試編寫(xiě)一
個(gè)精簡(jiǎn)版本的C++單元測(cè)試框架:nancytest ,通過(guò)編寫(xiě)這個(gè)簡(jiǎn)單的測(cè)試框架,將有助于我們理
解gtest。
二、整體設(shè)計(jì)
使用最精簡(jiǎn)的設(shè)計(jì),我們就用兩個(gè)類(lèi),夠簡(jiǎn)單吧:
1. TestCase類(lèi)
包含單個(gè)測(cè)試案例的信息。?
2. UnitTest類(lèi)
負(fù)責(zé)所有測(cè)試案例的執(zhí)行,管理。
三、TestCase類(lèi)
TestCase類(lèi)包含一個(gè)測(cè)試案例的基本信息,包括:測(cè)試案例名稱(chēng),測(cè)試案例執(zhí)行結(jié)果,同時(shí)還提
供了測(cè)試案例執(zhí)行的方法。我們編寫(xiě)的測(cè)試案例都繼承自TestCase類(lèi)。
復(fù)制代碼
class TestCase
{
public:
? ? TestCase(const char* case_name) : testcase_name(case_name){}
? ? // 執(zhí)行測(cè)試案例的方法
? ? virtual void Run() = 0;
? ? int nTestResult; // 測(cè)試案例的執(zhí)行結(jié)果?
? ? const char* testcase_name; // 測(cè)試案例名稱(chēng)
};
復(fù)制代碼
?
四、UnitTest類(lèi)
我們的UnitTest類(lèi)和gtest的一樣,是一個(gè)單件。我們的UnitTest類(lèi)的邏輯非常簡(jiǎn)單:
1. 整個(gè)進(jìn)程空間保存一個(gè)UnitTest 的單例。
2. 通過(guò)RegisterTestCase()將測(cè)試案例添加到測(cè)試案例集合testcases_中。
3. 執(zhí)行測(cè)試案例時(shí),調(diào)用UnitTest::Run(),遍歷測(cè)試案例集合testcases_,調(diào)用案例的Run()方法
復(fù)制代碼
class UnitTest
{
public:
? ? // 獲取單例
? ? static UnitTest* GetInstance();?
? ? // 注冊(cè)測(cè)試案例
? ? TestCase* RegisterTestCase(TestCase* testcase);
? ??
? ? // 執(zhí)行單元測(cè)試
? ? int Run();
? ? TestCase* CurrentTestCase; // 記錄當(dāng)前執(zhí)行的測(cè)試案例
? ? int nTestResult; // 總的執(zhí)行結(jié)果
? ? int nPassed; // 通過(guò)案例數(shù)
? ? int nFailed; // 失敗案例數(shù)
protected:
? ? std::vector<TestCase*> testcases_; // 案例集合
};
復(fù)制代碼
下面是UnitTest類(lèi)的實(shí)現(xiàn):
復(fù)制代碼
UnitTest* UnitTest::GetInstance()
{
? ? static UnitTest instance;
? ? return &instance;
}
TestCase* UnitTest::RegisterTestCase(TestCase* testcase)
{
? ? testcases_.push_back(testcase);
? ? return testcase;
}
int UnitTest::Run()
{
? ? nTestResult = 1;
? ? for (std::vector<TestCase*>::iterator it = testcases_.begin();
? ? ? ? it != testcases_.end(); ++it)
? ? {
? ? ? ? TestCase* testcase = *it;
? ? ? ? CurrentTestCase = testcase;
? ? ? ? std::cout << green << "======================================"?
<< std::endl;
? ? ? ? std::cout << green << "Run TestCase:" << testcase->testcase_name << std::endl;
? ? ? ? testcase->Run();
? ? ? ? std::cout << green << "End TestCase:" << testcase->testcase_name << std::endl;
? ? ? ? if (testcase->nTestResult)
? ? ? ? {
? ? ? ? ? ? nPassed++;
? ? ? ? }
? ? ? ? else
? ? ? ? {
? ? ? ? ? ? nFailed++;
? ? ? ? ? ? nTestResult = 0;
? ? ? ? }
? ? }
? ? std::cout << green << "======================================"?
<< std::endl;
? ? std::cout << green << "Total TestCase : " << nPassed + nFailed << std::endl;
? ? std::cout << green << "Passed : " << nPassed << std::endl;
? ? std::cout << red << "Failed : " << nFailed << std::endl;
? ? return nTestResult;
}
復(fù)制代碼
五、NTEST宏
接下來(lái)定一個(gè)宏NTEST,方便我們寫(xiě)我們的測(cè)試案例的類(lèi)。
復(fù)制代碼
#define TESTCASE_NAME(testcase_name) \
? ? testcase_name##_TEST
#define NANCY_TEST_(testcase_name) \
class TESTCASE_NAME(testcase_name) : public TestCase \
{ \
public: \
? ? TESTCASE_NAME(testcase_name)(const char* case_name) : TestCase(case_name){}; \
? ? virtual void Run(); \
private: \
? ? static TestCase* const testcase_; \
}; \
\
TestCase* const TESTCASE_NAME(testcase_name) \
? ? ::testcase_ = UnitTest::GetInstance()->RegisterTestCase( \
? ? ? ? new TESTCASE_NAME(testcase_name)(#testcase_name)); \
void TESTCASE_NAME(testcase_name)::Run()
#define NTEST(testcase_name) \
? ? NANCY_TEST_(testcase_name)
復(fù)制代碼
?
六、RUN_ALL_TEST宏
然后是執(zhí)行所有測(cè)試案例的一個(gè)宏:
#define RUN_ALL_TESTS() \
? ? UnitTest::GetInstance()->Run();
七、斷言的宏EXPECT_EQ?
這里,我只寫(xiě)一個(gè)簡(jiǎn)單的EXPECT_EQ :
復(fù)制代碼
#define EXPECT_EQ(m, n) \
? ? if (m != n) \
? ? { \
? ? ? ? UnitTest::GetInstance()->CurrentTestCase->nTestResult = 0; \
? ? ? ? std::cout << red << "Failed" << std::endl; \
? ? ? ? std::cout << red << "Expect:" << m << std::endl; \
? ? ? ? std::cout << red << "Actual:" << n << std::endl; \
? ? }
復(fù)制代碼
?
八、案例Demo
夠簡(jiǎn)單吧,再來(lái)看看案例怎么寫(xiě):
復(fù)制代碼
#include "nancytest.h"
int Foo(int a, int b)
{
? ? return a + b;
}
NTEST(FooTest_PassDemo)
{
? ? EXPECT_EQ(3, Foo(1, 2));
? ? EXPECT_EQ(2, Foo(1, 1));
}
NTEST(FooTest_FailDemo)
{
? ? EXPECT_EQ(4, Foo(1, 2));
? ? EXPECT_EQ(2, Foo(1, 2));
}
int _tmain(int argc, _TCHAR* argv[])
{
? ? return RUN_ALL_TESTS();
}
復(fù)制代碼
整個(gè)一山寨版gtest,呵。執(zhí)行一下,看看結(jié)果怎么樣:
九、總結(jié)?
本篇介紹性的文字比較少,主要是我們?cè)谏弦黄钊虢馕鰃test時(shí)已經(jīng)將整個(gè)流程弄清楚了,而現(xiàn)
在編寫(xiě)的nancytest又是其非常的精簡(jiǎn)版本,所有直接看代碼就可以完全理解。希望通過(guò)這個(gè)
Demo,能夠讓大家對(duì)gtest有更加直觀(guān)的了解?;氐介_(kāi)篇時(shí)所說(shuō)的,我們沒(méi)有必要每個(gè)人都造一
個(gè)輪子,因?yàn)間test已經(jīng)非常出色的為我們做好了這一切。如果我們每個(gè)人都寫(xiě)一個(gè)自己的框架的
話(huà),一方面我們要付出大量的維護(hù)成本,一方面,這個(gè)框架也許只能對(duì)你有用,無(wú)法讓大家從中受
益。
gtest正是這么一個(gè)優(yōu)秀C++單元測(cè)試框架,它完全開(kāi)源,允許我們一起為其貢獻(xiàn)力量,并能讓更
多人從中受益。如果你在使用gtest過(guò)程中發(fā)現(xiàn)gtest不能滿(mǎn)足你的需求時(shí)(或發(fā)現(xiàn)BUG),gtest
的開(kāi)發(fā)人員非常急切的想知道他們哪來(lái)沒(méi)做好,或者是gtest其實(shí)有這個(gè)功能,但是很多用戶(hù)都不
知道。所以你可以直接聯(lián)系gtest的開(kāi)發(fā)人員,或者你直接在這里回帖,我會(huì)將您的意見(jiàn)轉(zhuǎn)告給
gtest的主要開(kāi)發(fā)人員。
如果你是gtest的超級(jí)粉絲,原意為gtest貢獻(xiàn)代碼的話(huà),加入他們吧?! ?
本Demo代碼下載:/Files/coderzh/Code/nancytest.rar?
本篇是該系列最后一篇,其實(shí)gtest還有更多東西值得我們?nèi)ヌ剿?#xff0c;本系列也不可能將gtest介紹完
全,還是那句話(huà),想了解更多gtest相關(guān)的內(nèi)容的話(huà):
訪(fǎng)問(wèn)官方主頁(yè):http://code.google.com/p/googletest/
下載gtest源碼: http://code.google.com/p/googletest/downloads/list
系列鏈接:
1.玩轉(zhuǎn)Google開(kāi)源C++單元測(cè)試框架Google Test系列(gtest)之一 - 初識(shí)gtest
2.玩轉(zhuǎn)Google開(kāi)源C++單元測(cè)試框架Google Test系列(gtest)之二 - 斷言
3.玩轉(zhuǎn)Google開(kāi)源C++單元測(cè)試框架Google Test系列(gtest)之三 - 事件機(jī)制
4.玩轉(zhuǎn)Google開(kāi)源C++單元測(cè)試框架Google Test系列(gtest)之四 - 參數(shù)化
5.玩轉(zhuǎn)Google開(kāi)源C++單元測(cè)試框架Google Test系列(gtest)之五 - 死亡測(cè)試?
6.玩轉(zhuǎn)Google開(kāi)源C++單元測(cè)試框架Google Test系列(gtest)之六 - 運(yùn)行參數(shù)
7.玩轉(zhuǎn)Google開(kāi)源C++單元測(cè)試框架Google Test系列(gtest)之七 - 深入解析gtest
8.玩轉(zhuǎn)Google開(kāi)源C++單元測(cè)試框架Google Test系列(gtest)之八 - 打造自己的單元測(cè)試框架
?
========
使用 Visual Studio 2015 對(duì) C++ 代碼運(yùn)行單元測(cè)試
http://blog.csdn.net/lxf200000/article/details/51100094代碼寫(xiě)多了,往往規(guī)模會(huì)越來(lái)越大,這時(shí)候就有必要保證代碼的穩(wěn)定性了;不過(guò)我從網(wǎng)上看到的單
元測(cè)試貌似大多都是用的 JUnit, 難道 C++ 就沒(méi)有了嗎?我從網(wǎng)上找了一些方法試了下其實(shí)挺簡(jiǎn)
單的。下面我以一個(gè)示例作說(shuō)明。(如果你有準(zhǔn)備好的待測(cè)代碼可直接看創(chuàng)建單元測(cè)試項(xiàng)目那里。
)
創(chuàng)建一個(gè) Win32 空項(xiàng)目“stg”并添加下面的代碼用作測(cè)試。這里我創(chuàng)建了一個(gè)結(jié)構(gòu)體用來(lái)表示
一個(gè)物體,有X,Y,半徑這些變量,還有一個(gè)用來(lái)表示另一個(gè)物體是否在它的半徑內(nèi)的函數(shù),是
則返回1,否則為0。
【stg.h】
[cpp] view plain copy
#pragma once ?
struct SpriteType ?
{ ?
? ? SpriteType(); ?
? ? void SetValue(float, float, float, float); ?
? ? int IsShotBy(SpriteType*); ?
private: ?
? ? float posX, posY, shotRadius, sensedRadius; ?
? ? float _temp0; ?
}; ?
【stg.cpp】
[cpp] view plain copy
#include<cmath> ?
#include"stg.h" ?
??
SpriteType::SpriteType() :posX(0.0f), posY(0.0f), shotRadius(0.0f), sensedRadius(0.0f) ?
{ ?
??
} ?
??
void SpriteType::SetValue(float x, float y, float rshot, float rsensor) ?
{ ?
? ? posX = x, posY = y, shotRadius = rshot, sensedRadius = rsensor; ?
} ?
??
int SpriteType::IsShotBy(SpriteType *pOtherSprite) ?
{ ?
? ? _temp0 = sqrtf((pOtherSprite->posX - posX)*(pOtherSprite->posX - posX) +?
(pOtherSprite->posY - posY)*(pOtherSprite->posY - posY)); ?
? ? return (int)(_temp0 < shotRadius); ?
} ?
(為了簡(jiǎn)單我把 main 函數(shù)省略了。)
然后是創(chuàng)建單元測(cè)試項(xiàng)目。在這個(gè)解決方案中創(chuàng)建一個(gè)名為 stgTest 的單元測(cè)試工程(命名規(guī)則是
“項(xiàng)目名”+Test),
創(chuàng)建好后在引用中添加待測(cè)項(xiàng)目的引用(右鍵引用選擇“添加引用項(xiàng)目”),
點(diǎn)確定,它應(yīng)該會(huì)出現(xiàn)在單元測(cè)試工程的引用中。
然后在“stg”項(xiàng)目上右鍵選擇屬性,將其配置類(lèi)型改為靜態(tài)庫(kù)(你可以在測(cè)試完后改回為應(yīng)用程
序)
接下來(lái)找到單元測(cè)試的代碼,其文件名默認(rèn)為“unittest1.cpp”,最好也改為“stgTest.cpp”以
便區(qū)分;在這個(gè)代碼中有一個(gè)默認(rèn)的方法“TestMethod1”,它是由宏“TEST_METHOD”定義
的一個(gè)函數(shù),這個(gè)函數(shù)就是用來(lái)作測(cè)試的,運(yùn)行測(cè)試時(shí)它里面的代碼會(huì)執(zhí)行,測(cè)試結(jié)果由 Assert?
中的一系列函數(shù)決定。另外兩個(gè)
宏“TEST_METHOD_INITIALIZE”,“TEST_METHOD_CLEANUP”分別用于定義測(cè)試開(kāi)始前與
結(jié)束后執(zhí)行的函數(shù),通常將函數(shù)名定義為“SetUp”和“TearDown”(或許是因?yàn)?JUnit 中就是
這樣的?)。這里我將默認(rèn)的“TestMethod1”改為“TestShot”(命名規(guī)則為 Test+“函數(shù)名
”),然后包含頭文件 #include“..\stg\stg.h”,加入測(cè)試代碼后形成下面這個(gè)樣子:
【stgTest.cpp 中的部分代碼】
[cpp] view plain copy
namespace AmIShotTest ?
{ ? ? ? ??
? ? TEST_CLASS(UnitTest1) ?
? ? { ?
? ? private: ?
? ? ? ? SpriteType *s1, *s2; ?
? ? public: ?
? ? ? ? TEST_METHOD_INITIALIZE(SetUp) ?
? ? ? ? { ?
? ? ? ? ? ? s1 = new SpriteType(); ?
? ? ? ? ? ? s2 = new SpriteType(); ?
? ? ? ? ? ? Logger::WriteMessage("Test initialized.\n"); //用于輸出信息 ?
? ? ? ? } ?
??
? ? ? ? TEST_METHOD_CLEANUP(TearDown) ?
? ? ? ? { ?
? ? ? ? ? ? delete s1; ?
? ? ? ? ? ? delete s2; ?
? ? ? ? ? ? Logger::WriteMessage("Test completed.\n"); ?
? ? ? ? } ?
??
? ? ? ? TEST_METHOD(TestShot) ?
? ? ? ? { ?
? ? ? ? ? ? s1->SetValue(2.0f, 3.0f, 1.2f, 1.5f); ?
? ? ? ? ? ? s2->SetValue(2.2f, 3.1f, 0.0f, 0.0f); ?
? ? ? ? ? ? Assert::AreEqual<int>(1, s1->IsShotBy(s2)); ?
? ? ? ? ? ? Logger::WriteMessage("Shot tested.\n"); ?
? ? ? ? } ?
? ? }; ?
} ?
上面 AreEqual 那句中,模板填入待測(cè)值的類(lèi)型,第一個(gè)參數(shù)為預(yù)測(cè)值,第二個(gè)為實(shí)際運(yùn)行的結(jié)果
,若相等則測(cè)試成功,否則為失敗。寫(xiě)好代碼后選擇生成 stgTest 項(xiàng)目,然后在測(cè)試,窗口中打開(kāi)
測(cè)試資源管理器,如果生成沒(méi)有問(wèn)題就可以在這里看到測(cè)試項(xiàng)了,選擇運(yùn)行即可。
既然學(xué)會(huì)了如何使用單元測(cè)試,那么以后寫(xiě)代碼就不要忘了用哦!這樣你的代碼中的BUG就會(huì)越
來(lái)越少了!
========
輕松編寫(xiě) C++ 單元測(cè)試
http://www.ibm.com/developerworks/cn/linux/l-cn-cppunittest/介紹全新單元測(cè)試框架組合: googletest 與 googlemock
googletest 與 googlemock 是 Google 公司于 2008 年發(fā)布的兩套用于單元測(cè)試的應(yīng)用框架,本
文將向讀者介紹如何應(yīng)用這兩套應(yīng)用框架輕松編寫(xiě) C++ 單元測(cè)試代碼。以下討論基于 gtest-
1.2.1 及 gmock-1.0.0 。
單元測(cè)試概述
測(cè)試并不只是測(cè)試工程師的責(zé)任,對(duì)于開(kāi)發(fā)工程師,為了保證發(fā)布給測(cè)試環(huán)節(jié)的代碼具有足夠好的
質(zhì)量( Quality ),為所編寫(xiě)的功能代碼編寫(xiě)適量的單元測(cè)試是十分必要的。
單元測(cè)試( Unit Test ,模塊測(cè)試)是開(kāi)發(fā)者編寫(xiě)的一小段代碼,用于檢驗(yàn)被測(cè)代碼的一個(gè)很小的
、很明確的功能是否正確,通過(guò)編寫(xiě)單元測(cè)試可以在編碼階段發(fā)現(xiàn)程序編碼錯(cuò)誤,甚至是程序設(shè)計(jì)
錯(cuò)誤。
單元測(cè)試不但可以增加開(kāi)發(fā)者對(duì)于所完成代碼的自信,同時(shí),好的單元測(cè)試用例往往可以在回歸測(cè)
試的過(guò)程中,很好地保證之前所發(fā)生的修改沒(méi)有破壞已有的程序邏輯。因此,單元測(cè)試不但不會(huì)成
為開(kāi)發(fā)者的負(fù)擔(dān),反而可以在保證開(kāi)發(fā)質(zhì)量的情況下,加速迭代開(kāi)發(fā)的過(guò)程。
對(duì)于單元測(cè)試框架,目前最為大家所熟知的是 JUnit 及其針對(duì)各語(yǔ)言的衍生產(chǎn)品, C++ 語(yǔ)言所對(duì)
應(yīng)的 JUnit 系單元測(cè)試框架就是 CppUnit 。但是由于 CppUnit 的設(shè)計(jì)嚴(yán)格繼承自 JUnit ,而沒(méi)
有充分考慮 C++ 與 Java 固有的差異(主要是由于 C++ 沒(méi)有反射機(jī)制,而這是 JUnit 設(shè)計(jì)的基
礎(chǔ)),在 C++ 中使用 CppUnit 進(jìn)行單元測(cè)試顯得十分繁瑣,這一定程度上制約了 CppUnit 的普
及。筆者在這里要跟大家介紹的是一套由 google 發(fā)布的開(kāi)源單元測(cè)試框架( Testing?
Framework ): googletest 。
應(yīng)用 googletest 編寫(xiě)單元測(cè)試代碼
googletest 是由 Google 公司發(fā)布,且遵循 New BSD License (可用作商業(yè)用途)的開(kāi)源項(xiàng)目
,并且 googletest 可以支持絕大多數(shù)大家所熟知的平臺(tái)。與 CppUnit 不同的是: googletest 可
以自動(dòng)記錄下所有定義好的測(cè)試,不需要用戶(hù)通過(guò)列舉來(lái)指明哪些測(cè)試需要運(yùn)行。
定義單元測(cè)試
在應(yīng)用 googletest 編寫(xiě)單元測(cè)試時(shí),使用 TEST() 宏來(lái)聲明測(cè)試函數(shù)。如:
清單 1. 用 TEST() 宏聲明測(cè)試函數(shù)
TEST(GlobalConfigurationTest, configurationDataTest)?
?TEST(GlobalConfigurationTest, noConfigureFileTest)
分別針對(duì)同一程序單元 GlobalConfiguration 聲明了兩個(gè)不同的測(cè)試(Test)函數(shù),以分別對(duì)配
置數(shù)據(jù)進(jìn)行檢查( configurationDataTest ),以及測(cè)試沒(méi)有配置文件的特殊情況(?
noConfigureFileTest )。
實(shí)現(xiàn)單元測(cè)試
針對(duì)同一程序單元設(shè)計(jì)出不同的測(cè)試場(chǎng)景后(即劃分出不同的 Test 后),開(kāi)發(fā)者就可以編寫(xiě)單元
測(cè)試分別實(shí)現(xiàn)這些測(cè)試場(chǎng)景了。
在 googletest 中實(shí)現(xiàn)單元測(cè)試,可通過(guò) ASSERT_* 和 EXPECT_* 斷言來(lái)對(duì)程序運(yùn)行結(jié)果進(jìn)行檢查
。 ASSERT_* 版本的斷言失敗時(shí)會(huì)產(chǎn)生致命失敗,并結(jié)束當(dāng)前函數(shù); EXPECT_* 版本的斷言失敗
時(shí)產(chǎn)生非致命失敗,但不會(huì)中止當(dāng)前函數(shù)。因此, ASSERT_* 常常被用于后續(xù)測(cè)試邏輯強(qiáng)制依賴(lài)
的處理結(jié)果的斷言,如創(chuàng)建對(duì)象后檢查指針是否為空,若為空,則后續(xù)對(duì)象方法調(diào)用會(huì)失敗;而?
EXPECT_* 則用于即使失敗也不會(huì)影響后續(xù)測(cè)試邏輯的處理結(jié)果的斷言,如某個(gè)方法返回結(jié)果的多
個(gè)屬性的檢查。
googletest 中定義了如下的斷言:
表 1: googletest 定義的斷言( Assert )
基本斷言 二進(jìn)制比較 字符串比較
ASSERT_TRUE(condition);
EXPECT_TRUE(condition);
condition為真
ASSERT_FALSE(condition);
EXPECT_FALSE(condition);
condition為假 ASSERT_EQ(expected,actual);
EXPECT_EQ(expected,actual);
expected==actual
ASSERT_NE(val1,val2);
EXPECT_NE(val1,val2);
val1!=val2
ASSERT_LT(val1,val2);
EXPECT_LT(val1,val2);
val1<val2
ASSERT_LE(val1,val2);
EXPECT_LE(val1,val2);
val1<=val2
ASSERT_GT(val1,val2);
EXPECT_GT(val1,val2);
val1>val2
ASSERT_GE(val1,val2);
EXPECT_GE(val1,val2);
val1>=val2 ASSERT_STREQ(expected_str,actual_str);
EXPECT_STREQ(expected_str,actual_str);
兩個(gè) C 字符串有相同的內(nèi)容
ASSERT_STRNE(str1,str2);
EXPECT_STRNE(str1,str2);
兩個(gè) C 字符串有不同的內(nèi)容
ASSERT_STRCASEEQ(expected_str,actual_str);
EXPECT_STRCASEEQ(expected_str,actual_str);
兩個(gè) C 字符串有相同的內(nèi)容,忽略大小寫(xiě)
ASSERT_STRCASENE(str1,str2);
EXPECT_STRCASENE(str1,str2);
兩個(gè) C 字符串有不同的內(nèi)容,忽略大小寫(xiě)
下面的實(shí)例演示了上面部分?jǐn)嘌缘氖褂?#xff1a;
清單 2. 一個(gè)較完整的 googletest 單元測(cè)試實(shí)例
// Configure.h?
?#pragma once?
?#include <string>?
?#include <vector>?
?class Configure?
?{?
?private:?
? ? std::vector<std::string> vItems;?
?public:?
? ? int addItem(std::string str);?
? ? std::string getItem(int index);?
? ? int getSize();?
?};?
?// Configure.cpp?
?#include "Configure.h"?
?#include <algorithm>?
?/**?
?* @brief Add an item to configuration store. Duplicate item will be ignored?
?* @param str item to be stored?
?* @return the index of added configuration item?
?*/?
?int Configure::addItem(std::string str)?
?{?
std::vector<std::string>::const_iterator vi=std::find(vItems.begin(), vItems.end(), str);?
? ? if (vi != vItems.end())?
? ? ? ? return vi - vItems.begin();?
? ? vItems.push_back(str);?
? ? return vItems.size() - 1;?
?}?
?/**?
?* @brief Return the configure item at specified index.?
?* If the index is out of range, "" will be returned?
?* @param index the index of item?
?* @return the item at specified index?
?*/?
?std::string Configure::getItem(int index)?
?{?
? ? if (index >= vItems.size())?
? ? ? ? return "";?
? ? else?
? ? ? ? return vItems.at(index);?
?}?
?/// Retrieve the information about how many configuration items we have had?
?int Configure::getSize()?
?{?
? ? return vItems.size();?
?}?
?// ConfigureTest.cpp?
?#include <gtest/gtest.h>?
?#include "Configure.h"?
?TEST(ConfigureTest, addItem)?
?{?
? ? // do some initialization?
? ? Configure* pc = new Configure();?
? ??
? ? // validate the pointer is not null?
? ? ASSERT_TRUE(pc != NULL);?
? ? // call the method we want to test?
? ? pc->addItem("A");?
? ? pc->addItem("B");?
? ? pc->addItem("A");?
? ? // validate the result after operation?
? ? EXPECT_EQ(pc->getSize(), 2);?
? ? EXPECT_STREQ(pc->getItem(0).c_str(), "A");?
? ? EXPECT_STREQ(pc->getItem(1).c_str(), "B");?
? ? EXPECT_STREQ(pc->getItem(10).c_str(), "");?
? ? delete pc;?
?}
運(yùn)行單元測(cè)試
在實(shí)現(xiàn)完單元測(cè)試的測(cè)試邏輯后,可以通過(guò) RUN_ALL_TESTS() 來(lái)運(yùn)行它們,如果所有測(cè)試成功,
該函數(shù)返回 0,否則會(huì)返回 1 。 RUN_ALL_TESTS() 會(huì)運(yùn)行你鏈接到的所有測(cè)試――它們可以來(lái)自不
同的測(cè)試案例,甚至是來(lái)自不同的文件。
因此,運(yùn)行 googletest 編寫(xiě)的單元測(cè)試的一種比較簡(jiǎn)單可行的方法是:
為每一個(gè)被測(cè)試的 class 分別創(chuàng)建一個(gè)測(cè)試文件,并在該文件中編寫(xiě)針對(duì)這一 class 的單元測(cè)試;
編寫(xiě)一個(gè) Main.cpp 文件,并在其中包含以下代碼,以運(yùn)行所有單元測(cè)試:
清單 3. 初始化 googletest 并運(yùn)行所有測(cè)試
#include <gtest/gtest.h>?
?int main(int argc, char** argv) {?
? ? testing::InitGoogleTest(&argc, argv);?
? ? // Runs all tests using Google Test.?
? ? return RUN_ALL_TESTS();?
?}
最后,將所有測(cè)試代碼及 Main.cpp 編譯并鏈接到目標(biāo)程序中。
此外,在運(yùn)行可執(zhí)行目標(biāo)程序時(shí),可以使用 --gtest_filter 來(lái)指定要執(zhí)行的測(cè)試用例,如:
./foo_test 沒(méi)有指定filter,運(yùn)行所有測(cè)試;
./foo_test --gtest_filter=* 指定filter為*,運(yùn)行所有測(cè)試;
./foo_test --gtest_filter=FooTest.* 運(yùn)行測(cè)試用例FooTest的所有測(cè)試;
./foo_test --gtest_filter=*Null*:*Constructor* 運(yùn)行所有全名(即測(cè)試用例名 + “ . ” + 測(cè)試名
,如 GlobalConfigurationTest.noConfigureFileTest)含有"Null"或"Constructor"的測(cè)試;
./foo_test --gtest_filter=FooTest.*-FooTest.Bar 運(yùn)行測(cè)試用例FooTest的所有測(cè)試,但不包括
FooTest.Bar。
這一特性在包含大量測(cè)試用例的項(xiàng)目中會(huì)十分有用。
回頁(yè)首
應(yīng)用 googlemock 編寫(xiě) Mock Objects
很多 C++ 程序員對(duì)于 Mock Objects (模擬對(duì)象)可能比較陌生,模擬對(duì)象主要用于模擬整個(gè)應(yīng)
用程序的一部分。在單元測(cè)試用例編寫(xiě)過(guò)程中,常常需要編寫(xiě)模擬對(duì)象來(lái)隔離被測(cè)試單元的“下游
”或“上游”程序邏輯或環(huán)境,從而達(dá)到對(duì)需要測(cè)試的部分進(jìn)行隔離測(cè)試的目的。
例如,要對(duì)一個(gè)使用數(shù)據(jù)庫(kù)的對(duì)象進(jìn)行單元測(cè)試,安裝、配置、啟動(dòng)數(shù)據(jù)庫(kù)、運(yùn)行測(cè)試,然后再卸
裝數(shù)據(jù)庫(kù)的方式,不但很麻煩,過(guò)于耗時(shí),而且容易由于環(huán)境因素造成測(cè)試失敗,達(dá)不到單元測(cè)試
的目的。模仿對(duì)象提供了解決這一問(wèn)題的方法:模仿對(duì)象符合實(shí)際對(duì)象的接口,但只包含用來(lái)“欺
騙”測(cè)試對(duì)象并跟蹤其行為的必要代碼。因此,其實(shí)現(xiàn)往往比實(shí)際實(shí)現(xiàn)類(lèi)簡(jiǎn)單很多。
為了配合單元測(cè)試中對(duì) Mocking Framework 的需要, Google 開(kāi)發(fā)并于 2008 年底開(kāi)放了:?
googlemock 。與 googletest 一樣, googlemock 也是遵循 New BSD License (可用作商業(yè)
用途)的開(kāi)源項(xiàng)目,并且 googlemock 也可以支持絕大多數(shù)大家所熟知的平臺(tái)。
注 1:在 Windows 平臺(tái)上編譯 googlemock
對(duì)于 Linux 平臺(tái)開(kāi)發(fā)者而言,編譯 googlemock 可能不會(huì)遇到什么麻煩;但是對(duì)于 Windows 平
臺(tái)的開(kāi)發(fā)者,由于 Visual Studio 還沒(méi)有提供 tuple ( C++0x TR1 中新增的數(shù)據(jù)類(lèi)型)的實(shí)現(xiàn),
編譯 googlemock 需要為其指定一個(gè) tuple 類(lèi)型的實(shí)現(xiàn)。著名的開(kāi)源 C++ 程序庫(kù) boost 已經(jīng)提
供了 tr1 的實(shí)現(xiàn),因此,在 Windows 平臺(tái)下可以使用 boost 來(lái)編譯 googlemock 。為此,需要
修改 %GMOCK_DIR%/msvc/gmock_config.vsprops ,設(shè)定其中 BoostDir 到 boost 所在的目
錄,如:
<UserMacro?
? ? Name="BoostDir"?
? ? Value="$(BOOST_DIR)"?
?/>
其中 BOOST_DIR 是一個(gè)環(huán)境變量,其值為 boost 庫(kù)解壓后所在目錄。
對(duì)于不希望在自己的開(kāi)發(fā)環(huán)境上解包 boost 庫(kù)的開(kāi)發(fā)者,在 googlemock 的網(wǎng)站上還提供了一個(gè)
從 boost 庫(kù)中單獨(dú)提取出來(lái)的 tr1 的實(shí)現(xiàn),可將其下載后將解壓目錄下的 boost 目錄拷貝到?
%GMOCK_DIR% 下(這種情況下,請(qǐng)勿修改上面的配置項(xiàng);建議對(duì) boost 不甚了解的開(kāi)發(fā)者采
用后面這種方式)。
在應(yīng)用 googlemock 來(lái)編寫(xiě) Mock 類(lèi)輔助單元測(cè)試時(shí),需要:
編寫(xiě)一個(gè) Mock Class (如 class MockTurtle ),派生自待 Mock 的抽象類(lèi)(如 class Turtle )
;
對(duì)于原抽象類(lèi)中各待 Mock 的 virtual 方法,計(jì)算出其參數(shù)個(gè)數(shù) n ;
在 Mock Class 類(lèi)中,使用 MOCK_METHODn() (對(duì)于 const 方法則需用?
MOCK_CONST_METHODn() )宏來(lái)聲明相應(yīng)的 Mock 方法,其中第一個(gè)參數(shù)為待 Mock 方法
的方法名,第二個(gè)參數(shù)為待 Mock 方法的類(lèi)型。如下:
清單 4. 使用 MOCK_METHODn 聲明 Mock 方法
#include <gmock/gmock.h> ?// Brings in Google Mock.?
?class MockTurtle : public Turtle {?
? ? 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());?
?};
在完成上述工作后,就可以開(kāi)始編寫(xiě)相應(yīng)的單元測(cè)試用例了。在編寫(xiě)單元測(cè)試時(shí),可通過(guò)?
ON_CALL 宏來(lái)指定 Mock 方法被調(diào)用時(shí)的行為,或 EXPECT_CALL 宏來(lái)指定 Mock 方法被調(diào)用
的次數(shù)、被調(diào)用時(shí)需執(zhí)行的操作等,并對(duì)執(zhí)行結(jié)果進(jìn)行檢查。如下:
清單 5. 使用 ON_CALL 及 EXPECT_CALL 宏
using testing::Return; ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?// #1,必要的聲明
?TEST(BarTest, DoesThis) {?
? ? MockFoo foo; ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?// #2,創(chuàng)建 Mock 對(duì)象
? ? ON_CALL(foo, GetSize()) ? ? ? ? ? ? ? ? ? ? ? ? // #3,設(shè)定 Mock 對(duì)象默認(rèn)的行為(可選)
? ? ? ? .WillByDefault(Return(1));?
? ? // ... other default actions ...?
? ? EXPECT_CALL(foo, Describe(5)) ? ? ? ? ? ? ? ? ? // #4,設(shè)定期望對(duì)象被訪(fǎng)問(wèn)的方式及其響應(yīng)
? ? ? ? .Times(3)?
? ? ? ? .WillRepeatedly(Return("Category 5"));?
? ? // ... other expectations ...?
? ? EXPECT_EQ("good", MyProductionFunction(&foo)); ?
? ? // #5,操作 Mock 對(duì)象并使用 googletest 提供的斷言驗(yàn)證處理結(jié)果
?} ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?
?// #6,當(dāng) Mock 對(duì)象被析構(gòu)時(shí), googlemock 會(huì)對(duì)結(jié)果進(jìn)行驗(yàn)證以判斷其行為是否與所有設(shè)定
的預(yù)期一致
其中, WillByDefault 用于指定 Mock 方法被調(diào)用時(shí)的默認(rèn)行為; Return 用于指定方法被調(diào)用
時(shí)的返回值; Times 用于指定方法被調(diào)用的次數(shù); WillRepeatedly 用于指定方法被調(diào)用時(shí)重復(fù)的
行為。
對(duì)于未通過(guò) EXPECT_CALL 聲明而被調(diào)用的方法,或不滿(mǎn)足 EXPECT_CALL 設(shè)定條件的 Mock 方
法調(diào)用, googlemock 會(huì)輸出警告信息。對(duì)于前一種情況下的警告信息,如果開(kāi)發(fā)者并不關(guān)心這
些信息,可以使用 Adapter 類(lèi)模板 NiceMock 避免收到這一類(lèi)警告信息。如下:
清單 6. 使用 NiceMock 模板
testing::NiceMock<MockFoo> nice_foo;
在筆者開(kāi)發(fā)的應(yīng)用中,被測(cè)試單元會(huì)通過(guò)初始化時(shí)傳入的上層應(yīng)用的接口指針,產(chǎn)生大量的處理成
功或者失敗的消息給上層應(yīng)用,而開(kāi)發(fā)者在編寫(xiě)單元測(cè)試時(shí)并不關(guān)心這些消息的內(nèi)容,通過(guò)使用?
NiceMock 可以避免為不關(guān)心的方法編寫(xiě) Mock 代碼(注意:這些方法仍需在 Mock 類(lèi)中聲明,
否則 Mock 類(lèi)會(huì)被當(dāng)作 abstract class 而無(wú)法實(shí)例化)。
與 googletest 一樣,在編寫(xiě)完單元測(cè)試后,也需要編寫(xiě)一個(gè)如下的入口函數(shù)來(lái)執(zhí)行所有的測(cè)試:
清單 7. 初始化 googlemock 并運(yùn)行所有測(cè)試
#include <gtest/gtest.h>?
?#include <gmock/gmock.h>?
?int main(int argc, char** argv) {?
? ? testing::InitGoogleMock(&argc, argv);?
? ? // Runs all tests using Google Test.?
? ? return RUN_ALL_TESTS();?
?}
下面的代碼演示了如何使用 googlemock 來(lái)創(chuàng)建 Mock Objects 并設(shè)定其行為,從而達(dá)到對(duì)核心
類(lèi) AccountService 的 transfer (轉(zhuǎn)賬)方法進(jìn)行單元測(cè)試的目的。由于 AccountManager 類(lèi)
的具體實(shí)現(xiàn)涉及數(shù)據(jù)庫(kù)等復(fù)雜的外部環(huán)境,不便直接使用,因此,在編寫(xiě)單元測(cè)試時(shí),我們用?
MockAccountManager 替換了具體的 AccountManager 實(shí)現(xiàn)。
清單 8. 待測(cè)試的程序邏輯
// Account.h?
?// basic application data class?
?#pragma once?
?#include <string>?
?class Account?
?{?
?private:?
? ? std::string accountId;?
? ? long balance;?
?public:?
? ? Account();?
? ? Account(const std::string& accountId, long initialBalance);?
? ? void debit(long amount);?
? ? void credit(long amount);?
? ? long getBalance() const;?
? ? std::string getAccountId() const;?
?};?
?// Account.cpp?
?#include "Account.h"?
?Account::Account()?
?{?
?}?
?Account::Account(const std::string& accountId, long initialBalance)?
?{?
? ? this->accountId = accountId;?
? ? this->balance = initialBalance;?
?}?
?void Account::debit(long amount)?
?{?
? ? this->balance -= amount;?
?}?
?void Account::credit(long amount)?
?{?
? ? this->balance += amount;?
?}?
?long Account::getBalance() const?
?{?
? ? return this->balance;?
?}?
?std::string Account::getAccountId() const?
?{?
? ? return accountId;?
?}?
?// AccountManager.h?
?// the interface of external services which should be mocked?
?#pragma once?
?#include <string>?
?#include "Account.h"?
?class AccountManager?
?{?
?public:?
? ? virtual Account findAccountForUser(const std::string& userId) = 0;?
? ? virtual void updateAccount(const Account& account) = 0;?
?};?
?// AccountService.h?
?// the class to be tested?
?#pragma once?
?#include <string>?
?#include "Account.h"?
?#include "AccountManager.h"?
?class AccountService?
?{?
?private:?
? ? AccountManager* pAccountManager;?
?public:?
? ? AccountService();?
? ? void setAccountManager(AccountManager* pManager);?
? ? void transfer(const std::string& senderId,?
? ? ? ? ? ? ? ?const std::string& beneficiaryId, long amount);?
?};?
?// AccountService.cpp?
?#include "AccountService.h"?
?AccountService::AccountService()?
?{?
? ? this->pAccountManager = NULL;?
?}?
?void AccountService::setAccountManager(AccountManager* pManager)?
?{?
? ? this->pAccountManager = pManager;?
?}?
?void AccountService::transfer(const std::string& senderId,?
? ? ? ? ? ? ? ? ? const std::string& beneficiaryId, long amount)?
?{?
? ? Account sender = this->pAccountManager->findAccountForUser(senderId);?
? ? Account beneficiary = this->pAccountManager->findAccountForUser(beneficiaryId);?
? ? sender.debit(amount);?
? ? beneficiary.credit(amount);?
? ? this->pAccountManager->updateAccount(sender);?
? ? this->pAccountManager->updateAccount(beneficiary);?
?}
清單 9. 相應(yīng)的單元測(cè)試
// AccountServiceTest.cpp?
?// code to test AccountService?
?#include <map>?
?#include <string>?
?#include <gtest/gtest.h>?
?#include <gmock/gmock.h>?
?#include "../Account.h"?
?#include "../AccountService.h"?
?#include "../AccountManager.h"?
?// MockAccountManager, mock AccountManager with googlemock?
?class MockAccountManager : public AccountManager?
?{?
?public:?
? ? MOCK_METHOD1(findAccountForUser, Account(const std::string&));?
? ? MOCK_METHOD1(updateAccount, void(const Account&));?
?};?
?// A facility class acts as an external DB?
?class AccountHelper?
?{?
?private:?
? ? std::map<std::string, Account> mAccount;?
? ? ? ? ? ? ?// an internal map to store all Accounts for test?
?public:?
? ? AccountHelper(std::map<std::string, Account>& mAccount);?
? ? void updateAccount(const Account& account);?
? ? Account findAccountForUser(const std::string& userId);?
?};?
?AccountHelper::AccountHelper(std::map<std::string, Account>& mAccount)?
?{?
? ? this->mAccount = mAccount;?
?}?
?void AccountHelper::updateAccount(const Account& account)?
?{?
? ? this->mAccount[account.getAccountId()] = account;?
?}?
?Account AccountHelper::findAccountForUser(const std::string& userId)?
?{?
? ? if (this->mAccount.find(userId) != this->mAccount.end())?
? ? ? ? return this->mAccount[userId];?
? ? else?
? ? ? ? return Account();?
?}?
?// Test case to test AccountService?
?TEST(AccountServiceTest, transferTest)?
?{?
? ? std::map<std::string, Account> mAccount;?
? ? mAccount["A"] = Account("A", 3000);?
? ? mAccount["B"] = Account("B", 2000);?
? ? AccountHelper helper(mAccount);?
? ? MockAccountManager* pManager = new MockAccountManager();?
? ? // specify the behavior of MockAccountManager?
? ? // always invoke AccountHelper::findAccountForUser?
? ? ?// when AccountManager::findAccountForUser is invoked?
? ? EXPECT_CALL(*pManager, findAccountForUser(testing::_)).WillRepeatedly(?
? ? ? ? testing::Invoke(&helper, &AccountHelper::findAccountForUser));?
? ? // always invoke AccountHelper::updateAccount?
? ? //when AccountManager::updateAccount is invoked?
? ? EXPECT_CALL(*pManager, updateAccount(testing::_)).WillRepeatedly(?
? ? ? ? testing::Invoke(&helper, &AccountHelper::updateAccount));?
? ? AccountService as;?
? ? // inject the MockAccountManager object into AccountService?
? ? as.setAccountManager(pManager);?
? ? // operate AccountService?
? ? as.transfer("A", "B", 1005);?
? ? // check the balance of Account("A") and Account("B") to?
? ? //verify that AccountService has done the right job?
? ? EXPECT_EQ(1995, helper.findAccountForUser("A").getBalance());?
? ? EXPECT_EQ(3005, helper.findAccountForUser("B").getBalance());?
? ? delete pManager;?
?}?
?// Main.cpp?
?#include <gtest/gtest.h>?
?#include <gmock/gmock.h>?
?int main(int argc, char** argv) {?
? ? testing::InitGoogleMock(&argc, argv);?
? ? // Runs all tests using Google Test.?
? ? return RUN_ALL_TESTS();?
?}
注 2:上述范例工程詳見(jiàn)附件。要編譯該工程,請(qǐng)讀者自行添加環(huán)境變量 GTEST_DIR 、?
GMOCK_DIR ,分別指向 googletest 、 googlemock 解壓后所在目錄;對(duì)于 Windows 開(kāi)發(fā)者
,還需要將 %GMOCK_DIR%/msvc/gmock_config.vsprops 通過(guò) View->Property Manager 添
加到工程中,并將 gmock.lib 拷貝到工程目錄下。
通過(guò)上面的實(shí)例可以看出, googlemock 為開(kāi)發(fā)者設(shè)定 Mock 類(lèi)行為,跟蹤程序運(yùn)行過(guò)程及結(jié)果
,提供了豐富的支持。但與此同時(shí),應(yīng)用程序也應(yīng)該盡量降低應(yīng)用代碼間的耦合度,使得單元測(cè)試
可以很容易對(duì)被測(cè)試單元進(jìn)行隔離(如上例中, AccountService 必須提供了相應(yīng)的方法以支持?
AccountManager 的替換)。關(guān)于如何通過(guò)應(yīng)用設(shè)計(jì)模式來(lái)降低應(yīng)用代碼間的耦合度,從而編寫(xiě)
出易于單元測(cè)試的代碼,請(qǐng)參考本人的另一篇文章《應(yīng)用設(shè)計(jì)模式編寫(xiě)易于單元測(cè)試的代碼》(?
developerWorks , 2008 年 7 月)。
注 3:此外,開(kāi)發(fā)者也可以直接通過(guò)繼承被測(cè)試類(lèi),修改與外圍環(huán)境相關(guān)的方法的實(shí)現(xiàn),達(dá)到對(duì)其
核心方法進(jìn)行單元測(cè)試的目的。但由于這種方法直接改變了被測(cè)試類(lèi)的行為,同時(shí),對(duì)被測(cè)試類(lèi)自
身的結(jié)構(gòu)有一些要求,因此,適用范圍比較小,筆者也并不推薦采用這種原始的 Mock 方式來(lái)進(jìn)
行單元測(cè)試。
回頁(yè)首
總結(jié)
Googletest 與 googlemock 的組合,很大程度上簡(jiǎn)化了開(kāi)發(fā)者進(jìn)行 C++ 應(yīng)用程序單元測(cè)試的編
碼工作,使得單元測(cè)試對(duì)于 C++ 開(kāi)發(fā)者也可以變得十分輕松;同時(shí), googletest 及?
googlemock 目前仍在不斷改進(jìn)中,相信隨著其不斷發(fā)展,這一 C++ 單元測(cè)試的全新組合將變
得越來(lái)越成熟、越來(lái)越強(qiáng)大,也越來(lái)越易用。
========
總結(jié)
以上是生活随笔為你收集整理的C++单元测试学习总结9的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: SQL连接学习总结
- 下一篇: s3c2440移植MQTT