Mockcpp
Mockcpp 是 Google code的一个开源项目,用于模拟函数的行为,可以对入参进行校验,对出参进行设定,可以指定函数的返回值。
先理解打桩stub:简单来说,就是将一个函数用另一个函数(桩代码)实现进行替换,达到在原有函数入口的位置执行新的实现。
打桩的目的:隔离、占位、控制
- 隔离是指将测试任务从产品项目中分离出来,是指能够独立编译、链接,并独立运行。隔离的基本方法就是打桩,将测试任务外,与测试任务相关的代码,用桩来代替,实习分离测试的任务
- 占位就是用桩对未实现的函数进行占位
- 控制指在测试时,认为设定相关的代码,使之符合测试需求
一、关键字
- .stubs():指定函数的行为,不校验次数
- .defaults():定义一个mock规范
- .expects():指定函数行为,校验次数,里面可以放once(),never()等
- .id():给一个mock规范指定名字
- returnValue():指定匹配的调用所返回的值
- returnObjectList():匹配返回一系列的值
- throws():抛出异常
- ignoreReturnValue():忽略返回值
- invoke():实现函数替换
MOCK_METHOD后必须有.stubs()、.defaults()、.expects() 中的一个
MOCKER: 全局函数、静态成员函数可以用它mock
MOCK_METHOD:主要对非静态成员函数、虚函数等,需要先创建一个mocker
mock规范的verify/reset
GlobalMockObject::verify();
:使用mockcpp时,校验是否按照mock规范进行调用(如约定调用一次,该函数会检查是否调用了一次),verify执行完后,会自动执行reset
GlobalMockObject::reset();
: 仅执行reset进行清理(打桩函数恢复到打桩前),不做校验
若不使用verify/reset,在某个测试文件内包含多个测试样例TEST_F的时候,一些测试样例会无法通过,原因是mock对象在测试用例间不会清除,前一个TEST_F测试用例完成后mock对象还在,若在另一个测试用例中又对同样函数打桩,则此次打桩不会生效,调用的时候用的还是第一次打桩的约定。
二、常用使用场景
1.打桩跳过一个C语言函数
MOCKER(Func)
.stubs()
.will(returnValue(0));
2.指定一个函数的调用次数
MOCKER(Func)
.expects(once()) //只希望调用一次
.will(returnValue(0));
此外还有exactly(3) , atLeast(3), atMost(3), never(),分别表示调用三次,至少调用三次,最多调用三次,不调用
3.判断调用一个函数时的入参是否正确
MOCKER(Func)
.expects(once()) //调用到Func时,只希望调用一次
.with(any(), eq(3)) //第一个参数无所谓,第二个参数等于3
.will(returnValue(0));
4.用一个函数替代测试流程中会被调用的函数
//调用到MemberFunc时用Fake_MemberFunc来替代
//用来替代的函数第一个参数必须是被打桩类的指针,后面的参数与被打桩函数一致
unsigned int Fake_MemberFunc(ClassName *This, Unsigned int para)
{
//截取入参,判断
ASSERT_TRUR(para == 1);
//do something else
Return 0;
}
MOCKER(Func)
.expects(once())
.will(invoke(Fake_MemberFunc));
一、代码解读
#include "caseinfo.hpp"
#include "dtest_system_stub.h"
#include "test_hsi_stub.h"
#include "mockcpp/mokc.h"
#include "gmock/gmock.h"
#include "gtest/gtest.h"
#include "se_api.h"
#include "hsi_utils.h"
#include "libse7_link.h"
#include "libse7_link_check.h"
#include "libse7_local_data.h"
class Se7LinkCheck : public testing::Test //Test Fixture 生成初始的测试工程都有的 缺省
{
public:
void SetUp() override //setup和TearDown是每个类都要有的类似构造析构
{
(void)memset_s(g_se7LinkSimulateFault, sizeof(g_se7LinkSimulateFault), 0, sizeof(g_se7LinkSimulateFault));
}
void TearDown() override
{
GlobalMockObject::reset();
}
protected:
uint32_t chipId_{0};
uint32_t linkId_{0};
};
TEST_F(Se7LinkCheck, CheckLinkLost_SimulateLinklost_ReturnErr) //TEST_F是GoogleTest的一个框级? CheckLinkLost_SimulateLinklost_ReturnErr是测试用例名
{
CASE_INFO_G("CSI.TMSDK.SE.DFX.TEST.Simulate");
SE_API_DATA_DFX_INFO_S dfxInfo = {0};
MOCKER(se_api_ng_rut_query) //MOCKER用于打桩,即我们的代码而言,后面的函数内部会调用到se_api_ng_rut_query,它是CDK提供的外部接口,我们没法链接上跑代码,打桩就是告诉代码见到se_api_ng_rut_query时,输入输出是啥,而不去真正执行
.stubs()
.with(any(), outBoundP(&dfxInfo, sizeof(dfxInfo)))
.will(returnValue(HSI_OK));
uint32_t enable = 1;
(void)SE7_LINK_SimulateLinklost(chipId_, linkId_, enable); //运行相关测试的前提条件Linklost
uint32_t ret = SE7_LINK_CheckLinkLost(chipId_, linkId_, 0, 0); //执行相关函数获取返回值
ASSERT_EQ(HSI_ERR, ret); //判断返回值,根据返回值判断测试是否正确。
}
二、运行测试用例
跑测试脚本:
cd test/llt/hstm/build/
./build.sh -l #查看可测试的用例列表,找到自己目录相关
./build.sh -m se_se7 #就是跑se7目录下的测试用例
./build.sh -m se_se7 *CheckLinkLost_SimulateLinklost_ReturnErr* #指定只运行CheckLinkLost_SimulateLinklost_ReturnErr这个测试用例