Google C++單元測試框架GoogleTest---Google Mock簡介--概念及根底語法。本站提示廣大學習愛好者:(Google C++單元測試框架GoogleTest---Google Mock簡介--概念及根底語法)文章只能為提供參考,不一定能成為您想要的結果。以下是Google C++單元測試框架GoogleTest---Google Mock簡介--概念及根底語法正文
就在昨天終於做了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