目录
- 返回值
- Actions的组合
- 验证复杂参数
- mock副作用
- 改变mock对象的行为
- 设置返回类型的默认值
- 使用自定义函数作为Actions
通用示例
namespace mock_action {
class Foo {
public:
virtual ~Foo() {}
virtual int& GetBar() = 0; // 1
virtual int GetPointerValue() = 0; // 2
virtual int CalculateBar() = 0; // 3
virtual int Sign(int x) = 0; // 4
virtual int DoCombineActions() = 0; // 5
virtual void Mutate(bool mutate, int *value) = 0; // 6
};
class MockFoo : public Foo {
public:
MOCK_METHOD(int&, GetBar, (), (override));
MOCK_METHOD(int, GetPointerValue, (), (override));
MOCK_METHOD(int, CalculateBar, (), (override));
MOCK_METHOD(int, Sign, (int x), (override));
MOCK_METHOD(int, DoCombineActions, (), (override));
MOCK_METHOD(void, Mutate, (bool mutate, int* value), (override));
};
}
返回值
- mock函数使用
ReturnRef()
返回引用
// mock类函数为通用示例中的1
TEST(test_action, mock_action_returnref) {
mock_action::MockFoo foo;
int x = 10;
EXPECT_CALL(foo, GetBar()).WillOnce(ReturnRef(x));
EXPECT_EQ(foo.GetBar(), 10);
}
如果将ReturnRef(x)换成Return(x)或者Return(10)都会报语法错误。
2. 返回live value(可以理解为实时值)
Return(x)保存了x的一份拷贝,在后续测试中会一直使用这个拷贝值而不会随着x的改变而改变,在x有改动的情况下就无法满足要求了。要mock的函数的返回值必须是引用才能使用ReturnRef,且不能使用std::ref()(原因TODO)。此时可以通过ReturnPointee解决该问题。
// mock类函数为通用示例中的 2
TEST(test_action, mock_action_returnpointer) {
mock_action::MockFoo foo;
int x = 0;
EXPECT_CALL(foo, GetPointerValue())
.WillRepeatedly(ReturnPointee(&x)); // 这样做的意义是什么?
x = 42;
EXPECT_EQ(foo.GetPointerValue(), 42);
}
注意,这里action中返回值可以根据动态值而改变。这里mock函数返回值并不是指针,但是action可以使用ReturnPointee,这一点和ReturnRef不同。
Actions的组合
想要执行不止一个action,可以使用::testing::DoAll实现actions的组合:
// mock类函数为通用示例中的 5
TEST(test_action, mock_combine_actions) {
mock_action::MockFoo foo;
EXPECT_CALL(foo, DoCombineActions()).WillOnce(DoAll(
[](){
std::cout << "DoCombineActions 1" << std::endl;
},
[] () {
std::cout << "DoCombineActions 2" << std::endl;
},
Return(10)
));
EXPECT_EQ(foo.DoCombineActions(), 10);
}
输出(EXPECT_CALL改成ON_CALL会报warning?TODO):
验证复杂参数
TODO
mock副作用
- 如果想改变出参,可以使用SetArgPointee()
// mock类函数为通用示例中的 6
TEST(test_action, mock_side_effects01) {
mock_action::MockFoo mutator;
EXPECT_CALL(mutator, Mutate(true, _))
.WillOnce(SetArgPointee<1>(5));
int x = 1;
mutator.Mutate(true, &x);
EXPECT_EQ(x, 5);
}
SetArgPointee<index>(value)
index是从0开始的下标;且value必须支持赋值或者拷贝构造。
如果仍然要返回值,使用DoAll执行这两个action,但要注意需要将Return放到后面。
如果出参是数组,则需要使用 SetArrayArgument<N>(first, last)
(N只能从0开始吗?TODO)
TEST(test_action, mock_size_effects03) {
mock_action::MockArrayMutator mutator;
int values[5] = {1, 2, 3, 4, 5};
int values02[15] = {1,2,3};
EXPECT_CALL(mutator, Mutate(NotNull(), 15))
.WillOnce(SetArrayArgument<0>(values, values+5)); // N只能从0开始?
mutator.Mutate(values02, 15);
std::for_each(values02, values02+15, [](int n){
std::cout << n << std::endl;
});
}
改变mock对象的行为
如果想通过某个调用改变mock对象的行为,可以使用 ::testing::InSequence 指定这个调用之前和之后的不同行为
mock类代码
class ChangeBehavior {
public:
virtual ~ChangeBehavior() {}
virtual bool IsDirty() = 0;
virtual void Flush() = 0;
virtual bool FlushIfDirty() = 0;
};
class MockChangeBehavior : public ChangeBehavior {
public:
MOCK_METHOD(bool, IsDirty, (), (override));
MOCK_METHOD(void, Flush, (), (override));
MOCK_METHOD(bool, FlushIfDirty, (), (override));
};
测试代码
TEST(test_action, mock_change01) {
mock_action::MockChangeBehavior my_mock;
{
InSequence seq;
EXPECT_CALL(my_mock, IsDirty()).WillRepeatedly(
DoAll(
[] () { std::cout << "IsDirty(), return true" << std::endl; },
Return(true)
)
);
EXPECT_CALL(my_mock, Flush()).WillOnce(
[] () { std::cout << "Flush" << std::endl; }
);
EXPECT_CALL(my_mock, IsDirty()).WillRepeatedly(
DoAll(
[] () { std::cout << "IsDirty(), return false" << std::endl; },
Return(false)
)
);
}
EXPECT_EQ(my_mock.IsDirty(), true);
my_mock.Flush();
EXPECT_EQ(my_mock.IsDirty(), false);
}
说明:这段代码会在my_mock.Flush()调用之前调用my_mock.IsDirty()时返回true,在my_mock.Flush()调用之后调用my_mock.IsDirty()返回false。
- 保存结果 TODO
设置返回类型的默认值
- 通过
::testing::DefaultValue
指定默认值。
// mock类函数为通用示例中的 3和4
TEST(test_action, mock_default_value) {
int x = 3;
DefaultValue<int>::Set(x);
mock_action::MockFoo foo;
EXPECT_CALL(foo, CalculateBar());
EXPECT_CALL(foo, Sign(_)); // 对Sign也是有效的
EXPECT_EQ(foo.CalculateBar(), 3);
EXPECT_EQ(foo.Sign(2), 3);
DefaultValue<int>::Clear();
}
这样设置后对相同类型是全局有效的;要注意使用Set
和Clear
的顺序。
- 如果要求相同类型默认返回值不同,可以使用
ON_CALL+EXPECT_CALL
的方式实现这一目的
// mock类函数为通用示例中的 4
TEST(test_action, mock_default_action) {
mock_action::MockFoo foo;
ON_CALL(foo, Sign(_)).WillByDefault(Return(-1));
ON_CALL(foo, Sign(0)).WillByDefault(Return(0));
ON_CALL(foo, Sign(Gt(0))).WillByDefault(Return(1));
EXPECT_CALL(foo, Sign(_)).Times(AnyNumber());
EXPECT_EQ(foo.Sign(5), 1);
EXPECT_EQ(foo.Sign(-9), -1);
EXPECT_EQ(foo.Sign(0), 0);
}
测试程序的输出是没有警告的。
Note that both ON_CALL and EXPECT_CALL have the same “later statements take precedence” rule, but they don’t interact. That is, EXPECT_CALLs have their own precedence order distinct from the ON_CALL precedence order.
使用自定义函数作为Actions
class ACFoo {
public:
virtual ~ACFoo(){};
virtual int Sum(int x, int y) = 0;
virtual bool ComplexJob(int x) = 0;
};
class ACMockFoo : public ACFoo {
public:
MOCK_METHOD(int, Sum, (int x, int y), (override));
MOCK_METHOD(bool, ComplexJob, (int x), (override));
};
int CalculateSum(int x, int y) {
std::cout << "Enter in CalculateSum, x: " << x << ", y: " << y << std::endl;
return x + y;
}
int Sum3(int x, int y, int z) {
std::cout << "Sum3" << std::endl;
return x + y + z;
}
int NewPermanentCallback(const std::function<int(int,int,int)>& f, int x) {
std::cout << "Enter in NewPermanentCallback" << std::endl;
return f(1,2,3);
}
class Helper {
public:
bool ComplexJob(int x) {
std::cout << "Helper::ComplexJob" << std::endl;
return true;
}
};
验证代码:
TEST(test_action, mock_use_functor01) {
mock_action::ACMockFoo foo;
mock_action::Helper helper;
EXPECT_CALL(foo, Sum(_, _))
.WillOnce(&mock_action::CalculateSum) // 1
.WillRepeatedly(Invoke(mock_action::NewPermanentCallback(mock_action::Sum3, 1))); // 2 这里不正确,编译报错TODO
EXPECT_CALL(foo, ComplexJob(_))
.WillOnce(Invoke(&helper, &mock_action::Helper::ComplexJob)) // 3
.WillOnce([] { std::cout << "lambda 1 " << std::endl; return true;}) // 4
.WillRepeatedly([](int x) { std::cout << "lambda 2 " << std::endl; return x > 0;}); // 5
foo.Sum(5,6);
foo.ComplexJob(10);
foo.ComplexJob(-1);
foo.ComplexJob(3);
}
分析:
- 使用全局自由函数,注意函数签名要一致
- 这里有问题,编译出错,需要解决
- 使用其他对象的成员函数,函数签名也要保持一致
- 4和5都是用lambda表达式,但是4的签名和mock函数不一样,为什么没有报错?TODO
如果作为action的这些函数参数个数多于mock函数,可以通过预先绑定的方式(类似std::bind)
using ::testing::Invoke;
class MockFoo : public Foo {
public:
MOCK_METHOD(char, DoThis, (int n), (override));
};
char SignOfSum(int x, int y) {
const int sum = x + y;
return (sum > 0) ? '+' : (sum < 0) ? '-' : '0';
}
TEST_F(FooTest, Test) {
MockFoo foo;
EXPECT_CALL(foo, DoThis(2))
.WillOnce(Invoke(NewPermanentCallback(SignOfSum, 5)));
EXPECT_EQ('+', foo.DoThis(2)); // Invokes SignOfSum(5, 2).
}
- 但是这里的NewPermanentCallback是什么?TODO