gmock教程(gmclock)

gmock教程(gmclock)Gmock 是 C 中的一个接口测试框架 一般来说和 Google Test 搭配使用 但 Google Test 也可以和其他 Mock 框架一起使用 本部分是 Google Mock 基础常用的用法 如需要特殊用法 请查阅 Google Mock 官方文档 依次执行下面命令即可 官方文档 https google github io googletest Fake Mock Stub Fake 对象有具体的实现 但采取一些捷径 比如用内存替代真实的数据库读取 Stub 对象没有具体的实现



Gmock是C++中的一个接口测试框架,一般来说和Google Test搭配使用,但Google Test也可以和其他Mock框架一起使用。 本部分是Google Mock基础常用的用法,如需要特殊用法,请查阅Google Mock官方文档。

依次执行下面命令即可:


官方文档:https://google.github.io/googletest/

  1. Fake、Mock、Stub

    • Fake对象有具体的实现,但采取一些捷径,比如用内存替代真实的数据库读取
    • Stub对象没有具体的实现,只是返回提前准备好的数据
    • Mock对象和Stub类似,只是在测试中需要调用时,针对某种输入指定期望的行为,Mock和Stub的区别是,Mock除了返回数据还可以指定期望以验证行为。
  2. 简单示例

    Tutle类:

    
    

    MockTurtle类:

    
    

    创建Mock类的步骤:

    1. MockTutle继承Tutle

    2. 找到Tutle的一个虚函数

    3. 在public的部分,写一个MOCK_METHOD()

    4. 将虚函数的函数签名复制进MOCK_METHOD()中,加两个逗号:

      一个在返回类型和函数名之间,另一个在函数名和参数列表之间

      例如:void PenDown() 有三部分:void、PenDown、和(),这三部分就是MOCK_METHOD的前三个参数

    5. 如果要模拟const方法,添加一个包含const的第四个参数,必须到括号

    6. 建议添加override关键字。所以对于const方法,第四个参数变为(const, override),对于非const方法,第四个参数变为override。这不是强制性的。

    7. 重复步骤直至完成要模拟的所有虚拟函数

  3. 在测试中使用Mock

    在测试中使用Mock的步骤:

    1. 从testing名称空间导入gmock.h的函数名(每个文件只需要执行一次)
    2. 创建一些Mock对象
    3. 指定对它们的期望(方法将被调用多少次? 带有什么参数? 每次应该做什么? 返回什么值 等等)
    4. 使用Mock对象;可以使用googletest断言检查结果。如果mock函数的调用超出预期或参数错误,将会立即收到错误信息。
    5. 当Mock对象被销毁时,gmock自动检查对模拟的所有期望是否得到满足
    
    

    在这个例子中,我们期望tutle的PenDown()至少被调用一次。如果在tutle对象被销毁时,PenDown()还没有被调用或者调用两次以上,测试会失败。

  4. 指定期望

    EXCEPT_CALL(指定期望)是使用Google Mock的核心。EXCEPT_CALL的作用是两方面的:

    1. 告诉这个Mock(假)方法如何模拟原始方法:

      我们在EXPECT_CALL中告诉Google Mock,某个对象的某个方法第一次被调用时,会修改某个参数,会返回某个值,第二次调用时, 会修改某个参数,会返回某个值......

    2. 验证被调用的情况

      我们在EXPECT_CALL中告诉Google Mock,某个对象的某个方法总共会被调用N次(或大于N次,小于N次)。如果

      最终次数不符合预期,会导致测试失败。

    4.1 基本语法

    
    
    • mock_object是对象
    • method(matchers)用于匹配相应的函数调用
    • cardinality指定基数(被调用次数情况)
    • action指定被调用时的行为

    例子:

    
    

    这个EXPECT_CALL()指定的期望是:在turtle这个Mock对象销毁之前,turtle的getX()函数会被调用五次。第一次返回100,第二次返回150,第三次及以后都返回200。指定期望后, 5次对getX的调用会有这些行为。但如果最终调用次数不为5次,则测试失败。

    4.2 参数匹配:哪次调用

    
    
    • _ 相当于“任何”
    • 100相当于Eq(100)
    • Ge(50)指参数大于或等于50
    • 如果不关心参数,只写函数名就可以。比如:EXPECT_CALL(turtle, GoTo)

    4.3 基数:被调用几次

    用Times(m),TIme(AtLeast(n))等来指定期待的调用次数

    Times可以被省略。比如整个EXPECT_CALL只有一个WillOnce(action)相当于也说明了调用次数只能为1

    4.4 行为:该做什么

    常用模式:如果需要指定前几次调用的特殊情况,并且之后的调用情况相同。使用一系列WillOnce()之后有WillRepeatedly()

    ​ 除了用来指定调用返回值的Return(),Google Mock中常用行为中还有:SetArgPointee(value),SetArgPointee将第N个指针参数(从0开始)指向的变量赋值为value。

    ​ 比如void getObject(Object* response){...}的EXCEPT_CALL:

    
    

    ​ 就修改了传入的指针response,使其指向了一个我们新创建的对象 。

    ​ 如果有多个行为,应该使用DoALL(a1, a2, ..., an)。DoAll指向所有n个action并返回an的结果。

    4.5 使用多个预期

    例子:

    
    

    ​ 正常情况下,Google Mock以倒序搜索预期:如果和多个EXCEPT_CALL都可以匹配,只有之前的,距离调用最近的一个EXPECT_CALL()会被匹配。例如:

    • 连续三次调用Forward(10)会产生错误因为它和 #2 匹配
    • 连续三次调用Forward(20)不会有错误因为它和 #1 匹配

    ​ 一旦匹配,该预期会被一直绑定,即使执行次数达到上限之后,还是生效的,这就是为什么三次调用Forward(10)超过了2号的EXPECT_CALL的上限时,不会去试图调用绑定1号EXPECT_CALL而报错的原因。

    ​ 为了明确地让某一个EXPECT_CALL “退休”, 可以加上RetiresOnSaturation(),例如:

    
    

    ​ 在这个例子中,第一次GetX()调用和#2匹配,返回20,然后这个EXPECT_CALL就 “退休”了;第二次 GetX()调用和 #1匹配,返回10

    4.6 Sequence

    可以用sequence来指定期望匹配的顺序

    
    

    image-20210812101918896

    ​ 在上面的例子中,创建了两个Sequence s1 和 s2,属于 s1 的有Reset() 和 GetSize(),所以Reset()必须在GetSize()之前执行。属于s2的有Reset()和Describe(A<const char*>()),所以Reset()必须在Describe(A<const char >())之前执行。所以,Reset()必须在Describe(A<const char>())之前执行,而GetSize()和Describe()这两者之间没有顺序约束。

    ​ 如果需要指定很多期望的顺序,有另一种写法:

    
    

    ​ 在这种用法中,scope(大括号中)的期望必须遵守严格的顺序。

    在这部分,我们找一个示例项目来演示,如何在不同的情景中使用Google Test和 Google Mock写单元测试用例。

    1. 项目结构

      示例项目是一个C++命令行聊天室软件,包含服务器和客户端。

      
      
    2. 普通测试

      如果被测试的函数不包含外部依赖,用Google Test基础的用法就可以完成用例编写。

      原函数:

      
      

      ​ 这个函数很简单,就是给body_length_赋值,但是有最大值限制。测试用例可以这样写:

      
      

      ​ 我们可以看到,对于这类函数,用例编写很直接简单,步骤都是构造变量,再用合适的Google Test宏来验证变量值或者函数调用的返回值。

    3. 简单Mock

      原函数

      
      

      ​ participants_类型是 std::set<chat_participant_ptr>。这个函数的目的很明显,将一个participant从set中移除。

      ​ 真实地创建一个聊天参与者participant对象可能条件比较严苛或者成本比较高。为了有效率地验证这个函数,我们可以新建一些Mock的chat_participant_ptr而不用严格地去创建participant对象。

      ​ chat_participant 对象:

      
      

      ​ Mock对象:

      
      

      ​ 测试用例:

      
      
    4. Web请求

      ​ chat_room中有一个log(),依赖网络请求。原函数:

      
      

      ​ 在单元测试中,我们只关心被测试部分的逻辑。为了测试这个函数,我们不应该创建真实的 requester,应该使用mock。

      ​ http_request对象:

      
      

      Mock对象:

      
      

      测试用例:

      
      
    5. 数据库访问

      chat_room 对象会将聊天者发送的消息存储在redis中。当新用户加入时,chat_room对象从数据库获取所有历史消息发送给该新用户。

      join函数:

      
      

      message_dao对象:

      
      

      Mock对象:

      
      

      测试用例:

      
      

    1、单元测试文件应该放在项目的什么位置?

    ​ 一般来说,我们是会在根目录创建一个tests文件夹,里面放单元测试部分的源码,从而不会和被测试代码混在一起

    ​ 如果需要和其他测试(如接口测试、压力测试)等区分开,可以:

    ​ 1、把tests改成unittests、utests等

    ​ 2、在tests创建不同的子文件夹存放不同类型的测试代码

    2、Google Mock只能Mock虚函数,如果我想Mock非虚函数怎么办?

    ​ 由于Google Mock(及其他大部分Mock框架)通过继承来动态重载机制的限制,一般来说Google Mock只能Mock 虚函数。如果要Mock非虚函数,官方文档提供这几种思路:

    ​ 1、Mock类和原类没有继承关系,测试对象使用函数模板。在测试中,测试对象接收Mock类。

    ​ 2、创建一个接口(抽象类),原类继承自这个接口(抽象类)。在测试中Mock这个接口(抽象类)。

    ​ 这两种方法,都需要对代码进行一定的修改或重构。如果不想修改被测试代码。可以考虑使用hook技术替换被 Mock的部分从而Mock一般函数。

    使用TMock对非虚函数Mock的例子:

    mock函数:

    
    

    ​ 单测中应用tmock的方法和Google Mock基本一致。但在结束的时候需要使用TMOCK_CLEAR清除exception,detach hook的函数,防止干扰其他单元测试。

    3、Google Test官方文档中说测试套件名称、测试夹具名称、测试名称中不应该出现下划线_,为什么?

    ​ TEST(TestSuiteName, TestName),生成名为TestSuiteName_TestName_Test的类。

    ​ 下划线_是特殊的,因为C++保留以下内容供编译器和标准库使用。所以开头和结尾有下划线很容易让生成的类的标识符不合法。

    ​ 另一方面,下划线可能让不同测试生成相同的类。比如TEST(Time, Files_Like_An_Arrow) {.....}都生成名为Time_Files_Like_An_Arrow_Test的类。

    4、测试输出里有很多Uniteresting mock function call 警告怎么办?

    ​ 创建的Mock对象的某些调用如果没有相应匹配的EXCEPT_CALL,Google Mock会生成这个警告。

    ​ 为了去除这个警告,可以使用NiceMock。比如如果原本使用MockFoo nice_foo;新建mock对象的话,可以改成NiceMock nice_foo; NiceMock是MockFoo的子类。

    ​ 框架的使用,无非是一些语法糖的差异和使用的难易程度。不管使用什么语言,什么框架,最关键的是利用单元测试的思路,写出解耦的、可测试的、易于维护的代码,保证代码的质量。

    ​ 单元测试是一种手段,能够一定程度的改善生产力。凡事有度,过犹不及。如果一味盲目的追求测试覆盖率,忽视了测试代码本身的质量。那么各种无效的单元测试反而带来了沉重的维护负担。因此单测的代码,本身也是代码,也是和项目本身的代码一样,需要重构、维护的。

编程小号
上一篇 2025-03-28 20:11
下一篇 2025-03-13 14:46

相关推荐

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
如需转载请保留出处:https://bianchenghao.cn/bian-cheng-ri-ji/68046.html