C++开发测试工具gmock使用详解——对抽象接口类进行gmock打桩并测试

本文主要是介绍C++开发测试工具gmock使用详解——对抽象接口类进行gmock打桩并测试,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

目录

    • 一、简单Demo
      • 1.1 共同约定
      • 1.2 小A任务
      • 1.3 我的任务
      • 1.4 常规运行
      • 1.5 测试输出
    • 二、基本流程
    • 三、MOCK_METHOD
    • 四、EXPECT_CALL
      • 4.1 基本语法
      • 4.2 _
      • 4.3 Cardinalities
      • 4.4 默认操作

第一篇文章(C++开发测试工具gmock的安装与使用超详解)和第二篇文章(C++开发测试工具gmock的使用详解)分别对gmock的安装、配置与使用案例进行了详细介绍,本文将对抽象接口类进行gmock打桩测试进行介绍,结合上文一起食用效果更佳哦~

一、简单Demo

首先来编写一个简单Demo,编写代码之前,首先需要将解决方案的平台与目标程序一致,这里选择的是x64,然后版本也要与目标程序一致,这里选择的是release版本。
在这里插入图片描述
假设我和小A共同开发一款产品,他负责对不同的图形计算面积,而我负责根据面积进行相关处理(简单乘以一个固定系数)。

1.1 共同约定

首先我们需要共同约定定义一个接口(公共成员变量Area):

class Shape {public:virtual double Area(void) = 0;
};

1.2 小A任务

小A的任务是根据不同的类型进行计算,例如计算矩形面积,代码如下:

//小A任务
class Rectangle : public Shape {
private:double width, height;public://构造函数Rectangle(double w, double h){width = w;height = h;}// 析构函数~Rectangle() { } // Do nothing//计算矩形面积double Area(void){return width * height;}};

1.3 我的任务

//我的任务是根据小A计算得到的结果面积进行相关处理(简单乘以一个固定系数,这里以2为例):

double GetPrice(class Shape &s)
{return s.Area() * 2;
}

1.4 常规运行

正常情况下,在main函数中应该调用小A编写的代码获取结果面积,然后再调用我的代码对面积进行相关处理,将处理后的结果打印输出:

int main(void)
{Rectangle rect(3, 4);std::cout << GetPrice(rect) << std::endl;return 0;
}

1.5 测试输出

但是,由于小A需要完成三角形、梯形、圆等诸多形状面积开发,工作量很大,因此为了不阻塞自己的开发,可以通过gmock来模拟Shape接口返回的数据,代码如下:

//模拟Shape接口返回的数据
class ShapeMock : public Shape {public:MOCK_METHOD0(Area, double());};TEST(ShapeTest, Rectangle)
{ShapeMock rect;		//Mock objectEXPECT_CALL(rect, Area()).WillRepeatedly(testing::Return(12));//每次调用都会返回12EXPECT_EQ(24, GetPrice(rect));}int main(int argc, char **argv)
{testing::InitGoogleTest(&argc, argv);return RUN_ALL_TESTS();
}

运行结果如下:
在这里插入图片描述

二、基本流程

通过上述流程,可以总结出在利用gmock进行Mock打桩时的一般步骤如下:

  1. 定义一个Mock类,在类中需要通过类似MOCK_METHOD的宏定义接口;
  2. TEST时新建一个Mock对象(Mock Object),用来模拟接口定义的行为;
  3. 通过gmock提供的接口(EXPECT_CALL)设置Mock对象需要执行的动作;
  4. 调用Mock对象返回的数据,检查与预期是否相符(利用断言宏EXPECT或ASSERT

三、MOCK_METHOD

四、EXPECT_CALL

成功使用Mock对象的关键是对其设置正确的期望expectations。如果将期望值设置得太严格,则测试将因不相关的更改而失败。如果将它们设置得太松,错误可能会溜走。希望恰到好处地做到这一点,以便测试可以准确地捕获您希望它捕获的错误类型。gMock提供了必要的方法来"恰到好处"地做到这一点。
在 gMock 中,我们使用宏来设置对Mock方法的期望。一般语法为:EXPECT_CALL()。

4.1 基本语法

EXPECT_CALL用于定义成员函数的期望行为,常用语法如下:

EXPECT_CALL(mock_object, method_name[, matchers]).Times(cardinality).InSequence(sequences).After(expectations).WillOnce(action).WillRepeatedly(action).RetiresOnSaturation();

宏的参数如下:

  • mock_object:创建的mock对象;
  • method_name:方法名称(要模拟的小A的行为);
  • matchers:参数信息;

下面介绍一下.后各个语句的功能:

  • .Times(cardinality):定义调用的预期次数;
  • .InSequence(sequences):定义这个方法被执行顺序(优先级);
  • .After(expectations):定义调用应在在一个或多个expectations之后进行;
  • .WillOnce(action):定义一次调用时所产生的行为;
  • .WillRepeatedly(action):定义多次调用时所产生的行为;
  • .RetiresOnSaturation():定义调用达到期望后不再调用;

其他功能建议参考google mock官方文档。

来看以下代码实例:

	EXPECT_CALL(rect, Area()).Times(testing::AtLeast(5)).WillOnce(testing::Return(100)).WillOnce(testing::Return(150)).WillRepeatedly(testing::Return(200));

这个期望行为的含义是:

  • 调用rect的Area()方法
  • 这个方法会至少调用5次
  • 第一次被调用时返回100
  • 第二次被调用时返回150
  • 从第三次被调用开始每次都返回200

【注】:调用几次expectation就需要调用几次下面的执行语句,这个是一一对应的:

EXPECT_CALL(rect, Area()).WillOnce(testing::Return(1)).WillOnce(testing::Return(2)).WillOnce(testing::Return(3));std::cout << GetPrice(rect) << std::endl;
std::cout << GetPrice(rect) << std::endl;
std::cout << GetPrice(rect) << std::endl;

在上面调用了三次期望,因此在下面也应该执行三次获取输出。执行次数利用语句Times指定。

4.2 _

如果你对参数的值不感兴趣,请改成参数_,这意味着“任何事情都会发生”:

using ::testing::_;
...
// Expects that the turtle jumps to somewhere on the x=50 line.
EXPECT_CALL(turtle, GoTo(50, _));

_我们称之为matchers(匹配器)。匹配器就像一个判定,可以测试参数是否是我们所期望的。您可以在任何需要函数参数的位置使用匹配器。
上述例子中,50也是匹配器,它与Eq(50)相同。关于匹配器的详细内容,可以参考matchers

如果你不关心任何参数,而不是为每个参数指定,则可以省略参数列表:

// Expects the turtle to jump somewhere.
EXPECT_CALL(turtle, GoTo);

4.3 Cardinalities

.Times(cardinality)
指定模拟函数调用的预期次数。
更重要的是,基数可以是"模糊的",就像匹配器一样。这允许用户准确地表达测试的意图。
参数cardinality表示预期调用的数量,可以是以下参数之一,全部在命名空间::testing中定义:

cardinality含义
AnyNumber()函数可以调用任意次数
AtLeast(n)函数调用至少需要n次
AtMost(n)函数调用最多需要n次
Between(m,n)函数调用预期在m和n之间(包括m和n)
Exactly(n)或n函数调用正好需要n次,如果n部位0,则调用不应发生

如果省略该语句,则googletest将按如下方式推断Times:

  • 如果既未指定WillOnec,也未指定WillRepeatedly,则推断的基数为Times(1);
  • 如果WillOnce有n个子句,WillRepeatedly没有子句,则推断的基数为Times(n);
  • 如果WillOnce有n个子句,WillRepeatedly有一个子句,则推断的基数为Times(AtLeast(n));

4.4 默认操作

代码示例:

using ::testing::Return;
...
EXPECT_CALL(turtle, GetY()).Times(4).WillOnce(Return(100));

这段代码表示显然有望被调用四次。但是,如果您认为每次都会返回100,请三思而后行!请记住,每次调用函数时都会使用一个子句,之后将执行默认操作。因此,正确的答案是,第一次将返回 100,但从第二次开始返回 0,因为返回 0 是函数的默认操作


ok,以上便是本文的全部内容了,如果对你有所帮助,记得点个赞哟~

参考:

  1. https://gohalo.me/post/cpp-gmock-usage.html

这篇关于C++开发测试工具gmock使用详解——对抽象接口类进行gmock打桩并测试的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/293284

相关文章

Java中流式并行操作parallelStream的原理和使用方法

《Java中流式并行操作parallelStream的原理和使用方法》本文详细介绍了Java中的并行流(parallelStream)的原理、正确使用方法以及在实际业务中的应用案例,并指出在使用并行流... 目录Java中流式并行操作parallelStream0. 问题的产生1. 什么是parallelS

MySQL数据库双机热备的配置方法详解

《MySQL数据库双机热备的配置方法详解》在企业级应用中,数据库的高可用性和数据的安全性是至关重要的,MySQL作为最流行的开源关系型数据库管理系统之一,提供了多种方式来实现高可用性,其中双机热备(M... 目录1. 环境准备1.1 安装mysql1.2 配置MySQL1.2.1 主服务器配置1.2.2 从

C++中unordered_set哈希集合的实现

《C++中unordered_set哈希集合的实现》std::unordered_set是C++标准库中的无序关联容器,基于哈希表实现,具有元素唯一性和无序性特点,本文就来详细的介绍一下unorder... 目录一、概述二、头文件与命名空间三、常用方法与示例1. 构造与析构2. 迭代器与遍历3. 容量相关4

Linux join命令的使用及说明

《Linuxjoin命令的使用及说明》`join`命令用于在Linux中按字段将两个文件进行连接,类似于SQL的JOIN,它需要两个文件按用于匹配的字段排序,并且第一个文件的换行符必须是LF,`jo... 目录一. 基本语法二. 数据准备三. 指定文件的连接key四.-a输出指定文件的所有行五.-o指定输出

Linux jq命令的使用解读

《Linuxjq命令的使用解读》jq是一个强大的命令行工具,用于处理JSON数据,它可以用来查看、过滤、修改、格式化JSON数据,通过使用各种选项和过滤器,可以实现复杂的JSON处理任务... 目录一. 简介二. 选项2.1.2.2-c2.3-r2.4-R三. 字段提取3.1 普通字段3.2 数组字段四.

C++中悬垂引用(Dangling Reference) 的实现

《C++中悬垂引用(DanglingReference)的实现》C++中的悬垂引用指引用绑定的对象被销毁后引用仍存在的情况,会导致访问无效内存,下面就来详细的介绍一下产生的原因以及如何避免,感兴趣... 目录悬垂引用的产生原因1. 引用绑定到局部变量,变量超出作用域后销毁2. 引用绑定到动态分配的对象,对象

Linux kill正在执行的后台任务 kill进程组使用详解

《Linuxkill正在执行的后台任务kill进程组使用详解》文章介绍了两个脚本的功能和区别,以及执行这些脚本时遇到的进程管理问题,通过查看进程树、使用`kill`命令和`lsof`命令,分析了子... 目录零. 用到的命令一. 待执行的脚本二. 执行含子进程的脚本,并kill2.1 进程查看2.2 遇到的

MyBatis常用XML语法详解

《MyBatis常用XML语法详解》文章介绍了MyBatis常用XML语法,包括结果映射、查询语句、插入语句、更新语句、删除语句、动态SQL标签以及ehcache.xml文件的使用,感兴趣的朋友跟随小... 目录1、定义结果映射2、查询语句3、插入语句4、更新语句5、删除语句6、动态 SQL 标签7、ehc

详解SpringBoot+Ehcache使用示例

《详解SpringBoot+Ehcache使用示例》本文介绍了SpringBoot中配置Ehcache、自定义get/set方式,并实际使用缓存的过程,文中通过示例代码介绍的非常详细,对大家的学习或者... 目录摘要概念内存与磁盘持久化存储:配置灵活性:编码示例引入依赖:配置ehcache.XML文件:配置

Java 虚拟线程的创建与使用深度解析

《Java虚拟线程的创建与使用深度解析》虚拟线程是Java19中以预览特性形式引入,Java21起正式发布的轻量级线程,本文给大家介绍Java虚拟线程的创建与使用,感兴趣的朋友一起看看吧... 目录一、虚拟线程简介1.1 什么是虚拟线程?1.2 为什么需要虚拟线程?二、虚拟线程与平台线程对比代码对比示例:三