mock工具的作用是指定函数的行为(模拟函数的行为)。可以对入参进行校验,对出参进行设定,还可以指定函数的返回值。
(1)mock规范:每个MOCKER(function)开始,跟一系列的.stubs、.with、.will等的内容的整体,称为一个mock规范。
(2)核心关键字:指stubs/defaults/expects/before/with/after/will/then/id等这些直接跟在点后面的关键字。
(3)扩展关键字:指once()/eq()/checkWith()/returnValue()/repeat()等这些作为核心关键字参数的关键字。
下面,请看两段mockcpp的使用规范示例代码,其中带“/”或者“|”的表示在该位置可能有多种选择;带中括号的表示是可选的。
(1) 开源。
(2) mockcpp支持C函数的mock和虚接口的mock。
(3) VC下,mockcpp支持cdecl和stdcall调用约定的函数。(socket/bind/ftp等函数就是stdcall调用约定的)
(4) mockcpp的语法清晰、简单,容易理解(参见下面两个sample)。
(5) mockcpp的错误信息提示非常友好,包含完整的mock规范定义和实际运行情况(参见下面的样例)。
Unexpected invocation: the invocation cannot be found in allowed invoking list.
Invoked: add((int)0x1/1, (int)0x2/2)
Allowed:
method(add)
.stubs()
.invoked(0)
.with(eq((int)0xa/10), eq((int)0x14/20))
.will(returnValue((int)0xc8/200));
(9) mockcpp是强类型检查的,强类型检查也是C/C++的一个优势,比如eq(3),如果调用函数时用的参数是_UL,那么就应该用eq((_UL)3),对于函数返回值也是一样,声明的返回值类型应该跟mock中定义的一致。mock中支持类型检查,可能会发现更多代码BUG的。
C++强类型检查,也是为了提高程序安全性,有些问题通过类型检查在编译期可以发现的,就不需要在运行时再痛苦的定位了。C/C++的强类型检查是优势,我们不用把它抛弃了。
下面,请看两段mockcpp的使用规范示例代码,其中带“/”或者“|”的表示在该位置可能有多种选择;带中括号的表示是可选的。
TEST(mockcpp simple sample)
{
MOCKER(function) / MOCK_METHOD(mocker, method)
.stubs() / defaults() / expects(once())
[.before("some-mocker-id")]
[.with(eq(3))]
[.after("some-mocker-id")]
.will(returnValue(1)) / .will(repeat(1, 20))
[.then(returnValue(2))]
[.id("some-mocker-id")]
}
TEST(mockcpp detail sample)
{
MOCKER(function) / MOCK_METHOD(mocker, method)
.stubs() / defaults() / expects(never() | once() | exactly(3) | atLeast(3) | atMost(3) )
[.before("some-mocker-id")]
[.with( any() | eq(3) | neq(3) | gt(3) | lt(3) | spy(var_out) | checkWith(check_func)
| outBound(var_out) | outBoundP(var_out_addr, var_size) | mirror(var_in_addr, var_size)
| smirror(string) | contains(string) | startWith(string) | endWith(string) )]
[.after("some-mocker-id")]
.will( returnValue(1) | repeat(1, 20) | returnObjectList(r1, r2)
| invoke(func_stub) | ignoreReturnValue()
| increase(from, to) | increase(from) | throws(exception) | die(3))
[.then(returnValue(2))]
[.id("some-mocker-id")]
}
1、扩展关键字分类: expects里面的叫匹配关键字(Matcher); with里面的叫约束关键字(Constraint); will/then里面的叫桩关键字(Stub)。
2、spy的作用是监视执行该被mock的函数function被调用时传入的值,会保存在var_out中,供用例中其它地方使用。
3、outBound的作用是设置函数function的出参的值。多半是把该值作为后面部分被测代码的输入。(注意与spy区别)
4、outBoundP,与outBound作用相同,只是用于数组的情况。
5、mirror的作用是对数组类型的入参进行检查。(outBoundP是设置出参的值,两者是不同的)
6、check的作用是进行定制化的入参检查,比如只检查结构体的部分成员。可以通过函数指针或者仿函数的方式指定,用仿函数还能预先保存一些值,非常方便。(有些mock工具叫它follow)
7、check也能够用于设置出参的情况。
8、die表示程序退出,并且返回指定的值。它是异常退出,用于模拟一个函数调用崩溃的情况。
9、increase(from, to),表示依次返回from到to的对象,任何重载了++运算符的对象都可以用。
10、outBound和outBoundP都可以带一个约束参数,用于对参数进行检查,因为有同时作为in和out的参数。(如:outBound(var_out, eq(3)) )
1、mock C函数或者类的静态成员方法用MOCKER;
mock 类的非静态成员方法需要先用
MockObject<MyClass> mocker;
声明一个mock对象,再用MOCK_METHOD(mocker, method)来mock指定方法。
2、紧跟着MOCKER/MOCK_METHOD之后的是stubs、或者defaults、或者expects,三个必须有一个。(这是与AMOCK不同的地方,在这个层次上确定这三个关键字必须有一个,可以让mock语法更清晰)
stubs 表示指定函数的行为,不校验次数。 expects 与stubs的区别就是校验次数。(.expects(once()) / .expects(never()) / .expects(exactly(123))) defaults 表示定义一个默认的mock规范,但它优先级很低;如果后面有stubs或者expects重新指定函数行为,就会按照新指定的来运行。(一般用在setup中)
3、用will指定函数的返回值;
如果要指定20次调用都返回1,则用.will(repeat(1, 20)); 要指定第一次返回1,第二次返回2,第三次返回3,就用
.will(returnValue(1))
.then(returnValue(2))
.then(returnValue(3))
如果你指定了前三次的返回值依次为1、2、3,那么第四次、第五次调用,都是返回最后的返回值3。
4、用id给一个mock规范指定一个名字,然后可以用after、before来指定多个mock应该的调用顺序。
注意before在with前,after在with后,id在整个mock规范的最后。
5、使用mockcpp时,校验是否按照mock规范进行调用的,应该用: GlobalMockObject::verify(); verify之后,会自动执行reset。(如果是对象的mock,应该用mocker.verify(),同样也会自动reset。)
如果单单只想reset,也可以:(这很少见,难道前面你定义的mock规范都不想要了,也不校验?那为何要定义呢?) GlobalMockObject::reset();
一般是在teardown中调用verify。
6、如果要对某个函数(比如add)指定多个调用不同的行为(多个mock规范),比如调用入参为1,2时,返回30;调用入参为3、4时,返回700。那么可以这样写:
MOCKER(add)
.stubs()
.with(eq(1), eq(2))
.will(returnValue(30));
MOCKER(add)
.stubs()
.with(eq(3), eq(4))
.will(returnValue(700));
7、如果对于6的例子,还希望校验次数,则把stubs改为expects(once())这样的即可。
8、如果对于6的例子,还希望对调用顺序做严格要求,必须第一次调用参数是1、2,第二次是3、4,那么可以这样写:
MOCKER(add)
.stubs()
.with(eq(1), eq(2))
.will(returnValue(30))
.id("first");
MOCKER(add)
.stubs()
.with(eq(3), eq(4))
.after("first")
.will(returnValue(700));
9、指定出参的方法(例子中出参为指针类型,出参为引用的outBound(var)即可,更加简单)
void function(int *val)
{
}
应该用下面方式来指定出参:
int expect = 10;
MOCKER(function)
.stubs()
.with(outBoundP(&expect, sizeof(expect)));
10、对同一个函数指定多个mock规范,那么这些mock规范一般是应该在输入方面有差异的,否则没必要指定多个。而且,在输入方面无差异的两个mock规范,让mockcpp内部无法判断是否满足规范约束。 比如,测试函数 void function(int in, int &out),前面一个是入参,后面一个是出参,希望in为1时,输出out为100,in为2时,输出out为3,那么应该这样写:
int out = 100;
MOCKER(function)
.expects(once())
.with(eq(1), outBound(out));
out = 3;
MOCKER(function)
.expects(once())
.with(eq(2), outBound(out));
假设你把eq(1)、eq(2)写为any(),那么mockcpp会判断你的两个调用function(1, out)和function(2, out)都符合第一个mock规范,从而报告与你定义的次数1不匹配。
11、有时候需要对同一个函数指定多个mock规范,并且它们是有规律的,那么可以借助循环来简化代码。 假设要mock的函数是void function(int in),希望它依次以0、1、2...10为入参被调用,每次都调用一次,那么可以像这样写:
MOCKER(function)
.expects(once())
.with(eq(0))
.id("0");
for (int i = 1; i <= 10; i++)
{
MOCKER(function)
.expects(once())
.with(eq(i))
.after(string(i - 1))
.id(string(i));
}
12、spy约束关键字应用举例。 spy的作用是监视,下面例子,就是监视test在调用func时,传入的值是多少。有些时候,测试用例需要这样的值,这种方式是很有效的。
void func(int var)
{
}
void test()
{
int var = 10;
func(var);
}
TEST(sample test)
{
int var;
MOCKER(func)
.stubs()
.with(spy(var));
.with(eq(10));
test();
ASSERT_EQ(var, 10);
}
13、check约束关键字的用法。 约束关键字中,有很多都是对入参进行检查的,比如eq、neq、lt、gt等等,但它们都只实现了非常简单的检查。 如果用户想做一个定制化的检查,那么就可以用check。 例1:假设用户想检查入参p指向的结构的字段b是不是10,那么可以如下这样写:
struct AA
{
int a;
int b;
};
void func(int in, AA *p)
{
}
// 实现一个检查函数,入参类型跟要检查的入参相同,返回值为bool,返回true表示检查通过。
bool check_func(AA *p)
{
if ( p->b == 10)
{
return true;
}
return false;
}
TEST(sample test)
{
MOCKER(func)
.stubs()
.with(any(), checkWith(check_func));
func(in, p);
}
例2:如果需要检查b是否小于某个给定的数,而且有多个用例,每个用例中与b比较的数不同,则可以使用仿函数的方式。
// 实现一个仿函数类,即重载“()”运算符的类。
struct CheckFunctor
{
CheckFunctor(int _base) : base(_base){}
bool operator ()(AA *p)
{
if (p->b < base)
return true;
return false;
}
int base;
};
TEST(b should less than 100)
{
MOCKER(func)
.stubs()
.with(any(), checkWith(CheckFunctor(100)));
func(in, p);
}
TEST(b should less than 200)
{
MOCKER(func)
.stubs()
.with(any(), checkWith(CheckFunctor(200)));
func(in, p);
}
14、只要打桩了,那么就不会运行原函数,都是与mock规范匹配,运行桩。 下面例子中,对add的入参为1、2的情况打桩了,如果调用了入参为2、3的情况,则会报告调用在允许的mock规范列表中不存在的错误。
TEST(sample test)
{
MOCKER(add)
.expects(once())
.with(eq(1), eq(2))
.will(returnValue(100));
ASSERT_EQ(100, add(1, 2));
ASSERT_EQ(50, add(2, 3));
}
15、带有返回值的函数,MOCKER后面必须有will,否则mockcpp认为无返回值,校验时会发现返回类型不匹配。