當你寫一個原型或者測試的時候,依賴整個object 是不可行和明智的。一個 mock object和 real object 有一樣的接口(因此它能夠像同一個使用),可是讓你在運行時進行指定它應該如何被使用,它應當作什麼(哪些方法應該被調用?以何種順序?多少次?用什麼參數?什麼會被返回?)java
注意:很容易弄混 fake objects 和 mock objects。實際上fakes 和 mocks意味着不一樣的事情在Test-Driven Development(TDD)社區:python
Fake object(僞對象) 有 工做實現,可是常常採起一些捷徑(也許讓操做不昂貴),這會使它們不適合生產。內存中的文件系統就是一個例子。git
Mocks 是有指望的預編程對象。由一些預期會被調用的 sepcification組成。程序員
若是這對你來講太抽象的話,don't worry- 你須要記住最重要的就是mock 容許你使用它與你的代碼進行交互。當你使用mocks的時候,你對於fakes和 mocks的區別就更清晰了,github
Google C++ Mocking Framework (or Google Mock for short) 是一個用來建立 mock 類庫(叫「框架」是由於這樣聽起來更cool),就像 java 的 jMock and EasyMock。數據庫
使用 Google Mock 包含如下三個基本步驟:編程
1. 使用一些簡單的macros 來描述 你想mock的接口,這將擴展你的mock 類。網絡
2. 用很直觀的語法來描述一些mock對象的指望和行爲。app
3. 練習使用mock 對象的 代碼。任何 違反 expectation 的行爲一出現就會被Google Mock 捕獲。框架
爲何使用 Google Mock
雖然Mock Object 能夠幫助你移除測試中沒必要要的依賴,並使它們快速可靠,可是在C ++中手動使用mock是很難的:
有些人不得不實現mocks。這個工做既乏味又容易出錯。難怪有些人想要避免它。
手動寫的mock的質量是不可靠的,沒法預測的。你可能看過一些真正拋光過的,可是你也許會看到一些被匆忙砍掉有各類零時限制的。
你從一個mock 得到的知識不能使用到下一個mock上面。
相比之下,Java和Python程序員有一些精細的模擬框架,自動建立mock。所以,Mock是一種被證實是有效的技術,並在這些社區普遍採用的作法。擁有正確的工具絕對有所不一樣。
Google Mock旨在幫助C ++程序員。它的靈感來自jMock和EasyMock,可是設計時考慮了C ++的細節。它會幫助你的,若是你遇到如下問題:
你被不怎麼好的設計所困擾,早知道應該作更多的原型設計的,但一切都太遲了,可是用C++進行原型設計速度會很慢。
您的測試很慢,由於它們依賴於太多的庫或使用昂貴的資源(例如數據庫)
你的測試是脆弱的,由於他們使用的一些資源是不可靠的(例如網絡)
您想要測試代碼如何處理失敗(例如,文件校驗和錯誤),可是不容易去製造這麼一個失敗。
你想確保你的當前模塊和其餘模塊的交互是正確的,可是觀察交互是很不容易的;所以你訴諸於觀察行動結束時的反作用,這是最尷尬的
你想 mock out 你的 依賴,除了還mock尚未被實現;坦白的講,你對那些手寫的mock 不感冒
咱們鼓勵你像這樣使用Google Mock:
一個設計工具,它可讓你早日常常嘗試你的接口設計。更多的迭代致使更好的設計!
一個測試工具,切斷全部測試的外部依賴,探測你的模塊和其餘模塊的交互!
開始吧
使用Google Mock很容易! 在你的C ++源文件中,只要#include「gtest / gtest.h」和「gmock / gmock.h」,你已經準備好了。
讓咱們來看一個例子。假設你在開發一個圖形程序依賴一個 LOGO-like的 API 來繪圖。你該怎樣測試它作了正確的事情呢?你能夠運行它而且與一個golden screen snapshot進行比較,可是我認可:像這樣測試是昂貴的而且很脆弱(若是你要更新到一個全新抗鋸齒的圖像該怎麼辦?你要更新你全部的golden images),若是你的全部測試都是這樣的,這就很痛苦了。Fortunately,你學習到了Dependency Injection而且知道該做什麼:不要讓你的application 直接 調用 drawing API, 把API包在一個接口裏(say, Turtle
) and code to that interface:
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來替換。這讓你很容易的檢查你程序你調用的 drawing primitives。傳了哪些參數,以什麼樣的順序。以這種方式編寫的測試更強大,更容易讀取和維護(測試的意圖表示在代碼中,而不是在一些二進制圖像中)運行得多,快得多。
若是你幸運,你須要使用的mock已經被一些好的人實現。可是,你發現本身在寫一個模擬class,放鬆- Google Mock將這個任務變成一個有趣的遊戲!
使用Turtle接口做爲示例,如下是您須要遵循的簡單步驟:
1. MockTurtle繼承Turtle類
2.使用Turtle的虛函數(雖然可使用模板來模擬非虛方法 mock non-virtual methods using templates,可是它更多的涉及)。計算它有多少參數。
3. 在 public 區: section of the child class, write MOCK_METHODn();
(or MOCK_CONST_METHODn();
if you are mocking a const
method), where n
is the number of the arguments; if you counted wrong, shame on you, and a compiler error will tell you so.
4. 如今來到有趣的部分:你採起函數簽名,剪切和粘貼函數名做爲宏的第一個參數,留下的做爲第二個參數(若是你好奇,這是類型的功能)
5. 重複,直到您要模擬的全部虛擬功能完成。
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 *宏將爲您生成定義。 就是這麼簡單!
一旦你掌握了它,你能夠快速的寫出 mock class,以致於你的 source control system 都不能處理你的check-in 了
Tips: 若是 這對你來講工做量太大了,你能夠在 Google Mock 的 scripts/generator/目錄下面找到gmock_gen.py 工具。
Command-line 工具須要python2.4 安裝。你只要給它一個 定義了抽象類的C++文件,它就會給你打印 其mock class。因爲C++語言的複雜性,這個腳本可能不老是工做正常,但確實頗有用,read the user documentation.
當你定義了 mock class,你得決定你把這些定義放到什麼地方。有些人把它放在一個* _test.cc。當這些 mock對象是被一我的或者一個團隊使用的時候,這樣定義就很好。不然,當Foo的全部者改變它,你的測試可能會中斷。 (你不能真正指望Foo的維護者修復使用Foo的每一個測試,你能嗎?)
因此,經驗法則是:若是你須要模擬Foo而且它由其餘人擁有,在Foo的包中定義模擬類(更好的是,在一個測試子包中,你能夠清楚地分離生產代碼和測試實用程序),而且把它放在mock_foo.h。而後每一個人均可以從它們的測試引用mock_foo.h。若是Foo變化,只有一個MockFoo的副本要更改,只有依賴於更改的方法的測試須要修復。
另一種方法:你能夠在Foo的頂部引入一個 薄層FooAdaptor ,並將代碼引入這一新的接口。由於你擁有FooAdaptor,你能夠更容易的吸取Foo的變化。雖然這是最初的工做,仔細選擇適配器接口可使您的代碼更容易編寫和更加可讀性,由於你能夠選擇FooAdaptor適合你的特定領域比Foo更好。
一旦你有了Mock 類,使用它很是容易。典型的工做流程以下:
1. 從測試命名空間導入Google Mock名稱,以便您可使用它們(每一個文件只需執行一次。請記住,命名空間是一個好主意,有利於您的健康。)
2. 建立一些 mock對象
3.指定你對它們的指望(一個方法被調用多少次?有什麼參數?它應該作什麼等等)。
4.練習一些使用mock的代碼; 可使用Google Test斷言檢查結果。若是一個mock方法被調用超過預期或錯誤的參數,你會當即獲得一個錯誤。
5. 當模mock destructed,Google Mock將自動檢查是否知足了對其的全部指望
例子:
#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 object 歷來沒有被刪除,最終的驗證不會發生。所以,當您在堆上分配mock時,在測試中使用堆泄漏檢查器是個好主意。
重要提示:Google Mock 須要expectation 在mock 函數被調用以前就設置,否者 行爲就是 未定義的(undefined)。尤爲是,你不能交錯 EXPECT_CALL()和調用函數
這意味着EXPECT_CALL()應該被讀取爲指望call將在將來發生,而不是call已經發生。爲何Google Mock會這樣工做?
好的,事先指按期望容許Google Mock在上下文(堆棧跟蹤等)仍然可用時當即報告違例。這使得調試更容易。
誠然,這個測試是設計的,沒有作太多。不使用Google Mock,您也能夠輕鬆實現相同的效果。然而,正如咱們將很快揭示的,Google 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的事件偵聽器APIevent listener API 來正確地向測試框架報告測試失敗。 您須要實現事件偵聽器接口的OnTestPartResult()方法,但它應該是直接的。
若是這證實是太多的工做,咱們建議您堅持使用Google測試,它與Google Mock無縫地工做(實際上,它在技術上是Google Mock的一部分)。 若是您有某個緣由沒法使用Google測試,請告訴咱們。
成功使用Mock Object的關鍵是對它設置正確的指望。 若是你設置的指望太嚴格,你的測試將失敗做爲無關的更改的結果。 若是你把它們設置得太鬆,錯誤能夠經過。 你想作的只是正確的,使你的測試能夠捕獲到你想要捕獲的那種錯誤。 Google Mock爲您提供了必要的方法「恰到好處」。
在 Google Mock 中咱們在 mock mecthod 中使用 EXPECT_CALL() 宏去設置expectation。 通常的語法是:
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)。
注意:爲何咱們使用宏來作到這一點? 它有兩個目的:第一,它使預期容易識別(經過grep或由人類讀者),其次它容許Google Mock在消息中包括失敗的指望的源文件位置,使調試更容易。
當一個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(大於或等於)匹配器:
using ::testing::Ge; ... 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的許多高級功能,並提升您的享受和測試幸福的水平。