关于gmock单元测试的使用小白知识,初使用gmock前推荐看

1. 什么是单元测试?

单元测试,维基百科给出定义:Unit Testing,又称为模块测试,是针对程序模块(软件设计的最小单元)进行正确性检验的测试工作。

 

2. 什么是模块?或者什么是最小单元?

通俗的说就是函数或者类的方法。

“单元”的定义,其实可以更加宽泛,在面向对象语言中,一个单元可以指一个方法,也可以是一个类。单元的选定更多的取决于我们测试的意图。

 

3. 为什么需要单元测试?

我们常说的单元测试,是开发者编写的一小段代码,用于检验被测代码的一个很小的、很明确的功能是否正确。简单来说,一个单元测试就是用于判断某个特定条件(或者场景)下某个特定函数的行为。

好处:开发人员可以通过这种方式,测试自己写的函数是否符合预期,在这个过程中,往往可以发现函数内部逻辑错误,带来优秀的代码治理和良好的异常处理以及完善错误报告。

 

4. 怎么写单元测试?或者怎么才算好的单元测试?

有明确的预期;

可重复运行;

没有副作用。

这里说到的副作用,即业务逻辑从外部依赖中解耦处理,比如说:时间、随机数、并发性、基础设置、持久化、网络等。这就需要我们对代码做更合理的划分,函数的职责更加的清晰。

但我们的往往有各种外部依赖,比如需要读写数据库,需要进行网络通信,需要操作设备等等,这就需要用到测试替身:Stub(桩)。桩的最大作用就是,不包含逻辑,返回固定数据。

 

5. 这里主要是记录一下C++的gmock的使用。

虽然gmock的入门看起来很简单,但是将其应用到具体的工程中,初学者又总会面临各种各样的问题,主要原因还是对于gmock是如何运作的存在误区,下面通过一个例子来看下,究竟gmock是如何替换某些函数方法的。

1)首先需要在C++工程中,设置附加包含目录和附加库目录为gtest和gmock的include和lib路径,附加依赖项中添加:gmock.lib和gtest.lib。需要注意的是,所使用的gmock.lib和gtest.lib与你所创建的C++工程,最好是一样的vs版本,即C++库版本一样,否则会出现一些意想不到的错误哦。

2)这里有一个test类,先假定它的作用就是没啥作用,就是给你看下:

正常来说,我们一般会这样写:

test.h:

class test{
public:
    test();
    ~test();
    int add(Dev* dev, int a);
};

假定Dev类是一个需要和设备进行交互的类,获取设备上的数据:

dev.h:

class Dev{
public:
    Dev();
    ~Dev();
    int getDevNum();
};

其中add方法的实现,假设就是一个加法运算,但是需要从某个设备中读取值上来与a进行计算:(假定我们这里的数值都必须是正数。)

test.cpp:

int test:add(Dev* dev, int a){
    if(NULL == dev){
        return -1;
    }
    if(a < 0){
        return -2;
    }

    int sum = dev->getDevNum();//假定没有设备,函数返回-3
    if(sum < 0){
        return -3;
    }

    sum += a;
    return sum;
}

我们需要对这个add方法做单元测试,但如果测试的环境没有设备,这个函数不就没法完全的测试了吗?

这个时候就需要借助gmock,来对getDevNum打桩,让getDevNum可以返回我们想让它返回的任何数

需要对getDevNum进行打桩,还需要对Dev类的实现做更改,这是因为gmock的原理实际是依赖于C++的多态机制实现的,所以只有虚函数才能被mock,而非虚函数则无法mock。这一点也就要求我们在实现我们的类和方法时,要预先设计好,把这些依赖外部环境的实现分离开(不要与业务逻辑部分混合在一起,封装到单独的函数方法中),不然代码写一半再去使用gmock做单元测试,就往往需要对现有代码结构做很大的更改,造成很大的时间和精力浪费。

Dev类更改如下:

class Dev{
public:
    Dev();
    virtual ~Dev();
    virtual int getDevNum();
};

将析构函数和getDevNum函数都设为虚函数,这样getDevNum函数就可以被mock了。

mock一下getDevNum:

首先我们在测试代码中定义一个MockDev类:

#include “gmock\gmock.h”
#include “dev.h”

class MockDev:public Dev{
public:
    MOCK_METHOD0(getDevNum, int());
};

这样我们就成功mock了getDevNum函数了。

MOCK_METHOD0:固定写法,数字0表示mock的函数没有参数,如果有2个参数,就是MOCK_METHOD2,对应的4个参数就是MOCK_METHOD4;

参数1:就是我们需要mock的函数方法名称了;

参数2:是我们mock的函数指针类型,格式为:返回值类型(参数1,参数2...)

如果我们的getDevNum有两个形参:int getDevNum(int a, string b);那么这里的参数2格式为:int(int a, string b)

 

接下来就是我们的单元测试代码:

测试代码include包含我们刚刚创建MockDev的头文件,以及被测试代码的cpp:

#include “gtest\gtest.h”
#include “gmock\gmock.h”
#include “MockDev.h”
#include “test.cpp”

using namespace testing;

TEST(TestSuiteTest, add){
    int ret;
    MockDev mdev;
    EXPECT_CALL(mdev, getDevNum()).WillRepeatedly(Return(1));

    test t;
    ret = t.add(3, NULL);
    EXPECT_EQ(4, -1);

    ret = t.add(-1, &mdev);
    EXPECT_EQ(-2, ret);

    ret = t.add(3, &mdev);
    EXPECT_EQ(4, ret);
}

这里EXPECT_CALL,就是说当调用mdev的getDevNum方法的时候,返回1.

当然还有很多丰富的返回,比如第一次调用返回1,第二次调用返回2等等,其他使用方法可以参考gmock的语法。

 

简单总结一下:

上面的例子虽然很简单,但是可以看出,

1)将需要mock的函数方法定义为虚函数;

2)我们需要在编写代码之初,将必要的接口分离,避免依赖外部环境的实现部分与业务逻辑部分混合。这是为了单元测试的时候,我们可以将依赖外部环境的函数实现进行mock。

 

其他:

实际工程中,我们一个类中可能不仅仅是共有的函数方法,可能还有一些私有的方法,对于这些私有的函数方法,应不应该测试呢?这个目前还没有统一的定论,需要看开发人员自己的意愿,这里给出私有方法测试的几种方法:

1)使用#define private public粗暴地将private变成public,需要将define放在#include头文件之前。如:

#define private public  

#include “myclass.h”

 

2)使用friend。这个会相对友好一点,但是却需要修改原有的代码

 

3)将private方法定义为protected,然后在测试代码中继承,自己定义测试的共有方法,再调用父类中的private方法:

class Num{

protected:

    int add(int a, int b);

};

//测试代码

class TestNum:public Num{

public:
    int testAdd(int a, int b);

};

int TestNum:testAdd(int a, int b){

    return add(a, b);

}

缺点也很明显,还是需要更改一下原代码。

本文为作者原创,如需转载,请在评论区征得作者同意,原创链接:https://blog.csdn.net/anranjingsi/article/details/106084223

原创文章 3 获赞 5 访问量 162

猜你喜欢

转载自blog.csdn.net/anranjingsi/article/details/106084223