就在昨天終於做了gtest的分享,我的預研工作終於結束了,感覺離我辭職的日子不遠了,畢竟是專注java二百年啊,要告別實習啦。。
這篇是GoogleMock的簡介文檔,會在後邊附帶一個自己的例子。
當你寫一個原型或測試,往往不能完全的依賴真實對象。一個mock對象實現與一個真實對象相同的接口,但讓你在運行時指定它時,如何使用?它應該做什麼?(哪些方法將被稱為?什麼順序?多少次?有什麼參數?他們會返回什麼?等)
注意:很容易混淆偽造對象和模擬對象。fakes和mock在測試驅動開發(TDD)社區中實際上意味著非常不同的東西:
如果所有這些對你來說太抽象了,不要擔心 - 最重要的事情要記住是一個模擬允許你檢查它自己和調用者之間的交互。一旦你開始使用mock,fake和mock之間的差異將變得更加清晰。
Google C ++ Mocking框架(或簡稱為Google Mock)是一個庫(有時我們稱之為“框架”,以使其聲音很酷)用於創建模擬類和使用它們。 它之對於對C ++,就像jMock和EasyMock對於Java。
使用Google Mock涉及三個基本步驟:
雖然模擬對象可以幫助你刪除測試中不必要的依賴,並使它們快速可靠,在C ++中手動使用mock是很難的:
相比之下,Java和Python程序員有一些精細的模擬框架,自動創建mock。因此,Mock是一種被證明是有效的技術,並在這些社區廣泛采用的做法。擁有正確的工具絕對有所不同。
Google Mock旨在幫助C ++程序員。它的靈感來自jMock和EasyMock,但是設計時考慮了C ++的細節。如果下列任何問題困擾你會很有幫助:
我們建議您使用Google Mock:
一個設計工具,它可以讓你早日經常嘗試你的界面設計。更多的迭代導致更好的設計!
一個測試工具,用於剪切測試的出站依賴關系,並探測模塊與其協作者之間的交互。
使用Google Mock很容易! 在你的C ++源文件中,只要#include“gtest / gtest.h”和“gmock / gmock.h”,你已經准備好了。
讓我們看一個例子。 假設您正在開發一個基於LOGO的API來繪圖的圖形程序。 你將如何測試它做正確的事情? 好吧,你可以運行它,並將屏幕與金色屏幕快照,比較屏幕,但讓我們承認:這樣的測試運行和脆弱昂貴(如果你剛剛升級到一個閃亮的新顯卡有更好的抗鋸齒?突然 你必須更新所有的黃金圖像。)。 如果所有的測試都是這樣的,這將是太痛苦了。 幸運的是,你學習了依賴注入,並且知道正確的事情:不是讓應用程序直接與繪圖API交互,而是將API封裝在一個接口(例如,Turtle)中,並用代碼編譯該接口:
class Turtle { ... 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; };
(注意,Turtle的析構函數必須是虛擬的,就像你打算繼承的所有類的情況一樣 - 否則當通過基本指針刪除一個對象時,派生類的析構函數不會被調用,你會得到損壞的程序狀態,如內存洩漏。)
您可以使用PenUp()和PenDown()控制turtle的運動是否留下軌跡,並使用Forward(),Turn()和GoTo()控制其運動。最後,GetX()和GetY()告訴你當前位置的turtle。
你的程序通常會使用這個接口的實際實現。在測試中,您可以使用模擬實現。這允許您輕松地檢查您的程序正在調用什麼繪圖基元,有什麼參數,以及順序。以這種方式編寫的測試更強大,更容易讀取和維護(測試的意圖表示在代碼中,而不是在一些二進制圖像中)運行得多,快得多。
如果你幸運,你需要使用的mock已經被一些好的人實現。但是,你發現自己在寫一個模擬class,放松- Google Mock將這個任務變成一個有趣的游戲! (嗯,差不多。)
使用Turtle界面作為示例,以下是您需要遵循的簡單步驟:
After the process, you should have something like:
#include "gmock/gmock.h" // Brings in Google Mock. class MockTurtle : public Turtle { public: ... 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_METHOD *宏將為您生成定義。 就是這麼簡單! Once you get the hang of it, you can pump out mock classes faster than your source-control system can handle your check-ins。
提示:即使這樣做對你來說太多了,你會發現Google Mock的scripts / generator /目錄(由cppclean項目提供)中的gmock_gen.py工具很有用。 這個命令行工具需要你安裝Python 2.4。 你給它一個C ++文件和它定義的抽象類的名稱,它將為你打印模擬類的定義。 由於C ++語言的復雜性,這個腳本可能不總是工作,但它可以很方便。 有關更多詳細信息,請閱讀 user documentation。
當你定義一個mock類,你需要決定在哪裡放置它的定義。有些人把它放在一個* _test.cc。當被mock的接口(例如,Foo)由同一個人或團隊擁有時,這是很好的。否則,當Foo的所有者改變它,你的測試可能會中斷。 (你不能真正期望Foo的維護者修復使用Foo的每個測試,你能嗎?)
所以,經驗法則是:如果你需要模擬Foo並且它由其他人擁有,在Foo的包中定義模擬類(更好的是,在一個測試子包中,你可以清楚地分離生產代碼和測試實用程序),並且把它放在mock_foo.h。然後每個人都可以從它們的測試引用mock_foo.h。如果Foo變化,只有一個MockFoo的副本要更改,只有依賴於更改的方法的測試需要修復。
另一種方式:你可以在Foo的頂部引入一個薄層FooAdaptor,並將代碼引入這個新的接口。由於你擁有FooAdaptor,你可以更容易地吸收Foo的變化。雖然這是最初的工作,仔細選擇適配器接口可以使您的代碼更容易編寫和更加可讀性,因為你可以選擇FooAdaptor適合你的特定領域比Foo更好。
一旦你有一個模擬類,使用它很容易。 典型的工作流程是:
這裡有一個例子:
#include "path/to/mock-turtle.h" #include "gmock/gmock.h" #include "gtest/gtest.h" using ::testing::AtLeast; // #1 TEST(PainterTest, CanDrawSomething) { MockTurtle turtle; // #2 EXPECT_CALL(turtle, PenDown()) // #3 .Times(AtLeast(1)); Painter painter(&turtle); // #4 EXPECT_TRUE(painter.DrawCircle(0, 0, 10)); } // #5 int main(int argc, char** argv) { // The following line must be executed to initialize Google Mock // (and Google Test) before running the tests. ::testing::InitGoogleMock(&argc, argv); return RUN_ALL_TESTS(); }
正如你可能已經猜到的,這個測試檢查PenDown()被調用至少一次。 如果painter對象沒有調用此方法,您的測試將失敗,並顯示如下消息:
path/to/my_test.cc:119: Failure Actual function call count doesn't match this expectation: Actually: never called; Expected: called at least once.
提示1:如果從Emacs緩沖區運行測試,您可以在錯誤消息中顯示的行號上按<Enter>,直接跳到失敗的預期。
提示2:如果你的模擬對象從來沒有被刪除,最終的驗證不會發生。因此,當您在堆上分配mock時,在測試中使用堆洩漏檢查器是個好主意。
重要提示:Google Mock需要在調用mock函數之前設置期望值,否則行為是未定義的。特別是,你不能交錯EXPECT_CALL()和調用mock函數。
這意味著EXPECT_CALL()應該被讀取為期望call將在未來發生,而不是call已經發生。為什麼Google Mock會這樣工作?好的,事先指定期望允許Google Mock在上下文(堆棧跟蹤等)仍然可用時立即報告違例。這使得調試更容易。
誠然,這個測試是設計的,沒有做太多。您可以輕松實現相同的效果,而無需使用Google Mock。然而,正如我們將很快揭示的,谷歌模擬允許你做更多的mock。
如果您要使用除Google測試(例如CppUnit或CxxTest)之外的其他測試框架作為測試框架,只需將上一節中的main()函數更改為:
int main(int argc, char** argv) { // The following line causes Google Mock to throw an exception on failure, // which will be interpreted by your testing framework as a test failure. ::testing::GTEST_FLAG(throw_on_failure) = true; ::testing::InitGoogleMock(&argc, argv); ... whatever your testing framework requires ... }
這種方法有一個catch:它有時使Google Mock從一個模擬對象的析構器中拋出異常。對於某些編譯器,這有時會導致測試程序崩潰。 你仍然可以注意到測試失敗了,但它不是一個優雅的失敗。
更好的解決方案是使用Google Test的事件偵聽器API來正確地向測試框架報告測試失敗。 您需要實現事件偵聽器接口的OnTestPartResult()方法,但它應該是直接的。
如果這證明是太多的工作,我們建議您堅持使用Google測試,它與Google Mock無縫地工作(實際上,它在技術上是Google Mock的一部分)。 如果您有某個原因無法使用Google測試,請告訴我們。
成功使用模擬對象的關鍵是對它設置正確的期望。 如果你設置的期望太嚴格,你的測試將失敗作為無關的更改的結果。 如果你把它們設置得太松,錯誤可以通過。 你想做的只是正確的,使你的測試可以捕獲到你想要捕獲的那種錯誤。 Google Mock為您提供了必要的方法“恰到好處”。
在Google Mock中,我們使用EXPECT_CALL()宏來設置模擬方法的期望值。 一般的語法是:
EXPECT_CALL(mock_object, method(matchers)) .Times(cardinality) .WillOnce(action) .WillRepeatedly(action);
宏有兩個參數:首先是mock對象,然後是方法及其參數。 請注意,兩者之間用逗號(,)分隔,而不是句點(.)。 (為什麼要使用逗號?答案是,這是必要的技術原因。)
宏之後可以是一些可選的子句,提供有關期望的更多信息。 我們將在下面的章節中討論每個子句是如何工作的。
此語法旨在使期望讀取如英語。 例如,你可能猜到
using ::testing::Return;... EXPECT_CALL(turtle, GetX()) .Times(5) .WillOnce(Return(100)) .WillOnce(Return(150)) .WillRepeatedly(Return(200));turtle對象的GetX()方法將被調用五次,它將第一次返回100,第二次返回150,然後每次返回200。 有些人喜歡將這種語法風格稱為域特定語言(DSL)。
當一個mock函數接受參數時,我們必須指定我們期望什麼參數; 例如:
// Expects the turtle to move forward by 100 units. EXPECT_CALL(turtle, Forward(100));有時你可能不想太具體(記住,談論測試太僵硬,超過規范導致脆弱的測試和模糊測試的意圖,因此,我們鼓勵你只指定必要的 - )。 如果你想檢查Forward()將被調用,但對它的實際參數不感興趣,寫 _ 作為參數,這意味著“任何東西”:
using ::testing::_; ... // Expects the turtle to move forward. EXPECT_CALL(turtle, Forward(_));
_ 是我們稱為匹配器的實例。 匹配器就像一個謂詞,可以測試一個參數是否是我們期望的。 你可以在EXPECT_CALL()裡面使用一個匹配器,只要有一個函數參數。
內置匹配器的列表可以在CheatSheet中找到。 例如,這裡是Ge(大於或等於)匹配器:
1 using ::testing::Ge;... 2 EXPECT_CALL(turtle, Forward(Ge(100)));
這檢查,turtle將被告知前進至少100單位。
我們可以在EXPECT_CALL()之後指定的第一個子句是Times()。我們把它的參數稱為基數,因為它告訴調用應該發生多少次。它允許我們重復一個期望多次,而不實際寫多次。更重要的是,一個基數可以是“模糊的”,就像一個匹配器。這允許用戶准確地表達測試的意圖。
一個有趣的特殊情況是當我們說Times(0)。你可能已經猜到了 - 這意味著函數不應該使用給定的參數,而且Google Mock會在函數被(錯誤地)調用時報告一個Google測試失敗。
我們已經看到AtLeast(n)作為模糊基數的一個例子。有關您可以使用的內置基數列表,請參見CheatSheet。
Times()子句可以省略。如果你省略Times(),Google Mock會推斷出你的基數。規則很容易記住:
快速測驗:如果一個函數被調用兩次,但實際上調用了四次,你認為會發生什麼?
記住,一個模擬對象實際上沒有工作實現? 我們作為用戶必須告訴它當一個方法被調用時該做什麼。 這在Google Mock中很容易。
首先,如果一個模擬函數的返回類型是內置類型或指針,該函數有一個默認動作(一個void函數將返回,一個bool函數將返回false,其他函數將返回0)。此外,在C ++ 11及以上版本中,返回類型為默認可構造(即具有默認構造函數)的模擬函數具有返回默認構造值的默認動作。 如果你不說什麼,這個行為將被使用。
第二,如果模擬函數沒有默認動作,或者默認動作不適合你,你可以使用一系列WillOnce()子句指定每次期望匹配時要采取的動作,後跟一個可選的WillRepeatedly ()。例如,
using ::testing::Return;... EXPECT_CALL(turtle, GetX()) .WillOnce(Return(100)) .WillOnce(Return(200)) .WillOnce(Return(300));
這說明turtle.GetX()將被調用三次(Google Mock從我們寫的WillOnce()子句中推斷出了這一點,因為我們沒有明確寫入Times()),並且會返回100,200, 和300。
using ::testing::Return;... EXPECT_CALL(turtle, GetY()) .WillOnce(Return(100)) .WillOnce(Return(200)) .WillRepeatedly(Return(300));
turtle.GetY()將被調用至少兩次(Google Mock知道這一點,因為我們寫了兩個WillOnce()子句和一個WillRepeatedly(),沒有明確的Times()),將第一次返回100,200 第二次,300從第三次開始。
當然,如果你明確寫一個Times(),Google Mock不會試圖推斷cardinality(基數)本身。 如果您指定的數字大於WillOnce()子句,該怎麼辦? 好了,畢竟WillOnce()已用完,Google Mock每次都會為函數執行默認操作(除非你有WillRepeatedly()。)。
除了Return()之外,我們可以在WillOnce()中做什麼? 您可以使用ReturnRef(variable)返回引用,或調用預定義函數等。
重要說明:EXPECT_CALL()語句只評估一次操作子句,即使操作可能執行多次。 因此,您必須小心副作用。 以下可能不會做你想要的:
int n = 100; EXPECT_CALL(turtle, GetX()) .Times(4) .WillRepeatedly(Return(n++));
不是連續返回100,101,102,...,這個mock函數將總是返回100,因為n ++只被計算一次。 類似地,當執行EXPECT_CALL()時,Return(new Foo)將創建一個新的Foo對象,並且每次都返回相同的指針。 如果你想要每次都發生副作用,你需要定義一個自定義動作,我們將在 CookBook中教授。
時間另一個測驗! 你認為以下是什麼意思?
using ::testing::Return;... EXPECT_CALL(turtle, GetY()) .Times(4) .WillOnce(Return(100));
顯然turtle.GetY()被期望調用四次。但如果你認為它會每次返回100,三思而後行!請記住,每次調用函數時都將使用一個WillOnce()子句,然後執行默認操作。所以正確的答案是turtle.GetY()將第一次返回100,但從第二次返回0,因為返回0是int函數的默認操作。
到目前為止,我們只列出了你有一個期望的例子。更現實地,你要指定對多個模擬方法的期望,這可能來自多個模擬對象。
默認情況下,當調用模擬方法時,Google Mock將按照它們定義的相反順序搜索期望值,並在找到與參數匹配的活動期望時停止(您可以將其視為“新規則覆蓋舊的規則“)。如果匹配期望不能再接受任何調用,您將得到一個上限違反的失敗。這裡有一個例子:
using ::testing::_;... EXPECT_CALL(turtle, Forward(_)); // #1 EXPECT_CALL(turtle, Forward(10)) // #2 .Times(2);
如果Forward(10)在一行中被調用三次,第三次它將是一個錯誤,因為最後的匹配期望(#2)已經飽和。然而,如果第三個Forward(10)被Forward(20)替換,則它將是OK,因為現在#1將是匹配期望。
附注:Google Mock為什麼要以與預期相反的順序搜尋匹配?原因是,這允許用戶在模擬對象的構造函數中設置默認期望,或測試夾具的設置階段中設置默認期望,然後通過在測試體中寫入更具體的期望來定制模擬。所以,如果你對同一個方法有兩個期望,你想把一個具有更多的特定的匹配器放在另一個之後,或更具體的規則將被更為一般的規則所覆蓋。
默認情況下,即使未滿足較早的期望,期望也可以匹配調用。換句話說,調用不必按照期望被指定的順序發生。
有時,您可能希望所有預期的調用以嚴格的順序發生。在Google Mock中說這很容易:
using ::testing::InSequence;... TEST(FooTest, DrawsLineSegment) { ... { InSequence dummy; EXPECT_CALL(turtle, PenDown()); EXPECT_CALL(turtle, Forward(100)); EXPECT_CALL(turtle, PenUp()); } Foo(); }
通過創建類型為InSequence的對象,其范圍中的所有期望都被放入序列中,並且必須按順序發生。因為我們只是依靠這個對象的構造函數和析構函數做實際的工作,它的名字真的無關緊要。
在這個例子中,我們測試Foo()按照書寫的順序調用三個期望函數。如果調用是無序的,它將是一個錯誤。
(如果你關心一些呼叫的相對順序,但不是所有的呼叫,你能指定一個任意的部分順序嗎?答案是...是的!如果你不耐煩,細節可以在CookBook中找到。)
現在,讓我們做一個快速測驗,看看你可以多好地使用這個模擬的東西。你會如何測試,turtle被要求去原點兩次(你想忽略任何其他指令)?
在你提出了你的答案,看看我們的比較的筆記(自己先解決 - 不要欺騙!):
using ::testing::_;... EXPECT_CALL(turtle, GoTo(_, _)) // #1 .Times(AnyNumber()); EXPECT_CALL(turtle, GoTo(0, 0)) // #2 .Times(2);
假設turtle.GoTo(0,0)被調用了三次。 第三次,Google Mock將看到參數匹配期望#2(記住,我們總是選擇最後一個匹配期望)。 現在,由於我們說應該只有兩個這樣的調用,Google Mock會立即報告錯誤。 這基本上是我們在上面“使用多個期望”部分中告訴你的。
這個例子表明,Google Mock的期望在默認情況下是“粘性”,即使在我們達到其調用上界之後,它們仍然保持活動。 這是一個重要的規則要記住,因為它影響規范的意義,並且不同於它在許多其他Mock框架中做的(為什麼我們這樣做?因為我們認為我們的規則使常見的情況更容易表達和 理解。)。
簡單? 讓我們看看你是否真的理解它:下面的代碼說什麼?
using ::testing::Return; ... for (int i = n; i > 0; i--) { EXPECT_CALL(turtle, GetX()) .WillOnce(Return(10*i)); }
如果你認為它說,turtle.GetX()將被調用n次,並將返回10,20,30,...,連續,三思而後行! 問題是,正如我們所說,期望是粘性的。 所以,第二次turtle.GetX()被調用,最後(最新)EXPECT_CALL()語句將匹配,並將立即導致“上限超過(upper bound exceeded)”錯誤 - 這段代碼不是很有用!
一個正確的說法是turtle.GetX()將返回10,20,30,...,是明確說,期望是不粘的。 換句話說,他們應該在飽和後盡快退休:
using ::testing::Return; ... for (int i = n; i > 0; i--) { EXPECT_CALL(turtle, GetX()) .WillOnce(Return(10*i)) .RetiresOnSaturation(); }
而且,有一個更好的方法:在這種情況下,我們期望調用發生在一個特定的順序,我們排列動作來匹配順序。 由於順序在這裡很重要,我們應該顯示的使用一個順序:
using ::testing::InSequence; using ::testing::Return; ... { InSequence s; for (int i = 1; i <= n; i++) { EXPECT_CALL(turtle, GetX()) .WillOnce(Return(10*i)) .RetiresOnSaturation(); } }
順便說一下,期望可能不粘的其他情況是當它在一個序列 --- 一旦序列中的在他之後的期望已經使用,它自動退休(並且永遠不會用於匹配任何調用)。
模擬對象可能有很多方法,並不是所有的都是那麼有趣。例如,在一些測試中,我們可能不關心GetX()和GetY()被調用多少次。
在Google Mock中,如果你對一個方法不感興趣,只是不要說什麼。如果調用此方法,您將在測試輸出中看到一個警告,但它不會失敗。
恭喜!您已經學會了足夠的Google Mock開始使用它。現在,您可能想要加入googlemock討論組,並且實際上使用Google Mock編寫一些測試 - 這很有趣。嘿,它甚至可以上瘾 - 你已經被警告。
然後,如果你想增加你的Mock商,你應該移動到 CookBook。您可以了解Google Mock的許多高級功能,並提高您的享受和測試幸福的水平。
這是我寫的一個小例子,很簡單的例子:
//定義需要模擬的接口類 class FooInterface { public: virtual ~FooInterface() {} virtual std::string getArbitraryString() = 0; virtual int getPosition() = 0; };
//模擬類 class MockFoo : public FooInterface { public: MOCK_METHOD0(getArbitraryString, std::string()); MOCK_METHOD0(getPosition, int()); };
#include "stdafx.h" using namespace seamless; using namespace std; using ::testing::Return; int main(int argc, char** argv) { //Since Google Mock depends on Google Test, InitGoogleMock() is also responsible for initializing Google Test. //Therefore there's no need for calling testing::InitGoogleTest() separately. ::testing::InitGoogleMock(&argc, argv); int n = 100; string value = "Hello World!"; MockFoo mockFoo; EXPECT_CALL(mockFoo, getArbitraryString()) .Times(1) .WillOnce(Return(value)); string returnValue = mockFoo.getArbitraryString(); cout << "Returned Value: " << returnValue << endl; //在這裡Times(2)意思是調用兩次,但是下邊只調用了一次,所以會報出異常 EXPECT_CALL(mockFoo, getPosition()) .Times(2) .WillRepeatedly(Return(n++)); int val = mockFoo.getPosition(); //100 cout << "Returned Value: " << val << endl; //getPosition指定了調用兩次,這裡只調用了一次,所以運行結果顯示出錯 return EXIT_SUCCESS; }
運行結果:
可以看到上邊都輸出了,最後有個錯誤,這個錯誤是因為:getPosition指定了調用兩次,這裡只調用了一次,所以運行結果顯示出錯
轉載請注明出處:http://www.cnblogs.com/jycboy/p/gmock_summary.html