引入

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方法拿到参数的具体值,通过不同的参数改变而直接改变场景中的组合方式。

入门googletest相关推荐

  1. cmake + googletest 之一 入门

    一: 环境 OS: Ubuntu 18.04 CMAKE: 3.14.5 GTest: 1.8.1 注意: 不涉及如何安装 CMAKE 了, 相信看到这篇文章的你一定已经安装好了. 假设你已经基本会使 ...

  2. GoogleTest从入门到入门,小白都能看懂的gtest详细教程

    单元测试 项目管理和技术管理中做单元测试,衡量一个软件是否正常的标准,良好的单元测试以及足够多的覆盖率,至少保证关键功能,关键业务的覆盖率接近100%. gtest是谷歌公司发布的一个跨平台(Linu ...

  3. Googletest 实现简要分析

    借助于 Googletest 测试框架,我们只需编写测试用例代码,并定义简单的 main() 函数,编译之后并运行即可以把我们的测试用例跑起来.(更详细的内容可参考 Googletest 入门).但 ...

  4. 快速上手Google C++ 测试框架googletest

    Google Test 是一套编写C++单元测试的框架,可以运行在很多平台上. 如何安装 $ git clone https://github.com/google/googletest.git $ ...

  5. Gtest 测试指导 入门基础(A)

    Gtest 测试指导 入门基础(A) Table of Contents • 1 Gtest的基本使用,包括下载,安装,编译. o 1.1 下载 o 1.2 编译  1.2.1 Gtest静态库的编 ...

  6. GoogleTest使用教程

    1.简介 GoogleTest是由Google开发的一个C++测试框架,支持Linux.Windows和macOS操作系统,使用Bazel或CMake构建工具. 项目主页:https://github ...

  7. Fast DDS入门二、Fast DDS在Windows平台的编译安装

    Fast DDS入门五.在Windows平台创建一个简单的Fast DDS示例程序 1 Fast DDS动态库的编译安装 本节提供了在Windows环境中从源代码安装Fast DDS的说明.将安装以下 ...

  8. 深度学习部署神器——triton inference server入门教程指北

    开新坑!准备开始聊triton. 老潘用triton有两年多了,一直想写个教程给大家.顺便自己学习学习,拖了又拖,趁着这次换版本的机会,终于有机会了写了. ![](https://img-blog.c ...

  9. GoogleTest进阶——参数测试、Mock测试、耗时测试、类型测试

    1. 前情提要 GoogleTest是一个为 C++ 开发的单元测试框架,为书写单元测试提供了很多有利的轮子,可以较大程度上的避免为了书写 单元测试 而需要重复搭建轮子的困扰. 本系列文章之前有一篇入 ...

最新文章

  1. Ubuntu 镜像使用帮助
  2. 企业管理难题:团队协作
  3. 数据库死锁查询及处理
  4. Asp.net MVC调试-使用IP监听
  5. stylus导入时 报错These relative modules were not found
  6. java中的jpa_JPA教程–在Java SE环境中设置JPA
  7. 全栈工程师薪水_2019Java 全栈工程师 进阶路线图!一定要收藏!
  8. 使用 systemd 定时器调度任务
  9. android 插入gif,android – Gboard:在EditText上启用GIF插入
  10. python自动化办公模块_Python 自动化办公之 Excel 模块 — openpyxl 的基本使用!
  11. [BZOJ1492][NOI2007]货币兑换Cash(斜率优化+CDQ分治)
  12. win10计算机管理看不见蓝牙,如何解决Win10设备管理器找不到蓝牙?
  13. 2%用计算机怎么算,五险一金计算器的使用方法
  14. 连续状态空间模型离散化
  15. 小米笔记本Air13.3加装固态硬盘
  16. 使用 Nginx 反向代理域名
  17. 【零基础系列】了解学习 uni-app
  18. 七夕节来用python表白吧!爱情病毒浸染你的心!
  19. 新能源车动力总成技术探讨:混动和纯电之争、电驱动未来发展趋势
  20. MySQL8.0与SQL server 2016的技术区别(一)

热门文章

  1. 代码换肤术(一)——C#和VB (转)
  2. 155-161 李游前端精品课程笔记
  3. mongodb 权威指南_有关MongoDB安全性的权威指南
  4. python输入姓名年龄_【python学习】今天看看学习 %d ,%s, %f 等用法,下面的学习例子是说输入名字、年龄、工作,工资。并给出65岁退休还差多久的计算...
  5. 哪款耳机音质好又耐用?音质最好的耳机排名
  6. 深度解析LED显示屏SMD封装
  7. VisualVM安装插件报错 总结
  8. 【APP】App测试方法总结
  9. 【原创】Xshell无限标签和永久去更新教程
  10. Lua使用luacom模块操作EXCEL