测试框架gtest以及内存泄漏检测

文章目录

  • 我们首先要谈的几个问题
  • google test
  • 什么是好的测试
  • 案例
  • test fixture(测试夹具)
  • 断言
  • 类型参数化
  • 事件
  • 内存泄漏的检验
  • googlemock


我们首先要谈的几个问题

1、什么时候使用google test
2、什么是一个好的测试工具
3、自己实现该单元的测试,应该注意哪些点
4、googletest的层次关系
5、学习一些googletest案例
6、学习googletest的事件,如何打桩

google test

一个项目有以下这些阶段
开发阶段:写功能,编译调试
还有部署阶段和上线阶段
googletest作为一个工具可以快速发现一些错误,并且及时纠正错误,这个工具在代码编译成功后,要去发现一些逻辑错误。
主要在我们的开发阶段,也会在我们的部署阶段中遇到。

什么是好的测试

好的测试工具应该具备哪些特性
1、测试应该是独立的与可重复的
我们先看看我们的代码模块当中,每一个模块都有一个叫做test的文件,这个就是专门来检测模块功能的文件,里边会写很多的测试用例去验证结果,比如说为了验证这个模块是否有问题,那么就会运行这个test文件中的测试样例,对这个模块进行单独的测试,也可以整体地去跑整个项目的功能,这就是我们所说的可以单独性,与可重复性。先整体去跑,然后再局部去跑就可以发现其中的错误
测试框架gtest以及内存泄漏检测_第1张图片
2、测试应该很好地“组织”,并反映出测试代码的结构
比如说我们测试下边的模块,其中要包括所有情况的测试样例,就要枚举所有的情况,并且写出每一种情况的测试样例
测试框架gtest以及内存泄漏检测_第2张图片
这里我们可以看到多个线程并发读,其中包括1个到4个线程并发读与并发写的情况测试框架gtest以及内存泄漏检测_第3张图片
3、测试应该是可移植的和可重用的。谷歌有许多与平台无关的代码;它的测试也应该是平台中立的。
googletest 可以在不同的操作系统上工作,使用不同的编译器,所以 googletest 测试可以在多种配
置下工作。
4、当测试失败时,他们应该提供尽可能多的关于问题的信息。googletest 不会在第一次测试失败时停止。相反,它只停止当前的测试并继续下一个测试。还可以设置报告非致命失败的测试,在此之后当前测试将继续进行。因此,您可以在一个运行-编辑-编译周期中检测和修复多个错误。意思就是说我们的测试不会因为一个模块出问题而影响其他模块的测试
5、测试框架应该将测试编写者从日常琐事中解放出来,让他们专注于测试“内容”。googletest 自动跟踪所有定义的测试,并且不要求用户为了运行它们而枚举它们。
6、测试应该是快速的。使用 googletest,您可以在测试之间重用共享资源,并且只需要为设置/拆除支付一次费用,而无需使测试彼此依赖

测试框架gtest以及内存泄漏检测_第4张图片

案例

我们先了解一下测试的层次关系
测试框架gtest以及内存泄漏检测_第5张图片
我们在测试的时候,一个测试集合会按照顺序执行,但是也会遇到测试样例增加的情况,我们怎么处理呢?我们就想到了设计模式。
我们来思考一下独立、自动跟踪怎么实现,我们所用的设计模式就是单例模式
单例模式:对象唯一,是全局的访问点
单元测试会不断增加测试样例,如何跟踪测试对象。
我们就要回到当时设计模式的两个概念,一个是稳定点一个是变化点
稳定点:测试集合,按照顺序执行案例
变化点:会不断地新增测试案例
我们还有个方法就是组合模式+责任链,因为我们看到好的测试方法当中有一项是不会因为一次失败而停止,会继续测试下一个用例,组合模式是用来解决自动跟踪所有测试。
比如说我们来看看这个Foo函数的测试,下边写了两个测试样例,用的断言去检验,第一个测试样例测试正确的,有一个是测试失败的。
main函数里边的RUN_ALL_TESTS()是运行所有的测试样例
测试框架gtest以及内存泄漏检测_第6张图片
我们运行测试样例可以看到结果,有个成功有个失败的,我们可以看到这个结果,2+2期望的是4,然而实际是3,另外一个1+2也是,所以就有两处错误
测试框架gtest以及内存泄漏检测_第7张图片
googletest一共两个部分,一个是googletest,另一个是googlemock,分别包含头文件和库文件,googlemock依赖于googletest我们在实际运用当中,用到了googlemock,就不用加googletest的头文件或者库文件,我们直接加googlemock的
测试框架gtest以及内存泄漏检测_第8张图片
这里我们要进行单独测试的时候,不需要执行main函数,就用图中的方法
测试框架gtest以及内存泄漏检测_第9张图片
关于模块测试,我们再来剖析一下,先添加头文件,再写测试样例
测试框架gtest以及内存泄漏检测_第10张图片
比如说我们要测试阶乘和质数判断的功能,就要先添加头文件
测试框架gtest以及内存泄漏检测_第11张图片
我们可以看到test_suite_name的意思就是测试套件,一个测试套件就是一个功能
测试框架gtest以及内存泄漏检测_第12张图片
比如说这个Factorial我们在这个测试套件,输入一个测试案例Negative,下边的都是断言,还要注意边界问题,也就是INT_MIN那个测试断言
测试框架gtest以及内存泄漏检测_第13张图片

做了前边的加入头文件,以及写测试样例以后,我们就需要执行测试样例,我们就执行main,也就是执行main函数里边的RUN_ALL_TESTS()
测试框架gtest以及内存泄漏检测_第14张图片

test fixture(测试夹具)

用相同的数据配置来测试多个测试案例。
测试夹具也属于测试套件,然后呢,它的测试案例是可以共享数据的,比如说下边的派生类,与基类的关系,要测试这种类就需要共享数据,那么我们写测试套件使用的TEST_F而不是TEST了。测试夹具类似于我们的模板模式
测试框架gtest以及内存泄漏检测_第15张图片
EXPECT与ASSERT的区别在于它们处理错误的方式不一样,比如说前者出错,则可以继续运行后边的语句,后者出错将不会继续执行下去。它会走下一个测试案例,这个后者就是一个责任链模式

断言

断言成对出现,它们测试相同的东西,但对当前函数有不同的影响。 ASSERT_* 版本在失败时产生致命失败,并中止当前测试案例。 EXPECT_* 版本生成非致命失败,它不会中止当前函数。通常首选 EXPECT_* ,因为它们允许在测试中报告一个以上的失败。但是,如果在有问题的断言失败时继续没有意义,则应该使用 ASSERT_* 。
所有断言宏都支持输出流,也就是当出现错误的时候,我们可以通过流输出更详细的信息;注意编码问题,经流输出的信息会自动转换为 UTF-8;
比如说下边这句,还可以打印后边的那个字符串。
EXPECT_TRUE(my_condition) << “My condition is not true”;

明确指定成功或者失败
有时候我们测试案例当中的条件太复杂,不能使用断言,那么自己写判断语句;自己返回 成功或者失败;‘
SUCCEED() 或者 FAIL()
还有我们的谓词断言
测试框架gtest以及内存泄漏检测_第16张图片

类型参数化

有时候相同的接口,有多个实现,下面是复用测试代码流程;
复用测试案例,我们可以只写一个测试案例,我们会有多种实现,然后只调用这一个测试案例,本质上是一种复用的方式
以下有这些步骤
测试框架gtest以及内存泄漏检测_第17张图片
我们要实现的大致就是下面这个,第一步,tesfixture继承自test这个类,把这个类的多种实现,也就是多个class放到Implementtation的参数列表里边去,然后这个多个类的接口实现Implementation通过传参到TYPED_TEST_SUITE里边去,也就是我们参数化的测试套件,也就把多种实现的类(class1,class2.。。。)传进来,class1等都在TYPED_TEST里边测试。我们的参数化测试用的就是TYPED_TEST
测试框架gtest以及内存泄漏检测_第18张图片
有时候你写了某个接口,期望其他人实现它,你可能想写一系列测试,确保其他人的实现满足你的测试;
测试框架gtest以及内存泄漏检测_第19张图片

事件

可以通过 googletest 的事件机制,在测试前后进行埋点处理;
也就是在测试套件执行前加一个函数执行,或者在套件执行后加一个函数执行,也就可以是在测试案例前后加,也可以在断言前后加。类似于模板模式。

内存泄漏的检验

怎么产生?1. 忘记释放了;2. 因为逻辑bug,跳过了释放流程;
new 是 c++ 中的操作符;
调用 operator new 分配内存;
调用构造函数在步骤 1 返回的内存地址生成类对象;
可以通过重载 new 来修改 1 的功能;
delete 与 new 类似;只是是先调用析构函数,再释放内存;
我们可以重载new与delete操作符,用一个静态函数去计算new与delete的调用次数
测试框架gtest以及内存泄漏检测_第20张图片
我们·应该这样检测,使用测试案例,测试案例之前调用一下,测试案例结束之后调用一下就能够统计申请与释放的次数

class LeakChecker : public EmptyTestEventListener {
private:
// Called before a test starts.
void OnTestStart(const TestInfo& /* test_info */) override {
	initially_allocated_ = CLeakMem::allocated();
}
// Called after a test ends.
void OnTestEnd(const TestInfo& /* test_info */) override {
	int difference = CLeakMem::allocated() - initially_allocated_;
// You can generate a failure in any event handler except
// OnTestPartResult. Just use an appropriate Google Test assertion to do
// it.
	EXPECT_LE(difference, 0) << "Leaked " << difference << " unit(s) of class!";
}
int initially_allocated_;
};

googlemock

当你写一个原型或测试,往往不能完全的依赖真实对象。一个 mock 对象实现与一个真实对象相同的接口,但让你在运行时指定它时,如何使用?它应该做什么?(哪些方法将被调用?什么顺序?
多少次?有什么参数?会返回什么?等)
可以模拟检查它自己和调用者之间的交互;
mock 用于创建模拟类和使用它们;
使用一些简单的宏描述你想要模拟的接口,他们将扩展到你的 mock 类的实现;
创建一些模拟对象,并使用直观的语法指定其期望和行为;
练习使用模拟对象的代码。 googlemock 会在出现任何违反期望的情况时立即处理。
googlemock 依赖 googletest;调用 InitGoogleMock 时会自动调用 InitGoogleTest ;
什么时候使用?
测试很慢,依赖于太多的库或使用昂贵的资源;
测试脆弱,使用的一些资源是不可靠的(例如网络);某个功能由多个网络交互构成测试代码如何处理失败(例如,文件校验和错误),但不容易造成;
确保模块以正确的方式与其他模块交互,但是很难观察到交互;因此你希望看到观察行动结束时的副作用;想模拟出复杂的依赖;
编写模拟类,用这个来代替输入或者替换掉一些函数要调用的其他接口或者类
测试框架gtest以及内存泄漏检测_第21张图片

你可能感兴趣的:(进阶知识,测试工具)