初识Gmock是之前分析GTest源码时,它的源码和GTest源码在同一个代码仓库中(https://github.com/google/googletest)。本文我将以目前最新的Gmock1.7版本为范例,分析其实现原理。(转载请指明出于breaksoftware的csdn博客)

Gmock是google开发的一套辅助测试的工具,它往往和GTest结合在一起使用。在实际工作中,一个人不可能完成整条线的开发工作。于是我们会在约定接口的前提下,各自完成各自的模块。自己的模块开发完之后,我们需要自测。但是这个时候别人的模块可能还没完成,那么我们就需要模拟约定的接口进行自测。Gmock就是一个强大的模拟接口的工具。

使用方法

首先我们讲解一下其主要的使用方法。目前网络上有一篇写的不错的使用说明《转一篇小亮同学的google mock分享》,如果大家想了解其详细的使用方法,可以参阅这篇文章。如果只是想简单的了解其使用并理解其实现原理,可以先参阅本文。

我们假设一个支付场景逻辑开发业务。我们开发复杂的业务模块,而团队其他成员开发用户行为模块。他们和我们约定了如下接口

class User {
public:User() {};~User() {};
public:// 登录virtual bool Login(const std::string& username, const std::string& password) = 0;// 支付virtual bool Pay(int money) = 0;// 是否登录virtual bool Online() = 0;
};

我们的业务模块要让用户登录,并发起支付行为。于是我们的代码如下

class Biz {
public:void SetUser(User* user) {_user = user;}std::string pay(const std::string& username, const std::string& password, int money) {std::string ret;if (!_user) {ret = "pointer is null.";return ret;}if (!_user->Online()) {ret = "logout status.";// 尚未登录,要求登录if (!_user->Login(username, password)) {// 登录失败ret += "login error.";return ret;} else {// 登录成功ret += "login success.";}} else {// 已登录ret = "login.status";}if (!_user->Pay(money)) {ret += "pay error.";} else {ret += "pay success.";}return ret;}private:User* _user;
};

这段逻辑的口语描述就是:我们先看看用户登录了没,如果没有登录则要求用户登录。如果登录失败,则直接返回;如果登录成功,则执行支付行为。最后将流程的状态输出。

那我们如何使用Gmock辅助测试呢?

第一步我们需要Mock接口类

class TestUser : public User {
public:MOCK_METHOD2(Login, bool(const std::string&, const std::string&));MOCK_METHOD1(Pay, bool(int));MOCK_METHOD0(Online, bool());
};

可以发现其方法的声明非常有规律。MOCK_METHOD后跟一个数字,该数字表明需要mock的函数有几个参数(除去this)。像第5行,Online()方法没有入参,则使用的是MOCK_METHOD0。而又两个参数的Login使用的是MOCK_METHOD2。MOCK_METHOD系列宏的第一个参数是函数名,第二个参数是函数指针的类型。

第二步,我们就可以设计测试场景了。在设计场景之前,我们先看一些Gmock的方法

//   EXPECT_CALL(mock_object, Method(argument-matchers))
//       .With(multi-argument-matchers)
//       .Times(cardinality)
//       .InSequence(sequences)
//       .After(expectations)
//       .WillOnce(action)
//       .WillRepeatedly(action)
//       .RetiresOnSaturation();
//
// where all clauses are optional, and .InSequence()/.After()/
// .WillOnce() can appear any number of times.

我们可以使用

  • EXPECT_CALL声明一个调用期待,就是我们期待这个对象的这个方法按什么样的逻辑去执行。
  • mock_object是我们mock的对象,上例中就是TestUser的一个对象。
  • Method是mock对象中的mock方法,它的参数可以通过argument-matchers规则去匹配。
  • With是多个参数的匹配方式指定。
  • Times表示这个方法可以被执行多少次。如果超过这个次数,则按默认值返回了。
  • InSequence用于指定函数执行的顺序。它是通过同一序列中声明期待的顺序确定的。
  • After方法用于指定某个方法只能在另一个方法之后执行。
  • WillOnce表示执行一次方法时,将执行其参数action的方法。一般我们使用Return方法,用于指定一次调用的输出。
  • WillRepeatedly表示一直调用一个方法时,将执行其参数action的方法。需要注意下它和WillOnce的区别,WillOnce是一次,WillRepeatedly是一直。
  • RetiresOnSaturation用于保证期待调用不会被相同的函数的期待所覆盖。

我们就可以基于以上的逻辑组织测试逻辑。

先举一个例子,我们要求Online在第一调用时返回true,之后都返回false。Login一直返回false。Pay一直返回true。也就是说用户第一次支付前处于在线状态,并可以支付成功。而第二次将因为不处于在线状态,要触发登录行为,而登录行为将失败。我们看下这个逻辑该怎么写

    {TestUser test_user;EXPECT_CALL(test_user, Online()).WillOnce(testing::Return(true));EXPECT_CALL(test_user, Login(_,_)).WillRepeatedly(testing::Return(false));EXPECT_CALL(test_user, Pay(_)).WillRepeatedly(testing::Return(true));Biz biz;biz.SetUser(&test_user);std::string admin_ret = biz.pay("user", "", 1);admin_ret = biz.pay("user", "", 1);}

第4行的意思是Online在调用一次后返回true,之后的调用返回默认的false。第5行意思是Login操作一直返回false,其中Login的参数是两个下划线(_),它是通配符,就是对任何输入参数都按之后要求执行。第6行意思是Pay操作总是返回true。那么我们在第10行和第11行分别得到如下输出

login status.pay success.
logout status.login error.

可以见得输出符合我们的预期。

我们再看一种场景,这个场景我们使用了函数参数的过滤。比如我们不允许admin的用户通过我们方法登录并支付,则可以这么写

    {TestUser test_user;EXPECT_CALL(test_user, Online()).WillOnce(testing::Return(false));EXPECT_CALL(test_user, Login("admin",_)).WillRepeatedly(testing::Return(false));Biz biz;biz.SetUser(&test_user);std::string admin_ret = biz.pay("admin", "", 1);}

第3行表示,如果Login的第一个参数是admin,则总是返回false。于是07行返回是

logout status.login error.

那么如果不是admin的用户登录,则返回成功,这个案例要怎么写呢?

    {TestUser test_user;EXPECT_CALL(test_user, Online()).WillOnce(testing::Return(false));EXPECT_CALL(test_user, Login(StrNe("admin"),_)).WillRepeatedly(testing::Return(true));EXPECT_CALL(test_user, Pay(_)).WillRepeatedly(testing::Return(true));Biz biz;biz.SetUser(&test_user);std::string user_ret = biz.pay("user", "", 1);}

03行使用了StrNe的比较函数,即Login的第一个参数不等于admin时,总是返回true。08行的输出是

logout status.login success.pay success.

我们再看一个例子,我们要求非admin用户登录成功后,只能成功支付2次,之后的支付都失败。这个案例可以这么写

    {TestUser test_user;EXPECT_CALL(test_user, Online()).WillOnce(testing::Return(false));EXPECT_CALL(test_user, Login(StrNe("admin"),_)).WillRepeatedly(testing::Return(true));EXPECT_CALL(test_user, Pay(_)).Times(5).WillOnce(testing::Return(true)).WillOnce(testing::Return(true)).WillRepeatedly(testing::Return(false));Biz biz;biz.SetUser(&test_user);std::string user_ret = biz.pay("user", "", 1);user_ret = biz.pay("user", "", 1);user_ret = biz.pay("user", "", 1);}

第4行我们使用Times函数,它的参数5表示该函数期待被调用5次,从第6次的调用开始,返回默认值。Times函数后面跟着两个WillOnce,其行为都是返回true。这个可以解读为第一次和第二次调用Pay方法时,返回成功。最后的WillRepeatedly表示之后的对Pay的调用都返回false。我们看下执行的结果

logout status.login success.pay success.
logout status.login success.pay success.
logout status.login success.pay error.

从结果上看,前两次都支付成功了,而第三次失败。符合我们的期待。

下一节,我们将阅读Gmock的源码,分析其实现脉络和原理。

Google Mock(Gmock)简单使用和源码分析——简单使用相关推荐

  1. Google Mock(Gmock)简单使用和源码分析——源码分析

    源码分析 通过<Google Mock(Gmock)简单使用和源码分析--简单使用>中的例子,我们发现被mock的相关方法在mock类中已经被重新实现了,否则它们也不会按照我们的期待的行为 ...

  2. Google Test(GTest)使用方法和源码解析——Listener技术分析和应用

    在<Google Test(GTest)使用方法和源码解析--结果统计机制分析>文中,我么分析了GTest如何对测试结果进行统计的.本文我们将解析其结果输出所使用到的Listener机制. ...

  3. Google Test(GTest)使用方法和源码解析——结果统计机制分析

    在分析源码之前,我们先看一个例子.以<Google Test(GTest)使用方法和源码解析--概况 >一文中最后一个实例代码为基准,修改最后一个"局部测试"结果为错误 ...

  4. Google Test(GTest)使用方法和源码解析——自动调度机制分析

    在<Google Test(GTest)使用方法和源码解析--概况 >一文中,我们简单介绍了下GTest的使用和特性.从这篇博文开始,我们将深入代码,研究这些特性的实现.(转载请指明出于b ...

  5. Google Test(GTest)使用方法和源码解析——模板类测试技术分析和应用

    写C++难免会遇到模板问题,如果要针对一个模板类进行测试,似乎之前博文中介绍的方式只能傻乎乎的一个一个特化类型后再进行测试.其实GTest提供了两种测试模板类的方法,本文我们将介绍方法的使用,并分析其 ...

  6. Google Test(GTest)使用方法和源码解析——参数自动填充技术分析和应用

    在我们设计测试用例时,我们需要考虑很多场景.每个场景都可能要细致地考虑到到各个参数的选择.比如我们希望使用函数IsPrime检测10000以内字的数字,难道我们要写一万行代码么?(转载请指明出于bre ...

  7. Google Test(GTest)使用方法和源码解析——私有属性代码测试技术分析

    有些时候,我们不仅要测试类暴露出来的公有方法,还要测试其受保护的或者私有方法.GTest测试框架提供了一种方法,让我们可以测试类的私有方法.但是这是一种侵入式的,会破坏原来代码的结构,所以我觉得还是谨 ...

  8. Google Test(GTest)使用方法和源码解析——预处理技术分析和应用

    预处理 在<Google Test(GTest)使用方法和源码解析--概况>最后一部分,我们介绍了GTest的预处理特性.现在我们就详细介绍该特性的使用和相关源码.(转载请指明出于brea ...

  9. Google Test(GTest)使用方法和源码解析——断言的使用方法和解析

    在之前博文的基础上,我们将介绍部分断言的使用,同时穿插一些源码.(转载请指明出于breaksoftware的csdn博客) 断言(Assertions) 断言是GTest局部测试中最简单的使用方法,我 ...

最新文章

  1. 算法提高课-图论-单源最短路的建图方式-AcWing 1127. 香甜的黄油:spfa最短路
  2. iis 防火墙防止恶意ip攻击
  3. Swift傻傻分不清楚系列(七)控制流
  4. c语言动画原理,动画详解十大经典排序算法(C语言版)
  5. java ee最新_从此再无 JavaEE,现在叫 JakartaEE
  6. Nginx 笔记与总结(4)配置 server 访问日志
  7. tomcat:学习:一:(第1次操作:成功)手机下载 tomcat 服务器里面的文件+tomcat 安装配置使用
  8. 用java把word转pdf
  9. python图标变成了白色_桌面图标上有个白色文件图标怎么去掉?解决桌面图标白色方块挡住...
  10. mui用ajax上拉加载更多,mui 中的一个上拉加载及下拉刷新的方法 ,但是我使用ajax拿不到url中的数据,是为什么?...
  11. 快狗打车,打车新平台,这个猛!
  12. 书单收藏 | 17本中信版畅销书从科普、商业到技术带你轻松理解区块链
  13. BP神经网络实例及代码分析(python+tensorflow实现)
  14. 跨域请求的API接口
  15. ERROR Error: command failed: pnpm install --reporter silent --shamefully-hoist 错误解决办法
  16. 原创 | ArcGIS中受支持的地图投影
  17. ROS古月资料学习之ROS简介
  18. 【算法训练营】 - ⑩ 并查集与图
  19. Kali Linux系统apt命令详解
  20. qsv格式转换mp4手机安卓版 转换方法,无需工具

热门文章

  1. 机器学习中的算法(4.3):SVM----针对线性不可分问题理解
  2. BGR图像与HSV图像互相转换(opencv)
  3. 4. 编程规范和编程安全指南--go语言
  4. sklearn 朴素贝叶斯分类示例
  5. 【yolo目标检测】(1) yolov3,网络结构Darknet53,特征提取
  6. list mybatis 接收 类型_基于mybatis拦截器实现的一款简易影子表自动切换插件
  7. 二、多并发实现接口压力测试
  8. python七大神级插件_IntelliJ IDEA 15款超级牛逼插件推荐(自用,超级牛逼)
  9. 剑指offer:面试题32 - II. 从上到下打印二叉树 II
  10. 利用python中的gzip模块压缩和解压数据流和文件