Google Mock(Gmock)简单使用和源码分析——简单使用
? ? ? ? 初識Gmock是之前分析GTest源碼時,它的源碼和GTest源碼在同一個代碼倉庫中(https://github.com/google/googletest)。本文我將以目前最新的Gmock1.7版本為范例,分析其實現原理。(轉載請指明出于breaksoftware的csdn博客)
? ? ? ? Gmock是google開發的一套輔助測試的工具,它往往和GTest結合在一起使用。在實際工作中,一個人不可能完成整條線的開發工作。于是我們會在約定接口的前提下,各自完成各自的模塊。自己的模塊開發完之后,我們需要自測。但是這個時候別人的模塊可能還沒完成,那么我們就需要模擬約定的接口進行自測。Gmock就是一個強大的模擬接口的工具。
使用方法
? ? ? ? 首先我們講解一下其主要的使用方法。目前網絡上有一篇寫的不錯的使用說明《轉一篇小亮同學的google mock分享》,如果大家想了解其詳細的使用方法,可以參閱這篇文章。如果只是想簡單的了解其使用并理解其實現原理,可以先參閱本文。
? ? ? ? 我們假設一個支付場景邏輯開發業務。我們開發復雜的業務模塊,而團隊其他成員開發用戶行為模塊。他們和我們約定了如下接口
class User {
public:User() {};~User() {};
public:// 登錄virtual bool Login(const std::string& username, const std::string& password) = 0;// 支付virtual bool Pay(int money) = 0;// 是否登錄virtual bool Online() = 0;
};
? ? ? ? 我們的業務模塊要讓用戶登錄,并發起支付行為。于是我們的代碼如下
class Biz {
public:void SetUser(User* user) {_user = user;}std::string pay(const std::string& username, const std::string& password, int money) {std::string ret;if (!_user) {ret = "pointer is null.";return ret;}if (!_user->Online()) {ret = "logout status.";// 尚未登錄,要求登錄if (!_user->Login(username, password)) {// 登錄失敗ret += "login error.";return ret;} else {// 登錄成功ret += "login success.";}} else {// 已登錄ret = "login.status";}if (!_user->Pay(money)) {ret += "pay error.";} else {ret += "pay success.";}return ret;}private:User* _user;
};
? ? ? ? 這段邏輯的口語描述就是:我們先看看用戶登錄了沒,如果沒有登錄則要求用戶登錄。如果登錄失敗,則直接返回;如果登錄成功,則執行支付行為。最后將流程的狀態輸出。
? ? ? ? 那我們如何使用Gmock輔助測試呢?
?? ? ? ?第一步我們需要Mock接口類
class TestUser : public User {
public:MOCK_METHOD2(Login, bool(const std::string&, const std::string&));MOCK_METHOD1(Pay, bool(int));MOCK_METHOD0(Online, bool());
};
? ? ? ? 可以發現其方法的聲明非常有規律。MOCK_METHOD后跟一個數字,該數字表明需要mock的函數有幾個參數(除去this)。像第5行,Online()方法沒有入參,則使用的是MOCK_METHOD0。而又兩個參數的Login使用的是MOCK_METHOD2。MOCK_METHOD系列宏的第一個參數是函數名,第二個參數是函數指針的類型。
? ? ? ? 第二步,我們就可以設計測試場景了。在設計場景之前,我們先看一些Gmock的方法
// EXPECT_CALL(mock_object, Method(argument-matchers))
// .With(multi-argument-matchers)
// .Times(cardinality)
// .InSequence(sequences)
// .After(expectations)
// .WillOnce(action)
// .WillRepeatedly(action)
// .RetiresOnSaturation();
//
// where all clauses are optional, and .InSequence()/.After()/
// .WillOnce() can appear any number of times.
? ? ? ? 我們可以使用
- EXPECT_CALL聲明一個調用期待,就是我們期待這個對象的這個方法按什么樣的邏輯去執行。
- mock_object是我們mock的對象,上例中就是TestUser的一個對象。
- Method是mock對象中的mock方法,它的參數可以通過argument-matchers規則去匹配。
- With是多個參數的匹配方式指定。
- Times表示這個方法可以被執行多少次。如果超過這個次數,則按默認值返回了。
- InSequence用于指定函數執行的順序。它是通過同一序列中聲明期待的順序確定的。
- After方法用于指定某個方法只能在另一個方法之后執行。
- WillOnce表示執行一次方法時,將執行其參數action的方法。一般我們使用Return方法,用于指定一次調用的輸出。
- WillRepeatedly表示一直調用一個方法時,將執行其參數action的方法。需要注意下它和WillOnce的區別,WillOnce是一次,WillRepeatedly是一直。
- RetiresOnSaturation用于保證期待調用不會被相同的函數的期待所覆蓋。
? ? ? ? 我們就可以基于以上的邏輯組織測試邏輯。
? ? ? ? 先舉一個例子,我們要求Online在第一調用時返回true,之后都返回false。Login一直返回false。Pay一直返回true。也就是說用戶第一次支付前處于在線狀態,并可以支付成功。而第二次將因為不處于在線狀態,要觸發登錄行為,而登錄行為將失敗。我們看下這個邏輯該怎么寫
{TestUser test_user;EXPECT_CALL(test_user, Online()).WillOnce(testing::Return(true));EXPECT_CALL(test_user, Login(_,_)).WillRepeatedly(testing::Return(false));EXPECT_CALL(test_user, Pay(_)).WillRepeatedly(testing::Return(true));Biz biz;biz.SetUser(&test_user);std::string admin_ret = biz.pay("user", "", 1);admin_ret = biz.pay("user", "", 1);}
? ? ? ? 第4行的意思是Online在調用一次后返回true,之后的調用返回默認的false。第5行意思是Login操作一直返回false,其中Login的參數是兩個下劃線(_),它是通配符,就是對任何輸入參數都按之后要求執行。第6行意思是Pay操作總是返回true。那么我們在第10行和第11行分別得到如下輸出
login status.pay success.
logout status.login error.
? ? ? ? 可以見得輸出符合我們的預期。
? ? ? ? 我們再看一種場景,這個場景我們使用了函數參數的過濾。比如我們不允許admin的用戶通過我們方法登錄并支付,則可以這么寫
{TestUser test_user;EXPECT_CALL(test_user, Online()).WillOnce(testing::Return(false));EXPECT_CALL(test_user, Login("admin",_)).WillRepeatedly(testing::Return(false));Biz biz;biz.SetUser(&test_user);std::string admin_ret = biz.pay("admin", "", 1);}
? ? ? ? 第3行表示,如果Login的第一個參數是admin,則總是返回false。于是07行返回是
logout status.login error.
? ? ? ? 那么如果不是admin的用戶登錄,則返回成功,這個案例要怎么寫呢?
{TestUser test_user;EXPECT_CALL(test_user, Online()).WillOnce(testing::Return(false));EXPECT_CALL(test_user, Login(StrNe("admin"),_)).WillRepeatedly(testing::Return(true));EXPECT_CALL(test_user, Pay(_)).WillRepeatedly(testing::Return(true));Biz biz;biz.SetUser(&test_user);std::string user_ret = biz.pay("user", "", 1);}
? ? ? ? 03行使用了StrNe的比較函數,即Login的第一個參數不等于admin時,總是返回true。08行的輸出是
logout status.login success.pay success.
? ? ? ? 我們再看一個例子,我們要求非admin用戶登錄成功后,只能成功支付2次,之后的支付都失敗。這個案例可以這么寫
{TestUser test_user;EXPECT_CALL(test_user, Online()).WillOnce(testing::Return(false));EXPECT_CALL(test_user, Login(StrNe("admin"),_)).WillRepeatedly(testing::Return(true));EXPECT_CALL(test_user, Pay(_)).Times(5).WillOnce(testing::Return(true)).WillOnce(testing::Return(true)).WillRepeatedly(testing::Return(false));Biz biz;biz.SetUser(&test_user);std::string user_ret = biz.pay("user", "", 1);user_ret = biz.pay("user", "", 1);user_ret = biz.pay("user", "", 1);}
? ? ? ? 第4行我們使用Times函數,它的參數5表示該函數期待被調用5次,從第6次的調用開始,返回默認值。Times函數后面跟著兩個WillOnce,其行為都是返回true。這個可以解讀為第一次和第二次調用Pay方法時,返回成功。最后的WillRepeatedly表示之后的對Pay的調用都返回false。我們看下執行的結果
logout status.login success.pay success.
logout status.login success.pay success.
logout status.login success.pay error.
? ? ? ? 從結果上看,前兩次都支付成功了,而第三次失敗。符合我們的期待。
? ? ? ? 下一節,我們將閱讀Gmock的源碼,分析其實現脈絡和原理。
總結
以上是生活随笔為你收集整理的Google Mock(Gmock)简单使用和源码分析——简单使用的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: GoogleLog(GLog)源码分析
- 下一篇: Google Mock(Gmock)简单