Google Mock启蒙篇 [2] (Google C++ Mocking Framework for Dummies 翻译)
Setting Expectations
????成功地使用Mock對象的關鍵是在它上面設置合適的期望。如果你設置的期望太過嚴格,你的測試可能會因為無關的改變而失敗。如果你把期望設置的太過松馳,bugs可能會溜過去。而你需要的是你的測試可以剛好捕獲你想要捕獲的那一種bug。Google Mock提供了一些方法可以讓你的測試尺度剛好( just right )。
General Syntax
????在Goolge Mock中,我們用EXPECT_CALL()宏來設置一個Mock函數上的期望。一般語法是:
EXPECT_CALL(mock_object, method(matchers))< xmlnamespace prefix ="o" ns ="urn:schemas-microsoft-com:office:office" />
????.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()方法會被調用5次,它第一次返回100,第二次返回150,然后每次返回200。許多人喜歡稱這種語法方式為特定領域語言( Domain-Specific Language (DSL) )。
注意:為什么我們要用宏來實現呢?有兩個原因:第一,它讓期望更容易被認出來(?無論是grep還是人去閱讀?),第二,它允許Google Mock可以得到失敗期望在源文件的位置,從而使Debug更容易。
Matchers: What Arguments Do We Expect?
????當一個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(_));
????_是我們稱為Matchers的一個例子,一個matcher是像一個斷言,它可測試一個參數是否是我們期望的。你可用在EXPECT_CALL()中任何寫函數參數期望的地方用matcher。
????一個內置的matchers可以在CheatSheet中找到,比如,下面是Ge( greater than or equal ) matcher的應用。
using?::testing::Ge;...
EXPECT_CALL(turtle, Forward(Ge(100)));
????這個測試是檢查turtle是否被告知要至少前進至少100個單位。
Cardinalities: How Many Times Will It Be Called?
????在EXPECT_CALL()之后第一個我們可以指定的子句是Times()。我們稱Times的參數為cardinality,因為它是指這個函數應該被調用多少次。Times可以讓我們指定一個期望多次,而不用去寫一次次地寫這個期望。更重要的是,cardinality可以是“模糊”的,就像matcher一樣。它可以讓測試者更準確地表達他測試的目的。
????一個有趣的特例是我們指定Times(0)。你也許已經猜到了,它是指函數在指定參數下不應該被調用,如果這個函數被調用了,Google Mock會報告一個Google Test失敗。
????我們已經見過AtLeast(n)這個模糊cardinalities的例子了。你可以在CheatSheet中找一個內置cardinalities列表。
????Times()子句可以省略。如果你省略Times(),Google Mock會推斷出cardinality的值是什么。這個規則很容易記:
l??如果在EXPECT_CALL中既沒有WillOnce()也沒有WillRepeatedly(),那推斷出的cardinality就是Times(1)。
l??如果有n個WillOnce(),但沒有WillRepeatedl(),其中n >= 1,那么cardinality就是Times(n)。
l??如果有n個WillOnce(),和一個WillRepeatedly(),其中n >= 0,那么cardinality就是Times(AtLeast(n))。
小測試:如果一個函數期望被調用2次,但被調用了4次,你認為會發生什么呢?
Actions: What Should It Do?
????請記住一個Mock對象其實是沒有實現的。是我們這些用戶去告訴它當一個函數被調用時它應該做什么。這在Google Mock中是很簡單的。
????首先,如果Mock函數的返回類型是一個指針或是內置類型,那這個函數是有默認行為的(?一個void函數直接返回,bool函數返回false,其它函數返回0 )。如果你不想改變它,那這種行為就會被應用。
????其次,如果一個Mock函數沒有默認行為,或默認行為不適合你,你可以用WillOnce來指定每一次的返回值是什么,最后可以選用WillRepeatedly來結束。比如:
using?::testing::Return;...
EXPECT_CALL(turtle, GetX())
????.WillOnce(Return(100))
????.WillOnce(Return(200))
?.WillOnce(Return(300));
上面的意思是turtle.GetX()會被調用恰好3次,并分別返回100,200,300。
using?::testing::Return;...
EXPECT_CALL(turtle, GetY())
.WillOnce(Return(100))
???.WillOnce(Return(200))
.WillRepeatedly(Return(300));
上面的意思是指turtle.GetY()將至少被調用2次,第一次返回100,第二次返回200,從第三次以后都返回300。
當然,你如果你明確寫上Times(),Google Mock不會去推斷cardinality了。如果你指定的cardinality大于WillOnce()子句的個數時會發生什么呢?嗯,當WillOnce()用完了之后,Google Mock會每次對函數采用默認行為。
????我們在WillOnce()里除了寫Return()我們還能做些什么呢?你可以用ReturnRef( variable ),或是調用一個預先定義好的函數,自己在Others中找吧。
重要提示:EXPECT_CALL()只對行為子句求一次值,盡管這個行為可能出現很多次。所以你必須小心這種副作用。下面的代碼的結果可能與你想的不太一樣。
int?n = 100;
EXPECT_CALL(turtle, GetX())
.Times(4)
.WillRepeatedly(Return(n++));
????它并不是依次返回100,101,102...,而是每次都返回100,因為n++只會被求一次值。類似的,Return(new Foo)當EXPECT_CALL()求值時只會創建一個Foo對象,所以它會每次都返回相同的指針。如果你希望每次都看到不同的結果,你需要定義一個自定義行為,我們將在CookBook中指導你。
????現在又是一個小測驗的時候了!你認為下面的代碼是什么意思?
using?::testing::Return;...
EXPECT_CALL(turtle, GetY())
.Times(4)
.WillOnce(Return(100));
????顯然,turtle.Get()期望被調用4次。但如果你認為它每次都會返回100,那你就要再考慮一下了!記住,每次調用都會消耗一個WillOnce()子句,消耗完之后,就會使用默認行為。所以正確的答案是turtle.GetY()第一次返回100,以后每次都返回0,因為0是默認行為的返回值。
Using Multiple Expectations
????至今為止,我們只展示了如何使用單個期望。但是在現實中,你可能想指定來自不同Mock對象的Mock函數上的期望。
????默認情況下,當一個Mock函數被調用時,Google Mock會通過定義順序的逆序去查找期望,當找到一個與參數匹配的有效的期望時就停下來(?你可以把這個它想成是“老的規則覆蓋新的規則“?)。如果匹配的期望不能再接受更多的調用時,你就會收到一個超出上界的失敗,下面是一個例子:
using?::testing::_;...
EXPECT_CALL(turtle, Forward(_));??// #1
EXPECT_CALL(turtle, Forward(10))??// #2
????.Times(2);
如果Forward(10)被連續調用3次,第3次調用它會報出一個錯誤,因為最后一個匹配期望(#2)已經飽和了。但是如果第3次的Forward(10)替換為Forward(20),那它就不會報錯,因數現在#1將會是匹配的期望了。
邊注:為什么Google Mock會以逆序去匹配期望呢?原因是為了可以讓用戶開始時使用Mock對象的默認行為,或是一些比較松馳的匹配條件,然后寫一些更明確的期望。所以,如果你在同一個函數上有兩個期望,你當然是想先匹配更明確的期望,然后再匹配其它的,或是可以說明確的規則會隱藏更寬泛的規則。
Ordered vs Unordered Calls
????默認情況下,即使是在前一個期望沒有被匹配的情況下,一個期望仍然可以被匹配。換句話說,調用的匹配順序不會按照期望指定的順序去匹配。
????有時,你可能想讓所有的期望調用都以一個嚴格的順序來匹配,這在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的一個對象后,在這個對象作用域中的期望都會以順序存放,并要求調用以這個順序匹配。因為我們只是依賴這個對象的構造函數和析構函數來完成任務,所以對象的名字并不重要。
(?如果你只是關心某些調用的相對順序,而不是所有調用的順序?可以指定一個任意的相對順序嗎?答案是...可以!如果你比較心急,你可以在CookBook中找到相關的細節。)
All Expectations Are Sticky (Unless Said Otherwise)
????現在讓我們做一個小測驗,看你掌握Mock到什么程度了。你如何測試turtle恰好經過原點兩次?
????當你想出你的解法之后,看一下我們的答案比較一下(?先自己想,別作弊?)。
using?::testing::_;...
EXPECT_CALL(turtle, GoTo(_, _))??// #1
????.Times(AnyNumber());
EXPECT_CALL(turtle, GoTo(0, 0))??// #2
.Times(2);
假設turtle.GoTo(0,0)被調用了3次。在第3次,Google Mock會找到參數匹配期望#2。因為我們想要的是恰好經過原點兩次,所以Google Mock會立即報告一個錯誤。上面的內容其實就是我們在“Using Multiple Expectations”中說過的。
????上面的例子說明了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, ...,唉,你還是再想想吧!問題是,我們都說過了,期望是嚴格的。所以第2次turtle.GetX()被調用時,最后一個EXPECT_CALL()會被匹配,所以馬上會引起“超出上界”的錯誤。上面的代碼其實沒什么用途。
????一個正確表達turtle.GetX()返回10, 20, 30,...,的方法是明確地說明期望不是嚴格的。換句話說,在期望飽和之后就失效。
using?::testing::Return;
...
for?(int?i = n; i > 0; i--) {
??EXPECT_CALL(turtle, GetX())
????.WillOnce(Return(10*i))
????.RetiresOnSaturation();
}
????并且,有一個更好的解決方法,在這個例子中,我們期望調用以特定順序執行。因為順序是一個重要的因素,我們應該用InSequence明確地表達出順序:
using?::testing::InSequence;
using?::testing::Return;
...
{
??InSequence s;
?
??for?(int?i = 1; i <= n; i++) {
????EXPECT_CALL(turtle, GetX())
????????.WillOnce(Return(10*i))
????????.RetiresOnSaturation();
??}
}
????順便說一下,另一個期望可能不嚴格的情況是當它在一個順序中,當這個期望飽和后,它就自動失效,從而讓下一個期望有效。
Uninteresting Calls
????一個Mock對象可能有很多函數,但并不是所有的函數你都關心。比如,在一些測試中,你可能不關心GetX()和GetY()被調用多少次。
????在Google Mock中,你如果不關心一個函數,很簡單,你什么也不寫就可以了。如果這個函數的調用發生了,你會看到測試輸出一個警告,但它不會是一個失敗。
What Now?
????恭喜!你已經學習了足夠的Google Mock的知識了,你可以開始使用它了。現在你也許想加入googlemock討論組,并開始真正地用Google Mock開始寫一些測試——它是很有意思的,嗨,這可能是會上癮的,我可是警告過你了喔!
????如果你想提高你的Mock等級,你可以移步至CookBook。你可以在那學習更多的Google Mock高級特性——并提高你的幸福指數和測試快樂級別。
Copyright notice
???? 所有的內容全部翻譯自 Google 的文檔 Google C++ Mocking Framework for Dummies , Koala++/ 屈偉 ?總結
以上是生活随笔為你收集整理的Google Mock启蒙篇 [2] (Google C++ Mocking Framework for Dummies 翻译)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: gcc 参数 -fprofile-arc
- 下一篇: Google Mock启蒙篇 [1] (