随着事物的接触越来越多,了解的越来越深入,我们总会发现一些新的问题或者不足。
就像前文提到的一样,我们在面对有对象的虚函数依赖的时候,可以使用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等的用法,前文有作说明,这里不多做赘述。 | 最前面的expectValues的数据定义及赋值,并且对错误码索引(4)特殊处理,赋值为 0[无错误] |
完善好测试用例之后,运行看看CppFreeMock是否能解决我们非虚函数依赖的问题:
测试用例成功通过,表明非虚函数receivedatafromdevice按照我们设定的预期执行。
对应的demo源码,请点击mocknonvirtualfunc