小奥的学习笔记

  • Home
  • Learning & Working
    • Speech Enhancement Notes
    • Programming language
    • Computer & DL
    • MOOC
  • Life
    • Life Time
    • Thinking & Comprehension
    • Volunteer
    • Plan
    • Travel
  • Footprints
  • GuestBook
  • About
    • About Me
    • 个人履历
    • 隐私策略
  1. 首页
  2. Study-notes
  3. 正文

Google Test Mock学习笔记(上)

2021年2月16日 4623点热度 1人点赞 1条评论

什么是gmock?

一个mock对象实现了与真实对象相同的接口,但是它让您在运行时指定如何使用它以及应该做什么(包括它应该调用什么方法,以什么顺序调用方法,调用多少次,用什么参数,返回什么值等)。

当你使用gmock的时候:首先你要用一些简单的宏描述你希望mock的接口,这些宏将拓展为mock类的实现;其次要创建一些mock对象,并且使用直观的语法指定其期望或者行为;最后编写使用mock对象的代码,gMock将会在出现违反预期的时候立即捕获。

学习第一个gmock测试

写mock类

假设原来有一个类为

class Turtle {
  //...
  virtual ~Turtle() {}
  virtual void PenUp() = 0;
  virtual void PenDown() = 0;
  virtual void Forward(int distance) = 0;
  virtual void Turn(int degrees) = 0;
  virtual void GoTo(int x, int y) = 0;
  virtual int GetX() const = 0;
  virtual int GetY() const = 0;
};

那么如何用gmock写一个测试呢? 第一步是写一个Mock类,下面是要做的步骤:

  • 首先是从Turtle类里面派生一个MockTurtle类。
  • Take a virtual function of Turtle (while it's possible to mock non-virtual methods using templates, it's much more involved).【这一步我大概认为是最好只能mock Turtle的 virtual函数】
  • 第三部是在这个子类里面写MOCK_METHOD();
  • 然后就是在MOCK_METHOD的括号里面写参数,这个参数实际上就是将父类的成员函数进行分解为【返回值类型】、【函数名】、【参数列表】、【const/override】。

如下是上面这个类的Mock类:

#include "gmock/gmock.h"

class TurtleMock : public Turtle {
public:
//...
    MOCK_METHOD(void, PenUp, (), (override));
    MOCK_METHOD(void, PenDown,(), (override));
    MOCK_METHOD(void, Forward, (int distance), (override));
    MOCK_METHOD(void, Turn, (int degrees), (override));
    MOCK_METHOD(void, GoTo, (int x, int y), (override));
    MOCK_METHOD(int, GetX, (), (const, override));
    MOCK_METHOD(int, GetY, (), (const, override));

}

mock类放在哪里

当我们写完了这个类,我们应该将其放在哪里呢?

一些人喜欢将其放置在xxx_test.cpp文件中。如果被mock的接口一直是一个人或者一个团队在维护这是ok的,但是如果这个接口的维护之改变了,这个测试就有可能失败了。

所以,一般规定统一个原则:如果你需要mock一个类(假定为Foo)并且它属于其他人拥有,那么将mock class定义在Foo的package(更好的来说,定义在一个测试的子package里面,这样更清楚),将其放置在.h文件和一个cc_library。然后每一个人都可以在他们的测试中参考他们。如果这个类改变了,那么仅仅有一个MockFoo的copy会改变,而且仅有哪些依赖于改变内容的测试需要改变。

另一种实现该目的的方法是,你可以在Foo的顶层引入一个thin layer名为FooAdaptor,并将Foo置于FooAdaptor之下。由于你拥有FooAdaptor,你可以更轻松的处理好Foo中的修改。虽然这种方法在最初的时候需要做耕作的工作,但是这一做可以使你的代码更易于编写和更具有可读性。

使用mock类进行测试

使用的方法很简单,如下:

  1. 从testing的命名空间引入gMock的名字;
  2. 创建一些Mock对象;
  3. 指定你的期望(调用多少次,有什么参数,应该做什么等)
  4. 编写使用这些mocks的代码,可以使用gtest断言检查结果,如果mock方法的调用出现错误或者不符合预期,你会立即收到报错;
  5. 当mock被销毁的时候,gmock会自动检查我们所有的期望是否得到满足。

实现代码如下:

#include "path/to/mock-turtle.h"
#include "gmock/gmock.h"




#include "gtest/gtest.h"

using ::testing::AtLeast; //Step 1

TEST(PainterTest, CanDrawSometing) {
    MockTurtle turtle; //Step 2
    EXPECT_CALL(turtle, PenDown()).Times(AtLeast(1)); //Step 3

    Painter painter(&turtle); //Step 4
    EXPECT_TRUE(painter.DrawCircle(0, 0, 10)); //Step 5
}

提示:
1. 如果你从Emacs缓冲区运行测试,则可以在行号上按Enter键跳转到失败的期望值;
2.如果你的mock对象从未删除,则最后的验证不会发生。因此,在堆上分配mocks的时候,应该在测试中打开堆检查器。如果你已经使用gtest_main库,则会自动获取该信息。
3.一定要在mock函数被调用之前设置期望值,否则行为将不确定,并且一定不能将EXPECT_CALL()和调用交错使用。

EXPECT_CALL()该如何写

能够使用mock来进行测试的一个关键点是设置正确的期望值。如果你设置的期望值太过于严苛,你的测试将会在存在不相关改变时失败;如果你设置的期望值太过于轻松,可能会导致一些bug不能被发现。您想正确地做它,以便您的测试可以准确地捕获您打算捕获的错误类型。 gMock为您“做到正确”提供了必要的手段。 通常的语法如下:

EXPECT_CALL(mock_object, method(matcher1, matcher2, ...))
    .With(multi_argument_matcher)//多个参数的匹配方式指定
    .Times(cardinality)//重复多少次
    .InSequence(sequences)//定义这个方法被执行顺序(优先级)
    .After(expectations)//指定某个方法只能在另一个方法之后执行
    .WillOnce(action)//定义一次调用时所产生的行为,比如定义该方法返回怎么样的值等等
    .WillRepeatedly(action)//缺省/重复行为
    .RetiresOnSaturation();//用于保证期待调用不会被相同的函数的期待所覆盖

宏定义里面包括两个参数,分别是mock object和要调用的方法(包括参数)。如果方法没有重载(overloaded),那么可以在没有匹配器的情况下调用该宏:

EXPECT_CALL(mock_object, non-overloaded-method)
    .Times(cardinality)
    .WillOnce(action)
    .WillRepeatedly(action);

这种语法允许测试编写者指定“使用任何参数调用”,而无需明确指定参数的数量或类型。为避免意外的歧义,此语法只能用于未重载的方法。 我们注意到,在宏定义后面还跟着三个子句,下面我们来讨论下这些子句的语法。我们举个例子,如下:

using ::testing::Return;
...
EXPECT_CALL(turtle, GetX())
    .Times(5)
    .WillOnce(Return(100))
    .WillOnce(Return(150))
    .WillRepeatedly(Return(200));

我们可以明白这句话的意思,我们希望调用turtle中的GetX()函数,并且完成以下动作:①调用5次;②第一次返回值为100;③第二次返回值为150;④此后每一次返回值都为200。

Matchers:我们期望怎样的参数

当一个mock函数需要参数的时候,我们需要指定我们期望的参数,例如

// Expects the turtle to move forward by 100 units.
EXPECT_CALL(turtle, Forward(100));

一般来说,我们可能不希望参数过于具体,即我们可能只对多个输入参数中只关心一个或几个,而对其它的要求遍历或者不关心。这个时候如果我们对其它参数也指定了具体的参数,可能会导致一些存在的bug我们无法测试到。因此,如果我们对这部分的参数不感兴趣或者需要遍历,可以使用_来替代具体参数值。例如:

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

但事实上,上面的100和50也都是matchers,它们实际上可以视同为Eq(100)和Eq(50)。 下面是一个内置的matchers,例如

using ::testing::Ge;
...
// Expects the turtle moves forward by at least 100.
EXPECT_CALL(turtle, Forward(Ge(100)));

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

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

这适用于所有非重载方法; 如果方法被重载,则需要通过指定参数数量以及可能的参数类型来帮助gMock解决预期的重载。

Cardinalities:将被调用多少次?

.Times(cardinality)

cardinality除了是具体的数字,也可以是模糊的,这非常有助于我们进行测试。如何模糊的使用呢,有以下几种方式:

AnyNumber() The function can be called any number of times.
AtLeast(n) The call is expected at least n times.
AtMost(n) The call is expected at most n times.
Between(m, n) The call is expected between m and n (inclusive) times.
Exactly(n) or n The call is expected exactly n times. In particular, the call should never happen when n is 0.

一个特殊情况是当使用.Times(0)的时候,就意味着根本不应该使用给定的参数调用该函数,,并且无论何时(错误地)调用该函数,gMock都将报告googletest失败。 .Times()可以被忽略,如果忽略之,gMock将为您推断基数。 规则很容易记住:

  • 如果不存在WillOnce()或者WillRepeatedly(),则是Times(1);
  • 如果有n个WillOnce()且没有WillRepeatedly(),且n>=1,则是Times(n);
  • 如果有n个WillOnce()且有一个WillRepeadtedly(),并且n>=0,则是Times(AtLeast(n))。

Actions:实际做了什么

还记得模拟对象实际上没有有效的实现吗? 作为用户,我们必须告诉它在调用方法时该怎么做。

首先,如果mock函数的返回类型是内置类型或指针,则该函数具有默认操作(void函数将仅返回,布尔函数将返回false,其他函数将返回0)。 此外,在C++11及更高版本中,其返回类型可默认构造的mock函数(即具有默认构造函数)具有返回默认构造值的默认操作。 如果您什么也不说,将使用此行为。

其次,如果mock函数没有默认操作或者默认操作不适合,则可以使用一系列的WillOnce()子句及可选的WillRepeatedly()指定每次期望匹配时要执行的操作,例如

using ::testing::Return;
...
EXPECT_CALL(turtle, GetX())
     .WillOnce(Return(100))
     .WillOnce(Return(200))
     .WillOnce(Return(300));

这样的话,GetX()将要执行3次,分别返回100、200、300。

注意:
即使该动作可以执行多次,EXPECT_CALL()语句也仅对动作子句进行一次评估。 因此,您必须注意副作用。 以下内容可能无法满足您的要求:
using ::testing::Return;
...
int n = 100;
EXPECT_CALL(turtle, GetX())
.Times(4)
.WillRepeatedly(Return(n++));
我们可能期望它返回100、101、102、103,但实际上每一次返回都是100。我们可以这样理解,虽然重复进行4次,但是每一次都是独立的,故每一次返回都是100。

Using Multiple Expectations

到目前为止,我们一直使用的是只有一个期望的示例。但是在现实中,我们可能对多个mock对象的多个mock方法指定期望。

默认情况下,当调用mock方法时,gmock将按照与定义相反的顺序搜索期望值,并在找到与参数匹配的有效期望值的时候停止。如果匹配的期望值不能再进行更多的调用,就会收到一个失败的警告。例如:

using ::testing::_;
...
EXPECT_CALL(turtle, Forward(_));  // #1
EXPECT_CALL(turtle, Forward(10))  // #2
    .Times(2);

如果连续三次调用Forward(10),则第三次会报错,因为最后一个匹配的期望值(#2)已饱和。 但是,如果将第三个Forward(10)调用替换为Forward(20),则可以,因为现在#1将是匹配的期望值。

Ordered vs Unordered Calls

默认情况下,即使未满足先前的期望,期望也可以匹配调用。 换句话说,调用不必按期望的指定顺序进行。 有时,您可能希望所有预期的调用都按照严格的顺序进行。 在gMock中说这很容易,如下:

using ::testing::InSequence;
...
TEST(FooTest, DrawsLineSegment) {
  ...
  {
    InSequence seq;

    EXPECT_CALL(turtle, PenDown());
    EXPECT_CALL(turtle, Forward(100));
    EXPECT_CALL(turtle, PenUp());
  }
  Foo();
}

通过创建InSequence类型的对象,将其范围内的所有期望放入一个序列中,并且必须顺序发生。 由于我们只是依靠该对象的构造函数和析构函数来完成实际工作,因此它的名称确实无关紧要。

在此示例中,我们测试Foo()按编写顺序调用了三个预期函数。 如果调用混乱,将会报错。

参考文献

https://github.com/google/googletest/blob/master/docs/gmock_cheat_sheet.md#CardinalityList

https://github.com/google/googletest/blob/master/docs/gmock_for_dummies.md

https://github.com/google/googletest/blob/master/docs/gmock_cook_book.md

 

本作品采用 知识共享署名 4.0 国际许可协议 进行许可
标签: gmock google test
最后更新:2021年8月10日

davidcheung

这个人很懒,什么都没留下

打赏 点赞
< 上一篇

文章评论

razz evil exclaim smile redface biggrin eek confused idea lol mad twisted rolleyes wink cool arrow neutral cry mrgreen drooling persevering
取消回复

搜索
欢迎关注我的个人公众号
最新 热点 随机
最新 热点 随机
DEEPFILTERNET:一种基于深度滤波的全频带音频低复杂度语音增强框架 奥地利匈牙利九日游旅程 论文阅读之Study of the General Kalman Filter for Echo Cancellation 小奥看房之鸿荣源珈誉府 杭州往返旅途及西溪喜来登和万怡的体验报告 2022年的第一篇碎碎念
奥地利匈牙利九日游旅程论文阅读之Study of the General Kalman Filter for Echo CancellationDEEPFILTERNET:一种基于深度滤波的全频带音频低复杂度语音增强框架
[Leetcode]merge sorted array 新青年报[New Youth]第八期(高考毕业特刊)发布! Leetcode 14:最长公共前缀 把WordPress放在根目录而地址显示子目录的方法 Deep Learning in Neural Networks: An Overview(自己翻译版) 马云卸任CEO演讲:明天起生活将是我的工作
标签聚合
学习 鸟哥的linux私房菜 Python leetcode 高中 算法 linux Java python学习 生活
最近评论
davidcheung 发布于 5 个月前(02月09日) The problem has been fixed. May I ask if you can s...
tk88 发布于 5 个月前(02月07日) Hmm is anyone else having problems with the pictur...
cuicui 发布于 9 个月前(10月20日) :wink:
niming 发布于 10 个月前(09月19日) 同级校友,能刷到太巧了
davidcheung 发布于 2 年前(08月16日) 我得找一下我之前整理的word文档看一下,如果找到了我就更新一下这篇文章。
Nolan 发布于 2 年前(07月25日) 您的笔记非常有帮助。贴图不显示了,可以更新一下吗?
davidcheung 发布于 3 年前(06月19日) 到没有看webrtc的代码。现在主要在看我们公司的代码了。。。只是偶尔看一看webrtc的东西。。。
aobai 发布于 3 年前(03月13日) gain_change_hangover_ 应该是每三个block 只能够调整一次,这样保证每帧...
匿名 发布于 5 年前(12月30日) 烫
小奥 发布于 5 年前(12月12日) webRTC里面的NS本身我记得就是在C++里面呀

COPYRIGHT © 2025 小奥的学习笔记. ALL RIGHTS RESERVED.

Theme Kratos Made By Seaton Jiang

陕ICP备19003234号-1

鲁公网安备37120202000100号