如何解决非虚函数对象依赖
随着事物的接触越来越多,了解的越来越深入,我们总会发现一些新的问题或者不足。
就像前文提到的一样,我们在面对有对象的虚函数依赖的时候,可以使用gmock
框架来为我们提供方便的模拟期望值,以便我们能撇除外界的影响(依赖)从逻辑上设计单元测试并持续的进行,但是并非所有对象的函数都设计成了虚函数,那么我们在面对依赖对象的非虚函数这个问题时,又该如何解决?
这个问题,已经有先行者遇到并且提出了解决方案:手动打桩、使用hook
技术。
手动打桩有一个stub
挺好用,只有一个头文件,包含进去就可以使用,但由于手动,所以使用起来相对有一些繁琐,并且不能很好的统计和校验调用次数。
使用hook
技术的有mockcpp
和CppFreeMock
,这里使用的是CppFreeMock
。因为它是基于gmock
而来,是对gmock
只能mock
虚函数的一个补充,并且在用法上也能完美兼容gmock
框架,如果单元测试已经是使用gtest+gmock
的组合了,那么使用CppFreeMock
的成本将不会高。
举个栗子:
void model::hardwardResponse() {
memset(m_aRecvResponse, 0, MAXLEN);
int iRet = _pdevice->receivedatafromdevice(m_aRecvResponse);
if (iRet == 0)
{
m_iHardwareErrCode = ERR_RECV_INVALID_LEN;
return;
}
m_iHardwareErrCode = m_aRecvResponse[ERRNO];
if (m_iHardwareErrCode == 0)
{
/* NO ERROR, response data process... */
}
}
这个函数,依赖于设备返回的数据。并且receivedatafromdevice
函数是一个非虚函数:
class device
{
private:
string _serialno;
string _version;
string _firewareversion;
devicetype _type;
hardware* _phardware;
public:
device();
~device();
bool senddatatodevice(unsigned char* buff, int len);
int receivedatafromdevice(unsigned char* receivedata);
char* requestdeviceinfo(int requesttype);
devicetype requestdevicetype();
};
我们不用修改原本已有的modelTest
测试套件,需要先将CppFreeMock
的头文件包含进来
#include "cpp_free_mock.h"
然后,开始设计我们的测试用例:
TEST_F(modelTest, hardwareResponse_Lenis0) {
/* 测试硬件返回长度为0的情况 */
// 准备动作
// 执行函数
pm->hardwardResponse();
// 校验期望
EXPECT_EQ(pm->getHardwardCode(), 44444);
}
TEST_F(modelTest, hardwareResponse_Success) {
/* 测试硬件数据正常的情况 */
// 准备动作
// 执行函数
pm->hardwardResponse();
// 校验期望
EXPECT_EQ(pm->getHardwardCode(), 0);
EXPECT_EQ(memcmp(pm->getResponseData(), expectValues, 254), 0);
}
TEST_F(modelTest, hardwareResponse_Error) {
/* 测试硬件数据异常的情况 */
// 准备动作
// 执行函数
pm->hardwardResponse();
// 校验期望
EXPECT_EQ(pm->getHardwardCode(), 4);
}
根据上面hardwardResponse
函数的实现,设计出上述三个单元测试用例,那么准备工作该如何使用CppFreeMock
来设定预期呢?下面以第二个测试用例作详细说明:
// 准备动作
unsigned char expectValues[1024];
for (int i = 0; i < 255; i++) {
expectValues[i] = (i==4)?0:i;
}
auto mockerDevice = MOCKER(&device::receivedatafromdevice);
EXPECT_CALL(*mockerDevice, MOCK_FUNCTION(_,_)).Times(1).WillOnce(DoAll(SetArrayArgument<1>(expectValues, expectValues+254), Return(32)));
auto mockerDevice = MOCKER(&device::receivedatafromdevice); // 创建
device
类的非虚成员函数receivedatafromdevice
的mock
对象auto : 这里用的
auto
是C++11
中的关键字MOCKER :
MOCKER
宏是CppFreeMock
中定义的,其作用是用于创建指定类的指定函数的mock
对象。普通成员函数用法:MOCKER(&类名::函数名) --例–>MOCKER(&device::receivedatafromdevice)
静态成员函数用法:MOCKER(类名::函数名) --例–>MOCKER(device::receivedatafromdevice)
普通全局函数用法:MOCKER(函数名) --例–>MOCKER(receivedatafromdevice)更多用法请查阅
CppFreeMock
EXPECT_CALL(*mockerDevice, MOCK_FUNCTION(
_
,_
)).Times(1)
.WillOnce(DoAll(SetArrayArgument<1>(expectValues, expectValues+254), Return(32))); // 期望receivedatafromdevice
函数调用1
次,传出的数据是expectValues
数组中的前255
个内容,并且返回接收数据长度为32
个字节MOCK_FUNCTION : 宏是
CppFreeMock
中定义的,表明是一个mock
函数对象。
DoAll : 表明这次函数调用时,gmock
需要执行DoAll(Action,Action)
中的多个Action
SetArrayArgument(fisrtAddr, lastAddr) : 表示需要将从【firstAddr
,lastAddr
】 段中的数据传给第 n 个参数其中
Times
、WillOnce
、Return
等的用法,[[5-如何解决虚函数对象依赖?|前文(函数有其他对象虚函数依赖如何单元测试)]] 有作说明,这里不多做赘述。最前面的
expectValues
的数据定义及赋值,并且对错误码索引(4
)特殊处理,赋值为0
[无错误]
完善好测试用例之后,运行看看CppFreeMock
是否能解决我们非虚函数依赖的问题:
测试用例成功通过,表明非虚函数receivedatafromdevice
按照我们设定的预期执行。
对应的demo源码,请点击 mocknonvirtualfunc
也可扫码关注博主同名公众号"不解之榬",回复 “非虚” 获取