Google C++ Mocking Framework Cheat Sheet中文版
· 定義一個模擬(Mock)類
· 模擬(Mocking)普通類
· 模擬(Mocking)模板類
· 為模擬函數(shù)(Mock Functions)指定調(diào)用約定
· 在測試中使用模擬
· 設(shè)置默認(rèn)動作(Default Actions)
· 設(shè)置預(yù)期(Expectations)
· 匹配器(Matchers)
· 通配符
· 一般比較
· 浮點數(shù)匹配器
· 字符串匹配器
· 容器匹配器
· 成員匹配器
· 匹配函數(shù)或函數(shù)對象的返回值
· 指針匹配器
· 使用函數(shù)或函數(shù)對象作為匹配器
· 多參數(shù)匹配器
· 復(fù)合匹配器
· 轉(zhuǎn)換匹配器
· 匹配器作為謂詞(Predicates)
· 匹配器作為測試斷言
· 動作(Actions)
· 返回一個值
· 副作用(Side Effects)
· 使用函數(shù)或函數(shù)對象作為動作(Action)
· 默認(rèn)動作(Default Action)
· 復(fù)合動作(Composite Actions)
· 基數(shù)(Cardinalities)
· 序列(Sequences)
· 驗證并重置Mock
定義一個模擬(Mock)類
假設(shè)有
class Foo {
...
virtual ~Foo();
virtual int GetSize() const = 0;
virtual string Describe(const char* name) = 0;
virtual string Describe(int type) = 0;
virtual bool Process(Bar elem, int count) = 0;
};
(注意 ~Foo() 必須 是virtual的) 我們可以如下定義它的模擬類
#include <gmock/gmock.h>
class MockFoo : public Foo {
MOCK_CONST_METHOD0(GetSize, int());
MOCK_METHOD1(Describe, string(const char* name));
MOCK_METHOD1(Describe, string(int type));
MOCK_METHOD2(Process, bool(Bar elem, int count));
};
如下,建立一個"合適"的模擬對象 (mock object)來忽略所有不關(guān)心的調(diào)用,或者是一個"精確"的模擬對象讓這些調(diào)用置為失敗:
NiceMock<MockFoo> nice_foo; // The type is a subclass of MockFoo.
StrictMock<MockFoo> strict_foo; // The type is a subclass of MockFoo.
要模擬
template <typename Elem>
class StackInterface {
...
virtual ~StackInterface();
virtual int GetSize() const = 0;
virtual void Push(const Elem& x) = 0;
};
(注意 ~StackInterface() 必須 是 virtual 的) 只要追加_T 到 MOCK_* 宏即可:
template <typename Elem>
class MockStack : public StackInterface<Elem> {
...
MOCK_CONST_METHOD0_T(GetSize, int());
MOCK_METHOD1_T(Push, void(const Elem& x));
};
如果你模擬的函數(shù)不是使用默認(rèn)的調(diào)用約定,你可以追加 _WITH_CALLTYPE 到前兩段講到的所有宏里,并把調(diào)用約定作為宏的第一個參數(shù),例如,
MOCK_METHOD_1_WITH_CALLTYPE(STDMETHODCALLTYPE, Foo, bool(int n));
MOCK_CONST_METHOD2_WITH_CALLTYPE(
STDMETHODCALLTYPE, Bar, int(double x, double y));
這里的 STDMETHODCALLTYPE 是Windows中的頭文件 <objbase.h> 定義的.
在測試中使用模擬(Mocking)
典型的流程是:
1. 引入你要用到的 Google Mock 名稱. 除宏或其它特別提到的之外所有 Google Mock 名稱都位于testing 名空間之下.
2. 建立模擬對象(mock objects).
3. 可選的,設(shè)置模擬對象的默認(rèn)動作.
4. 在模擬對象上設(shè)置你的預(yù)期(它們怎樣被調(diào)用,應(yīng)該怎樣回應(yīng)?).
5. 調(diào)用使用模擬對象的代碼,如有必要,使用Google Test斷言檢查返回值.
6. 當(dāng)模擬對象析構(gòu)后,Google Mock 自動驗證它是否達(dá)到所有預(yù)期.
這里是一個例子:
1. using testing::Return;
2.
3. TEST(BarTest, DoesThis) {
4. MockFoo foo;
5.
6. ON_CALL(foo, GetSize())
7. .WillByDefault(Return(1));
8.
9.
10. EXPECT_CALL(foo, Describe(5))
11. .Times(3)
12. .WillRepeatedly(Return("Category 5"));
13.
14.
15. EXPECT_EQ("good", MyProductionFunction(&foo));
16. }
設(shè)置默認(rèn)動作
對于返回void, bool, 數(shù)值, 或指針的函數(shù), Google Mock 都有一個內(nèi)置默認(rèn)動作.
要自定義返回類型為T的函數(shù)的默認(rèn)動作:
1. using testing::DefaultValue;
2.
3. DefaultValue<T>::Set(value);
4.
5. DefaultValue<T>::Clear();
要自定義特定方法的默認(rèn)動作,使用ON_CALL():
ON_CALL(mock_object, method(matchers))
.WithArguments(multi_argument_matcher) ?
.WillByDefault(action);
設(shè)置預(yù)期(Expectations)
EXPECT_CALL()在模擬方法(mock method)上設(shè)置預(yù)期(它們怎樣被調(diào)用?應(yīng)該怎樣回應(yīng)?):
EXPECT_CALL(mock_object, method(matchers))
.WithArguments(multi_argument_matcher) ?
.Times(cardinality) ?
.InSequence(sequences) *
.WillOnce(action) *
.WillRepeatedly(action) ?
.RetiresOnSaturation(); ?
如果沒有Times(), 那么cardinality就依據(jù)其它參數(shù)做出假定:
· Times(1) 既沒有WillOnce()也沒有WillRepeatedly();
· Times(n) 有n個WillOnce()但沒有WillRepeatedly(), 這里的n >= 1;
· Times(AtLeast(n)) 有n個WillOnce()并且有一個WillRepeatedly(), 這里的n >= 0.
沒用EXPECT_CALL()的方法可以自由地調(diào)用任意次, 并以默認(rèn)動作應(yīng)對每次調(diào)用.
匹配器(matcher)
匹配器 (matcher)匹配單個參數(shù)(指函數(shù)參數(shù)). 內(nèi)置的匹配器分成幾種類型(下面的argument指函數(shù)參數(shù)):
_ |
argument& nbsp;可以是適當(dāng)類型的任意值 |
A<type>() 或 An<type>() |
argument& nbsp;可以是type類型的任意值 |
Eq(value) 或 value |
argument == value |
Ge(value) |
argument >= value |
Gt(value) |
argument > value |
Le(value) |
argument <= value |
Lt(value) |
argument < value |
Ne(value) |
argument != value |
NULL |
argument 是 NULL. |
NotNull() |
argument 是一個非null指針. |
Ref(variable) |
argument 是一個variable的引用. |
TypedEq<type>(value) |
argument 是type類型并且等于value.當(dāng)模擬函數(shù)被重載時你可能需要用它來代替Eq(value). |
除了Ref(), 這些匹配器在測試用例中使用一份value的拷貝來修改或析構(gòu). 如果編譯器提示value沒有public的拷貝構(gòu)造, 可以嘗試使用ByRef()包裝, 比如. Eq(ByRef(non_copyable_value)). 如果你這樣做的話, 請確保調(diào)用之后non_copyable_value沒有被修改.
浮點數(shù)匹配器
DoubleEq(a_double) |
argument 是一個double值,近似等于a_double, 兩個NaN是不相等的. |
FloatEq(a_float) |
argument 是一個float值,近似等于a_float, 兩個NaN是不相等的. |
NanSensitiveDoubleEq(a_double) |
argument 是一個double值,近似等于a_double, 兩個NaN是相等的. |
NanSensitiveFloatEq(a_float) |
argument 是一個float值,近似等于a_float, 兩個NaN是相等的. |
這些匹配器使用基于ULP的比較 (和Google Test所使用的一樣). 它們基于預(yù)期的絕對值自動選擇一個合理的誤差范圍. DoubleEq()和FloatEq()符合IEEE標(biāo)準(zhǔn), 該標(biāo)準(zhǔn)要求比較兩個NaN是否相等時返回false. NanSensitive* 版本則視兩個NaN為相等, 這是用戶通常所希望的.
argument可以是C風(fēng)格字符串或C++的string對象:
ContainsRegex(string) |
argument 匹配給定的正則表達(dá)式. |
EndsWith(suffix) |
argument 含有后綴suffix. |
HasSubstr(string) |
argument 含有子串string. |
MatchesRegex(string) |
argument 從第一個字符到最后一個字符都完全匹配給定的正則表達(dá)式. |
StartsWith(prefix) |
argument 含有前綴prefix. |
StrCaseEq(string) |
argument 與string相等, 忽略大小寫. |
StrCaseNe(string) |
argument 與string不等, 忽略大小寫. |
StrEq(string) |
argument 與string相等. |
StrNe(string) |
argument 與string不等. |
StrCaseEq(), StrCaseNe(), StrEq(), 和 StrNe() 也能工作于寬字符字符串.
很多STL-風(fēng)格容器支持==, 所以你可以使用Eq(expected_container)或簡單地expected_container來精確匹配容器. 如果你想直接寫元素或更靈活地匹配, 可以使用:
ElementsAre(e0, e1, ..., en) |
argument 有n + 1個元素, 每i個元素匹配ei, 它們可以是一個值或是一個匹配器. 允許0到10個參數(shù). |
ElementsAreArray(array) 或 ElementsAreArray(array, count) |
和ElementsAre()相同, 除了預(yù)期值/匹配器來源于一個C風(fēng)格數(shù)組. |
Field(&class::field, m) |
argument.field (或 argument->field, 當(dāng)argument是一個指針時)與匹配器m匹配, 這里的argument是一個class類的實例. |
Property(&class::property, m) |
argument.property() (或 argument->property(), 當(dāng)argument是一個指針時)與匹配器m匹配, 這里的argument是一個class類的實例. |
ResultOf(f, m) |
f(argument) 與匹配器m匹配, 這里的f是一個函數(shù)或函數(shù)對象. |
Pointee(m) |
argument (不論是智能指針還是原始指針) 指向的值與匹配器m匹配. |
Truly(predicate) |
predicate(argument) 返回值為true, 這里的predicate是一個函數(shù)或函數(shù)對象. |
多參數(shù)匹配器
這些匹配器為元組類型(tuple types). 它們可以用在.WithArguments()里匹配兩個參數(shù)的函數(shù):
Eq() |
arg1 == arg2 |
Ge() |
arg1 >= arg2 |
Gt() |
arg1 > arg2 |
Le() |
arg1 <= arg2 |
Lt() |
arg1 < arg2 |
Ne() |
arg1 != arg2 |
復(fù)合匹配器
你可以把一個或多個匹配器合成一個匹配器:
AllOf(m1, m2, ..., mn) |
argument 匹配所有的匹配器m1到mn. |
AnyOf(m1, m2, ..., mn) |
argument 至少匹配m1到mn中的一個. |
Not(m) |
argument 不與匹配器m匹配. |
MatcherCast<T>(m) |
轉(zhuǎn)換匹配器m為Matcher<T>類型. |
通過Matches(m),Google Mock 能讓你轉(zhuǎn)換一個匹配器m到一個無參數(shù)謂詞(就象STL算法使用的一樣).
ASSERT_THAT(expression, m) |
如果expression的值和匹配器m不匹配,就產(chǎn)生一個致命失敗. |
EXPECT_THAT(expression, m) |
如果expression的值和匹配器m不匹配,就產(chǎn)生一個非致命失敗. |
動作(Actions)
動作 (Actions)指定了一個模擬函數(shù)被調(diào)用時應(yīng)該做什么.
Return() |
從一個void的模擬函數(shù)中返回 |
Return(value) |
返回value. |
ReturnNull() |
返回NULL指針. |
ReturnRef(variable) |
返回variable的引用. |
Assign(&variable, value) |
給variable 賦值value. |
SetArgumentPointee<N>(value) |
給第N(0-based)個參數(shù)指向的變量賦值value. |
SetArrayArgument<N>(first, last) |
拷貝源范圍[first, last)里的元素到第N(0- based)個參數(shù)指向的數(shù)組, 它可以是一個指針或一個迭代器. 這個動作不會改變源范圍元素的所有權(quán). |
SetErrnoAndReturn(error, value) |
設(shè)置errno為error,并且返回value. |
Invoke(f) |
使用模擬函數(shù)的參數(shù)調(diào)用f, 這里的f可以是全局/靜態(tài)函數(shù)或函數(shù)對象. |
Invoke(object_pointer, &class::method) |
使用模擬函數(shù)的參數(shù)調(diào)用object_pointer對象的mothod方法. |
InvokeWithoutArgs(f) |
無參數(shù)調(diào)用f, 這里的f可以是全局/靜態(tài)函數(shù)或函數(shù)對象. |
InvokeWithoutArgs(object_pointer, &class::method) |
無參數(shù)調(diào)用object_pointer對象的mothod方法. |
InvokeArgument<N>(arg1, arg2, ..., argk) |
調(diào)用模擬函數(shù)的第N個(0-based)參數(shù), 這個參數(shù)必須是一個函數(shù)或函數(shù)對象, 傳入這里的k個參數(shù). |
這里調(diào)用函數(shù)的返回值將作為動作的返回值.
當(dāng)定義一個函數(shù)或函數(shù)對象用于Invoke*(), 你可以使用Unused來聲明一些不用的參數(shù):
double Distance(Unused, double x, double y) { return sqrt(x*x + y*y); }
...
EXPECT_CALL(mock, Foo("Hi", _, _)).WillOnce(Invoke(Distance));
在 InvokeArgument<N>(...), 如果一個參數(shù)需要引用傳遞, 可以使用ByRef()包裝. 例如,
InvokeArgument<2>(5, string("Hi"), ByRef(foo))
調(diào)用模擬函數(shù)的第二個參數(shù), 以傳值的方式傳入5和string("Hi"), 引用的方式傳入foo.
DoDefault() |
使用默認(rèn)動作(由ON_CALL()指定或內(nèi)置). |
注意: 由于技術(shù)原因, DoDefault()不能用于復(fù)合動作 - 嘗試的結(jié)果是一個運行期錯誤.
DoAll(a1, a2, ..., an) |
每次發(fā)動時執(zhí)行a1到an的所有動作. |
IgnoreResult(a) |
執(zhí)行動作a并忽略它的返回值. a不能返回void. |
WithArg<N>(a) |
傳入模擬函數(shù)的第N個(0-based)參數(shù)作為動作a的參數(shù)并執(zhí)行之. |
WithArgs<N1, N2, ..., Nk>(a) |
傳入選中的模擬函數(shù)的多個第N個(0-based)參數(shù)作為動作a的參數(shù)并執(zhí)行之. |
WithoutArgs(a) |
無參數(shù)執(zhí)行動作a. |
基數(shù)(Cardinalities)
基數(shù)用于Times()中來指定模擬函數(shù)將被調(diào)用多少次:
AnyNumber() |
函數(shù)可以被調(diào)用任意次. |
AtLeast(n) |
預(yù)計至少調(diào)用n次. |
AtMost(n) |
預(yù)計至多調(diào)用n次. |
Between(m, n) |
預(yù)計調(diào)用次數(shù)在m和n(包括n)之間. |
Exactly(n) 或 n |
預(yù)計精確調(diào)用n次. 特別是, 當(dāng)n為0時,函數(shù)應(yīng)該永遠(yuǎn)不被調(diào)用. |
序列(Sequences)
序列 (Sequences) 指定預(yù)期的順序. 在同一序列里的所有預(yù)期調(diào)用必須按它們指定的順序發(fā)生; 反之則可以是任意順序.
建立序列:
Sequence s1, s2;
使用序列:
EXPECT_CALL(foo, Reset())
.InSequence(s1, s2)
.WillOnce(Return(true));
EXPECT_CALL(foo, GetSize())
.InSequence(s1)
.WillOnce(Return(1));
EXPECT_CALL(foo, Describe(A<const char*>()))
.InSequence(s2)
.WillOnce(Return("dummy"));
(Reset()必須在GetSize()和Describe()之前被調(diào)用; 之后兩個則可以任意順序.)
方便地在一個序列中放入多個預(yù)期:
{
InSequence dummy;
EXPECT_CALL(...)...;
EXPECT_CALL(...)...;
...
EXPECT_CALL(...)...;
}
(在dummy的生命周期內(nèi)的所有預(yù)期的調(diào)用必須以精確的順序發(fā)生. 至于名字dummy與此無關(guān).)
驗證并重置 Mock
當(dāng)模板對象析構(gòu)時Google Mock將驗證其上的所有預(yù)期, 或者你也可以提前做這些事:
1. using testing::Mock;
2. ...
3.
4.
5. Mock::VerifyAndClearExpectations(&mock_obj);
6. ...
7.
8.
9.
10. Mock::VerifyAndClear(&mock_obj);