入门googletest

2023-11-11

引入

Google test是一种比较方便的C++测试框架,它可以帮助我们比较方便的进行测试代码,以及输出尽可能详细的失败信息,能够大大缩短我们测试代码的编写效率,而且这个框架的使用也比较简单。

之前还在学校学习过Junit框架,作为一个java程序员或多或少接触到这个框架,同样的C++的测试框架最常用的就是GTest。

什么是断言?

上述两个测试框架都是断言式测试框架,了解测试框架首先就要了解什么是断言。

在平时的开发当中,一个项目往往包含了大量的方法,可能有成千上万个。如何去保证这些方法产生的结果是我们想要的呢?当然了,最容易想到的一个方式,就是我们通过System.out来输出我们的结果,看看是不是满足我们的需求,但是项目中这些成千上万个方法,我们总不能在每一个方法中都去输出一遍。而且对于测试人员来说这个函数我也不需要知道细节,我们只需要知道这个函数需要什么参数和返回什么结果。

在这个环境下包裹式的断言式框架就应运而生,断言就是一种在程序中的一阶逻辑,当程序执行到断言的位置时,对应的断言就应该为真,若断言不为真时,程序会中止运行,并给出错误消息

这样看它和if逻辑很像,两者 的区别就是断言语句只会在debug版本中才有效是用来调试和定位错误的,而if是正常程序逻辑的一部分。

为什么学习GTest框架?

当我们把一个函数用断言函数包裹时就构成了一个测试用例,我们甚至可以自己规定自己的断言函数而写一个自己的框架。

我们之所以学习框架就是它足够的完善,GoogleTest采用了一系列断言来进行代码测试,定义了许多宏,当断言失败时Google Test将会打印出assertion时的源文件和出错行的位置,以及附加的失败信息,
用户可以直接通过“<<”在这些断言宏后面跟上自己希望在断言命中时的输出信息。

测试用例

我们直接通过一个简单的测试用例来看这个框架的语法(语法就是C++的语法,其实就是用框架中定义的一些函数将你要测试的函数或类方法包裹起来用于生成传入参数和验证输出结果。)

#include "log.h"
#include "gtest/gtest.h"
void ThrowException(int n) {
    switch (n) {
    case 0:
        throw 0;
    case 1:
        throw "const char*";
    case 2:
        throw 1.1f;
    case 3:
        return;
    }
}
 
TEST(ThrowException, Check) {
    EXPECT_THROW(ThrowException(0), int);
    EXPECT_THROW(ThrowException(1), const char*);
    ASSERT_ANY_THROW(ThrowException(2)); 
    ASSERT_NO_THROW(ThrowException(3));  
}
                                                                    

这个TEST测试函数就是我们预期ThrowException在传入0时,会返回int型异常;传入1时,会返回const char*异常。传入2时,会返回异常,但是异常类型我们并不关心。传入3时,不返回任何异常。当然ThrowExeception的实现也是按以上预期设计的。

这个很像我们写一个函数的时候,像测试这个函数的功能的时候就会把它从项目中抽离出来,用一个main函数去调用它的感觉;
这个测试函数做的工作也差不多,区别就是你不需要再把这个方法抽离项目了,单独再写一个测试类就可以做这个测试了,而且项目上线后这个测试函数会默认不再运行。

框架的一些常用宏和函数

框架测试宏

测试宏可以分为两大类:

  • ASSERT_*
  • EXPECT_*

这些成对的断言功能相同,但效果不同。
其中ASSERT_*将会在失败时产生致命错误并中止当前调用它的函数执行(注意不是当前测试用例)。
而EXPECT_会生成非致命错误,不会中止当前函数,而是继续执行当前函数。通常情况应该首选使用EXPECT_,因为ASSERT_*在报告完错误后不会进行清理工作,有可能导致内容泄露问题。

基本断言(真值比较)

Fatal assertion Nonfatal assertion Verifies
ASSERT_TRUE(condition); EXPECT_TRUE(condition); condition is true
ASSERT_FALSE(condition); EXPECT_FALSE(condition); condition is false

二值比较

Fatal assertion Nonfatal assertion Verifies
ASSERT_EQ(val1,val2); EXPECT_EQ(val1,val2); val1 == val2
ASSERT_NE(val1,val2); EXPECT_NE(val1,val2); val1 != val2
ASSERT_LT(val1,val2); EXPECT_LT(val1,val2); val1 < val2
ASSERT_LE(val1,val2); EXPECT_LE(val1,val2); val1 <= val2
ASSERT_GT(val1,val2); EXPECT_GT(val1,val2); val1 > val2
ASSERT_GE(val1,val2); EXPECT_GE(val1,val2); val1 >= val2

字符串比较

Fatal assertion Nonfatal assertion Verifies
ASSERT_STREQ(str1,str2); EXPECT_STREQ(str1,str2); the two C strings have the same content(字符串相等)
ASSERT_STRNE(str1,str2); EXPECT_STRNE(str1,str2); the two C strings have different content
ASSERT_STRCASEEQ(str1,str2); EXPECT_STRCASEEQ(str1,str2); the two C strings have the same content, ignoring case
ASSERT_STRCASENE(str1,str2); EXPECT_STRCASENE(str1,str2); the two C strings have different content, ignoring case

浮点对比断言

在对比数据方面,我们往往会讨论到浮点数的对比。因为在一些情况下,浮点数的计算精度将影响对比结果,所以这块都会单独拿出来说。GTest对于浮点数的对比也是单独的

Fatal assertion Nonfatal assertion Verifies
ASSERT_FLOAT_EQ(val1, val2); EXPECT_FLOAT_EQ(val1, val2); the two float values are almost equal
ASSERT_DOUBLE_EQ(val1, val2); EXPECT_DOUBLE_EQ(val1, val2); the two double values are almost equal

almost euqal表示两个数只是近似相似,默认的是是指两者的差值在4ULP之内(Units in the Last Place)。我们还可以自己制定精度

Fatal assertion Nonfatal assertion Verifies
ASSERT_NEAR(val1, val2, abs_error); EXPECT_NEAR(val1, val2, abs_error); the difference between val1 and val2 doesn’t exceed the given absolute error

成功失败断言

该类断言用于直接标记是否成功或者失败。可以使用SUCCEED()宏标记成功,使用FAIL()宏标记致命错误(同ASSERT_),ADD_FAILURE()宏标记非致命错误(同EXPECT_)

if (Check) {
  SUCCEED();
}
else {
  FAIL();
}

这儿有个地方需要说一下,SUCCEED()宏会调用GTEST_MESSAGE_AT_宏,从而会影响TestResult的test_part_results结构体,这也是唯一的成功情况下影响该结构体的地方。

异常断言

异常断言是在断言中接收一定类型的异常,并转换成断言形式。

Fatal assertion Nonfatal assertion Verifies
ASSERT_THROW(statement, exception_type); EXPECT_THROW(statement, exception_type); statement throws an exception of the given type
ASSERT_ANY_THROW(statement); EXPECT_ANY_THROW(statement); statement throws an exception of any type
ASSERT_NO_THROW(statement); EXPECT_NO_THROW(statement); statement doesn’t throw any exception

参数名输出断言

Fatal assertion Nonfatal assertion Verifies
ASSERT_PRED1(pred1, val1); EXPECT_PRED1(pred1, val1); pred1(val1) returns true
ASSERT_PRED2(pred2, val1, val2); EXPECT_PRED2(pred2, val1, val2); pred2(val1, val2) returns true
template <typename T1, typename T2>
bool GreaterThan(T1 x1, T2 x2) {
  return x1 > x2;
}
TEST(PredicateAssertionTest, AcceptsTemplateFunction) {
  int a = 5;
  int b = 6;
  ASSERT_PRED2((GreaterThan<int, int>), a, b);
}

看上面的用例大家也看出来了,所有的测试代码都被一个TEST所包裹起来了,这不是一个函数而是一个宏,宏就是用来封装一个测试代码块的一种定义字。

(额外说一句,Java中的Junit测试用例是用的注解机制,C++的GTest使用的宏定义机制,其本质是一样的,都是为了表示和区分测试代码和程序逻辑代码)

除了上面写到的TEST宏,这个框架还有TEST_F宏、TEST_P宏等下面就分别介绍几者的区别和用处。

测试用例(Test Case)是为某个特殊目标而编制的一组测试输入、执行条件以及预期结果,以便测试某个程序路径或核实是否满足某个特定需求,测试特例是测试用例下的一个(组)测试;

我们要写的测试代码都会包含在一个宏中构成一个测试特例。

TEST宏

TEST宏是一个很重要的宏,它构成一个测试特例。

TEST宏的第一个参数是test_case_name(测试用例名),第二个参数是test_name(测试特例名)

TEST(IsPrimeTest, Negative) {
 // This test belongs to the IsPrimeTest test case.
 EXPECT_FALSE(IsPrime(-1));
 EXPECT_FALSE(IsPrime(-2));
 EXPECT_FALSE(IsPrime(INT_MIN));
}

// Tests some trivial cases.
TEST(IsPrimeTest, Trivial) {
 EXPECT_FALSE(IsPrime(0));
 EXPECT_FALSE(IsPrime(1));
 EXPECT_TRUE(IsPrime(2));
 EXPECT_TRUE(IsPrime(3));
}

// Tests positive input.
TEST(IsPrimeTest, Positive) {
 EXPECT_FALSE(IsPrime(4));
 EXPECT_TRUE(IsPrime(5));
 EXPECT_FALSE(IsPrime(6));
 EXPECT_TRUE(IsPrime(23));
}

对于我们定义的测试用例名和测试特例名,不能有下划线(_)。因为GTest源码中需要使用下划线把它们连接成一个独立的类名

也不能有相同的“测试用例名和特例名”的组合——否则类名重合

测试用例名和测试特例名的分开,使得我们编写的测试代码有着更加清晰的结构——即有相关性也有独立性。相关性是通过相同的测试用例名联系的,而独立性通过不同的测试特例名体现的。

TEST_F宏

场景:我们要测试向数据库插入(id,name,location)这样的三个数据,那要先构建一个基础数据(0,Fang,Beijing)。我们第一个测试特例可能需要关注于id这个字段,于是它要在基础数据上做出修改,将(1,Fang,Beijing)插入数据库。第二个测试特例可能需要关注于name字段,于是它要在基础数据上做出修改,将(0,Wang,Beijing)插入数据库。第三个测试特例可能需要关注于location字段,于是它要修改基础数据,将(0,Fang,Nanjing)插入数据库。如果使用GTEST宏来测试的话,那么每个测试特例前,我们需要把所有的数据填充好,再去操作。真实场景中一条记录往往不止三个数据,这样做会显得非常繁琐和不直观。

Google工程师早就考虑到这样的场景,可以将上述的场景提炼一下,其实我们只要在每个特例执行前,获取一份基础数据(原始数据),然后修改其中本次测试特例关心的一项就可以了。同时这份基础数据不可以在每个测试特例中被修改——即本次测试特例获取的基础数据不会受之前测试特例对基础数据修改而影响——获取的是一个恒定的数据。
这个时候我们就需要使用TEST_F宏了,TEST_F叫作测试套件。

我们直接看一个例子来理解:

class TestFixtures : public ::testing::Test {
public:
    TestFixtures() {
        printf("\nTestFixtures\n");
    };
    ~TestFixtures() {
        printf("\n~TestFixtures\n");
    }
protected:
    void SetUp() {
        printf("\nSetUp\n");
        data = 0;
    };
    void TearDown() {
        printf("\nTearDown\n");
    }
protected:
    int data;
};
 
TEST_F(TestFixtures, First) {
    EXPECT_EQ(data, 0);
    data =  1;
    EXPECT_EQ(data, 1);
}
 
TEST_F(TestFixtures, Second) {
    EXPECT_EQ(data, 0);
    data =  1;
    EXPECT_EQ(data, 1);
}

这相当于我们使用一个TestFixtures类继承于::testing::Test类,将需要改变的数值封装起来,数据改变的操作就不用再设置很多参数了,而是通过同一个类保护这个基础数据。

像上述代码,First测试特例中,我们修改了data的数据(23行),第24行验证了修改的有效性和正确性。在second的测试特例中,一开始就检测了data数据(第28行),如果First特例中修改data(23行)影响了基础数据,则本次检测将失败。我们将First和Second测试特例的实现定义成一样的逻辑,可以避免编译器造成的执行顺序不确定从而影响测试结果。

TEST_P宏

这个宏和TEST_F大致相同,第一个参数是一个已定义类名,第二个参数是测试特例名,都是为了多场景下的测试,每个场景都可能要细致地考虑到到各个参数的选择时框架就提供了一种宏帮助我们组合场景和参数,它就是TEST_P宏。

它的TEST_F的区别是TestFixtures这个测试类我们不是继承的 : : t e s t i n g : : T e s t ::testing::Test ::testing::Test,而是继承的 : : t e s t i n g : : W i t h P a r a m I n t e r f a c e < T > ::testing::WithParamInterface< T> ::testing::WithParamInterface<T> ,这样我们可以使用重写这个类中的GetPara方法拿到参数的具体值,通过不同的参数改变而直接改变场景中的组合方式。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

入门googletest 的相关文章

随机推荐

  • 还不会JVM?今天来教你如何解决Outofmemory问题

    内存泄漏介绍 Java的一个核心优点是在内置垃圾收集器 简称GC 的帮助下实现了自动内存管理 GC隐式地负责分配和释放内存 因此能够处理大多数内存泄漏问题 虽然GC可以有效地处理大部分内存 但它不能保证为内存泄漏提供一个万无一失的解决方案
  • python爬取12306_详解python 爬取12306验证码

    一个简单的验证码爬取程序 本文介绍了在Python2 7环境下爬取网站验证码 思路就是获取验证码对应的url 然后发起requst请求 读取该URL对应的内容 然后写入到一个本地文件 实现一个验证码的保存 大量下载可以把以上程序写入一个死循
  • Typora 下载方法(windows/ linux)

    原先还没有收费的时候 typora下载比较方便 以ubuntu为例 sudo apt key adv keyserver keyserver ubuntu com recv keys BA300B7755AFCFAE add Typora
  • qt中锁住按钮的高光所在的位置

    void MainWindow on pushButton clicked ui gt pushButton 2 gt setEnabled true ui gt pushButton gt setEnabled false ui gt p
  • 代码随想录算法训练营19期第48天

    198 打家劫舍 视频讲解 动态规划 偷不偷这个房间呢 LeetCode 198 打家劫舍 哔哩哔哩 bilibili 代码随想录 初步思路 动态规划 总结 dp i 考虑下标i 包括i 以内的房屋 最多可以偷窃的金额为dp i 递归公式
  • 解决远程搜索select框focus()聚焦后无光标无法输入的问题

    解决远程搜索select框focus 聚焦后无光标无法输入的问题 实测antd和elementUI通用 需求为全键盘操作 html内容
  • es index 改名_将字段重命名为Elasticsearch中的新索引

    I have an index with this mapping curl XPUT http localhost 9200 origindex mapping page d page properties title type text
  • Http中Content-Type等属性详解

    前言 一直以来对HTTP请求中各种属性一知半解 偶然在博客中找到一篇 特意摘录过来方便自己以后查看 正文 敬请关注博客 后期不断更新优质博文 谢谢 这里讲解Content Type的可用值 以及在spring MVC中如何使用它们来映射请求
  • 封装Vue.js组件库

    一 组件库介绍 1 开源组件库 Element UI IView 2 组件开发方式CDD 自下而上 从组件级别开始 到页面级别结束 3 CDD的好处 组件在最大程度上被重用 并行开发 可视化测试 二 处理组件边界情况 vue中处理组件边界情
  • 详解IP、TCP报文头部及报文封装过程

    TCP报文 字段介绍 1 源端口 16bit 一个端口所属一个进程 可以通过源端口定位到具体的进程 2 目的端口 16bit 通过目的端口和Ip报文的目的ip地址可以唯一定位到一个进程 3 序号 32bit 当前发送数据的起始序号 每一个字
  • 手机视频网页点播服务器,手机视频点播系统搭建完整方案

    技术选择 服务器操作系统 windows 点播服务器 Nginx 转码和生成清单 ffmpeg 手机客户端 ExoPlayer SDK Nginx和ffmpeg都是跨平台的 应该也可以在linux上搭建出来 搭建视频点播和直播系统使用的协议
  • 2022-2023年度应届生画像白皮书

    导读 进入2023年以来 全球经济环境和国际关系日趋动荡复杂 我国的外贸需求增长放缓 企业经营新老风险交织叠加 在此背景下 诸多企业逐步加大了提质增效的力度 更加谨慎地管理招聘需求 减少招聘规模 加高招聘门槛 上半年招聘市场需求萎缩明显 然
  • 数字IC设计学习笔记_静态时序分析STA_ PrimeTime 基本概述

    数字IC设计学习笔记 PrimeTime 基本概述 1 PrimeTime 基本概述 2 运行模式 1 PrimeTime 基本概述 PrimeTime Synopsys公司提出的 针对于复杂的 全芯片的 门级静态时序分析的工具 可集成在逻
  • Process实操教程

    Process实操教程 中介效应检验 1 Process 下载安装 2 Process 做中介检验 Hello 大家好 这里是行上行下 我是喵君姐姐 之前给大家介绍了如何用amos进行中介调节效应检验 Amos实操教程 中介效应检验 今天我
  • 队列的算法实现[数组法]

    采用数组来保存队列的元素 设立一个队首指针 front 一个队尾指针 rear 分别指向队首和队尾元素 则 rear front 即为存储的元素个数 include
  • rust使用睡袋_Rust怎么按睡袋

    发布时间 2016 08 17 在RUST这款游戏中 玩家需要做的事情就是尽可能的生存下来 很多时候对于资源的分配不均可能导致有些玩家缺乏生存物资 所以容易产生激烈的竞争 如何生存就成了玩家需要做的事情 卡灵魂卡石头方法 卡灵魂的意思是 你
  • 第十四天---LDP,MPLS技术

    LDP 标签分发协议 主要应用在MPLS的控制层面 MPLS控制层面需要完成的工作主要就是分配标签和传递标签 分配标签的前提是本地路由表中得先存在标签 传递标签的前提也是得先具备路由基础 所以 LDP想要正常工作 则需要IGP作为基础 1
  • 蛋花花分析孩子学编程到底有没有用

    蛋花花分析孩子学编程到底有没有用 据蛋花花了解对于6到16岁的青少儿来说 在这个AI时代 学习编程简直就是必修课 那么究竟什么样的课程是适合孩子的 能够让孩子对编程保持持续的兴趣 又是什么样的课程能够让孩子们学到的是计算机语言的基本逻辑及分
  • Unity上一个动画的Duration导致下一个动画的事件被调用多次

    情景 我用一个blendtree JumpAndFallBlendTree来切换Jump和Fall的动画 当inAir为true时进入 当inAir为false时退出 当人物着地时 我将inAir置为false 将land置为true 播放
  • 入门googletest

    引入 Google test是一种比较方便的C 测试框架 它可以帮助我们比较方便的进行测试代码 以及输出尽可能详细的失败信息 能够大大缩短我们测试代码的编写效率 而且这个框架的使用也比较简单 之前还在学校学习过Junit框架 作为一个jav