FakeIt 使用学习
前言
FakeIt 旨在实现简具有表达性的测试桩函数。其中主要利用了一些 C++11 标准的特性,因此在使用 FakeIt 时确保编译器支持 C++11 标准。
Include
想要使用 FakeIt 首先添加对应的头文件,以及对应的命名空间:
#include <fakeit.hpp>
using namespace fakeit;
Stubbing
假设存在以下需要进行打桩的接口:
struct SomeInterface {
virtual int foo(int) = 0;
virtual int bar(int,int) = 0;
};
使用 FakeIt 打桩:
Mock<SomeInterface> mock;
// 只返回一次值,第二次调用会出错
When(Method(mock,foo)).Return(1);
// 设定多次返回的值,第一次返回 1, 第二次返回 2, 第三次返回 3
When(Method(mock,foo)).Return(1,2,3);
When(Method(mock,foo)).Return(1).Return(2).Return(3);//含义同上
// 设定 56 次调用返回 1.
When(Method(mock,foo)).Return(56_Times(1));
// Return many values many times (First 100 calls will return 1, next 200 calls will return 2)
When(Method(mock,foo)).Return(100_Times(1), 200_Times(2));
// 一直返回 1
When(Method(mock,foo)).AlwaysReturn(1);
Method(mock,foo) = 1;//含义同上
设定带有参数时特殊返回值的桩函数:
// 设置当传入 1 (foo(1)) 时返回 100 注意仅可以调用一次
When(Method(mock,foo).Using(1)).Return(100);
When(Method(mock,foo)(1)).Return(100);
// 设置当传入 1 (foo(1)) 时永远返回 100。其他的调用则一直返回 0
When(Method(mock,foo)).AlwaysReturn(0); // Any invocation of foo will return 0
When(Method(mock,foo).Using(1)).AlwaysReturn(100); // override only for 'foo(1)'
// 这两句的含义时一样的,都是当传入 1 (foo(1)) 时永远返回 0
When(Method(mock,foo).Using(1)).AlwaysReturn(0);
Method(mock,foo).Using(1) = 0;
设定带有抛出异常的桩函数:
// Throw once
When(Method(mock,foo)).Throw(exception());
// Throw several times
When(Method(mock,foo)).Throw(exception(),exception());
// Throw many times
When(Method(mock,foo)).Throw(23_Times(exception()));
// Always throw
When(Method(mock,foo)).AlwaysThrow(exception());
其他技巧:
// 使用 lamdba 函数设定想要实现的桩函数具体内容
When(Method(mock,foo)).Do([](int a)->int{ ... });
When(Method(mock,foo)).AlwaysDo([](int a)->int{ ... });
// Or, with C++14:
When(Method(mock,foo)).AlwaysDo([](auto a){ ... });
虚析构函数打桩:
struct SomeInterface {
virtual ~SomeInterface() = 0;
};
Mock<SomeInterface> mock;
When(Dtor(mock)).Do([](){ ... });
Faking
大部分情况下打桩函数不会有什么具体操作。可以通过使用 Faking 显式地方法来指定“无为”行为。
struct SomeInterface {
virtual void foo(int) = 0;
virtual int bar(int,int) = 0;
virtual ~SomeInterface() = 0;
};
Mock<SomeInterface> mock;
// Following 3 lines do exactly the same:
Fake(Method(mock,foo));
When(Method(mock,foo)).AlwaysReturn();
When(Method(mock,foo)).AlwaysDo([](...){});
// And here is another example:
Fake(Method(mock,bar));
When(Method(mock,bar)).AlwaysReturn(0);
When(Method(mock,bar)).AlwaysDo([](...){return 0;});
也可以一次声明多个桩函数:
Fake(method1, method2, ...)
Fake(Method(mock,foo), Method(mock,bar));
Fake 析构:
Fake(Dtor(mock)); // same as When(Dtor(mock)).AlwaysDo([](){});
参数匹配 Argument Matching
// Stub foo to return 1 only when: arg > 1.
When(Method(mock,foo).Using(Gt(1)).Return(1);
// Stub foo to return 1 only when: arg >= 1.
When(Method(mock,foo).Using(Ge(1)).Return(1);
// Stub foo to return 1 only when: arg < 1.
When(Method(mock,foo).Using(Lt(1)).Return(1);
// Stub foo to return 1 only when: arg <= 1.
When(Method(mock,foo).Using(Le(1)).Return(1);
// Stub foo to return 1 only when: arg != 1.
When(Method(mock,foo).Using(Ne(1)).Return(1);
// Stub foo to return 1 only when: arg == 1.
// The following 2 lines do exactly the same
When(Method(mock,foo).Using(Eq(1)).Return(1);
When(Method(mock,foo).Using(1)).Return(1);
// Stub foo to return 1 for any value.
// The following 2 lines do exactly the same
When(Method(mock,foo).Using(Any<int>()).Return(1);
When(Method(mock,foo).Using(_).Return(1);
// Stub foo to return 1 when arg1 == 1 and arg2 is any int.
// The following 3 lines do exactly the same:
When(Method(mock,foo).Using(1, _)).Return(1);
When(Method(mock,foo).Using(1, Any<int>())).Return(1);
When(Method(mock,foo).Using(Eq(1), _)).Return(1);
调用匹配 Invocation Matching
如何仅匹配参数 “a” 大于参数 “b” 的调用?
在这些情况下,参数匹配不够强大,因为每个参数匹配器仅限于一个参数。这里需要的是调用匹配。
// Stub foo to return 1 only when argument 'a' is even.
auto argument_a_is_even = [](int a){return a%2==0;};
When(Method(mock,foo).Matching(argument_a_is_even)).Return(1);
// Throw exception only when argument 'a' is negative.
auto argument_a_is_negative = [](int a){return a < 0;};
When(Method(mock,foo).Matching(argument_a_is_negative)).Throw(exception());
// Stub bar to throw exception only when argument 'a' is bigger than argument 'b'.
auto a_is_bigger_than_b = [](int a, int b){return a > b;};
When(Method(mock,bar).Matching(a_is_bigger_than_b)).Throw(exception());
// Or, with C++14:
When(Method(mock,bar).Matching([](auto a, auto b){return a > b;})).Throw(exception());
验证 Verification
Mock<SomeInterface> mock;
When(Method(mock,foo)).AlwaysReturn(1);
SomeInterface & i = mock.get();
// Production code:
i.foo(1);
i.foo(2);
i.foo(3);
i.bar(2,1);
// 验证 foo 是否至少被调用一次 (The four lines do exactly the same)
Verify(Method(mock,foo));
Verify(Method(mock,foo)).AtLeastOnce();
Verify(Method(mock,foo)).AtLeast(1);
Verify(Method(mock,foo)).AtLeast(1_Time);
// 验证 foo 是否被正确调用了3次。. (The next two lines do exactly the same)
Verify(Method(mock,foo)).Exactly(3);
Verify(Method(mock,foo)).Exactly(3_Times);
// 验证 foo(1) 刚被调用一次
Verify(Method(mock,foo).Using(1)).Once();
Verify(Method(mock,foo).Using(1)).Exactly(Once);
// 验证 bar(a>b) 被调用一次
Verify(Method(mock,bar).Matching([](int a, int b){return a > b;})).Exactly(Once);
// Or, with C++14:
Verify(Method(mock,bar).Matching([](auto a, auto b){return a > b;})).Exactly(Once);
验证调用顺序:
// 验证 foo(1) 在 foo(3) 之前的任何地方被调用过
Verify(Method(mock,foo).Using(1), Method(mock,foo).Using(3));
验证确切的调用顺序:
- 可以通过以下方式表示序列:
- 两次连续的 foo 调用:
Method(mock,foo) * 2
- 调用 foo 后再调用 bar:
Method(mock,foo) + Method(mock,bar)
- 连续两次调用 foo + bar,即 foo + bar + foo + bar:
(Method(mock,foo) + Method(mock,bar)) * 2
- 验证实际调用序列中是否存在特定序列:
// 验证实际的调用序列至少包含两次连续的 foo 调用。
Verify(Method(mock,foo) * 2);
// 验证实际调用序列是否包含一次连续两次的 foo 调用。
Verify(Method(mock,foo) * 2).Exactly(Once);
// 验证实际的调用序列包含对 foo(1)的调用,然后对 bar(1,2)进行恰好两次的调用。
Verify(Method(mock,foo).Using(1) + Method(mock,bar).Using(1,2)).Exactly(2_Times);
一个序列包含多个 Mock 实例:
Mock<SomeInterface> mock1;
Mock<SomeInterface> mock2;
When(Method(mock1,foo)).AlwaysReturn(0);
When(Method(mock2,foo)).AlwaysReturn(0);
SomeInterface & i1 = mock1.get();
SomeInterface & i2 = mock2.get();
// Production code:
i1.foo(1);
i2.foo(1);
i1.foo(2);
i2.foo(2);
i1.foo(3);
i2.foo(3);
// Verify exactly 3 occurrences of the sequence {mock1.foo(any int) + mock2.foo(any int)}.
Verify(Method(mock1,foo) + Method(mock2,foo)).Exactly(3_Times);
验证没有其他调用 Verify No Other Invocations
Mock<SomeInterface> mock;
When(Method(mock,foo)).AlwaysReturn(0);
When(Method(mock,bar)).AlwaysReturn(0);
SomeInterface& i = mock.get();
// call foo twice and bar once.
i.foo(1);
i.foo(2);
i.bar("some string");
// verify foo(1) was called.
Verify(Method(mock,foo).Using(1));
// Verify no other invocations of any method of mock.
// Will fail since foo(2) & bar("some string") are not verified yet.
VerifyNoOtherInvocations(mock);
// Verify no other invocations of method foo only.
// Will fail since foo(2) is not verified yet.
VerifyNoOtherInvocations(Method(mock,foo));
Verify(Method(mock,foo).Using(2));
// Verify no other invocations of any method of mock.
// Will fail since bar("some string") is not verified yet.
VerifyNoOtherInvocations(mock);
// Verify no other invocations of method foo only.
// Will pass since both foo(1) & foo(2) are now verified.
VerifyNoOtherInvocations(Method(mock,foo));
Verify(Method(mock,bar)); // verify bar was invoked (with any arguments)
// Verify no other invocations of any method of mock.
// Will pass since foo(1) & foo(2) & bar("some string") are now verified.
VerifyNoOtherInvocations(mock);.
验证范围 Verification Scoping
验证作用域,显式指定用于验证序列的一组实际调用的方法。
假设我们具有以下接口:
struct IA {
virtual void a1(int) = 0;
virtual void a2(int) = 0;
};
struct IB {
virtual void b1(int) = 0;
virtual void b2(int) = 0;
};
以及以下两个模拟对象
Mock<IA> aMock;
Mock<IB> bMock;
生产代码将创建以下实际调用列表:
aMock.a1(1);
bMock.b1(1);
aMock.a2(1);
bMock.b2(1);
验证:
// Will PASS since the scenario {aMock.a1 + bMock.b1} is part of the
// actual list {aMock.a1 + bMock.b1 + aMock.a2 + bMock.b2}
// {aMock.a1 + bMock.b1} 是实际调用列表
// {aMock.a1 + bMock.b1 + aMock.a2 + bMock.b2} 的一部分 因此此项通过
Using(aMock,bMock).Verify(Method(aMock, a1) + Method(bMock, b1));
// Will FAIL since the scenario {aMock.a1 + bMock.b1} is not part of the
// actual list {aMock.a1 + aMock.a2}
// 使用 Using 指定为 aMock 的调用,因此实际调用列表为 {aMock.a1 + aMock.a2},
// 而 {aMock.a1 + bMock.b1} 不是其中一部分 因此此项失败
Using(aMock).Verify(Method(aMock, a1) + Method(bMock, b1));
// Will PASS since the scenario {aMock.a1 + aMock.a2} is part of the
// actual list {aMock.a1 + aMock.a2}
// 同上
Using(aMock).Verify(Method(aMock, a1) + Method(aMock, a2));
默认情况下,FakeIt 使用已验证场景中涉及的所有模拟对象来隐式定义验证范围。 即以下两行的功能完全相同:
// 显示调用 aMock 和 bMock 的所有方法调用
Using(aMock,bMock).Verify(Method(aMock, a1) + Method(bMock, b1));
// 隐式使用 aMock 和 Mock 的所有方法调用
Verify(Method(aMock, a1) + Method(bMock, b1));
监视 Spying
在某些情况下,监视现有对象非常有用。 FakeIt 是唯一支持监视的 C++ 开源模拟框架。
class SomeClass {
public:
virtual int func1(int arg) {
return arg;
}
virtual int func2(int arg) {
return arg;
}
};
SomeClass obj;
Mock<SomeClass> spy(obj);
When(Method(spy, func1)).AlwaysReturn(10); // Override to return 10
Spy(Method(spy, func2)); // 监视 func2 而不更改任何行为
SomeClass& i = spy.get();
cout << i.func1(1); // will print 10.
cout << i.func2(1); // func2 没有桩函数
通常,所有 Mock 和验证功能都对监视对象起作用,就像对模拟对象起作用一样。
将桩重置为初始状态 Reset Mock to Initial State
在大多数情况下,需要在每个测试方法之前/之后将 mock 对象重置为初始状态。为此,只需将每个模拟对象的以下行添加到测试的初始化/清理代码中。
mock.Reset();
您还可以通过以下方式保留存根并仅清除收集的调用数据:
mock.ClearInvocationHistory();
继承与动态转换 Inheritance & Dynamic Casting
struct A {
virtual int foo() = 0;
};
struct B : public A {
virtual int foo() override = 0;
};
struct C : public B
{
virtual int foo() override = 0;
};
支持转基类:
Mock<C> cMock;
When(Method(cMock, foo)).AlwaysReturn(0);
C& c = cMock.get();
B& b = c;
A& a = b;
cout << c.foo(); // prints 0
cout << b.foo(); // prints 0
cout << a.foo(); // prints 0
dynamic_cast 支持:
Mock<C> cMock;
When(Method(cMock, foo)).AlwaysReturn(0);
A& a = cMock.get(); // get instance and upcast to A&
B& b = dynamic_cast<B&>(a); // downcast to B&
cout << b.foo(); // prints 0
C& c = dynamic_cast<C&>(a); // downcast to C&
cout << c.foo(); // prints 0
Mock 重载方法 Mocking Overloaded Methods
Mock 重载的方法时,只需要指定方法的原型:
struct SomeInterface {
virtual int func() = 0;
virtual int func(int) = 0;
virtual int func(int, std::string) = 0;
};
Mock<SomeInterface> mock;
//stub the func with prototype: int()
When(OverloadedMethod(mock,func, int()) ).Return(1);
//stub the func with prototype: int(int)
When(OverloadedMethod(mock,func, int(int)) ).Return(2);
//stub the func with prototype: int(int,std::string)
When(OverloadedMethod(mock,func, int(int, std::string)) ).Return(3);
SomeInterface& i = mock.get();
cout << i.func(); // will print 1
cout << i.func(1); // will print 2
cout << i.func(1,""); // will print 3
mock const 重载的方法类似:
struct SomeInterface {
virtual int func(int) = 0;
virtual int func(int) const = 0;
};
Mock<SomeInterface> mock;
//stub the func with prototype: int(int)
When(OverloadedMethod(mock,func, int(int)) ).Return(1);
//stub the const func with prototype: int(int)
When(ConstOverloadedMethod(mock,func, int(int)) ).Return(2);
SomeInterface& v = mock.get();
const SomeInterface& c = mock.get();
cout << v.func(1); // will print 1
cout << c.func(1); // will print 2