基于hook和gmock开展单元测试

栏目: 编程工具 · 发布时间: 7年前

内容简介:基于hook和gmock开展单元测试

​一、什么是UT

单元测试(英语:Unit Testing)又称为模块测试,是针对程序模块(软件设计的最小单位)来进行正确性检验的测试工作。程序单元是应用的最小可测试部件。在过程化编程中,一个单元就是单个程序、函数、过程等。

对于面向对象编程,最小单元就是方法,包括基类(超类)、抽象类、或者派生类(子类)中的方法—摘自维基百科。

二、为什么要做UT

16年下半年对滴滴SDK接口进行梳理,并进行了BVT接口自动化以及截图半自动化效果验证,但是有几个问题没能得到很好的解决:

(1)SDK的整体代码行覆盖率是57.6%,但导航引擎的覆盖率仅31.2%;

(2)从SDK这层测试导航引擎,需要回放不同类型的轨迹,测试效率低;

(3)从端上直接测试引擎,不符合分层测试思想,较难发现深层次问题。

三、UT开展三部曲

(1)熟悉被测模块

无论是做自动化测试也好,集成测试也罢,都需要对待测模块有一定程度的了解,对于单元测试这种需要深入代码逻辑的测试来讲,更是如此。在开展测试之前,主要从几个方面对待测模块进行分析:代码逻辑、圈复杂度、代码深度、扇入、扇出以及代码行等,如下图1所示:

基于hook和gmock开展单元测试

图1可测性分析

可以看到,该模块有些接口的圈复杂度达到了200+,而业内设计较好的代码圈复杂度在15左右,对这类接口,不建议做UT,最好的方法是让开发进行优化,降低函数的圈复杂度。

(2)选用合适的测试框架

工欲善其事必先利其器,对UT而言也是如此。C++的历史已经非常悠久了,开源框架也是非常多,其中google公司出品的gtest和gmock就是做C++单测的必备神器(https://github.com/google/googletest)。

目前该测试框架可以支持Windows、 Linux 以及Mac OSX平台。

结合SDK实际情况,整合gtest和gmock框架至测试分支,如下图2所示:

基于hook和gmock开展单元测试

图2代码组织结构

这里的UT是嵌入到开发工程里的,做为开发源码WorkSpace中的一个target,该target和之前BVT的target的区别在于,其是基于MAC OSX的Command Line工程,运行环境是MAC OSX,类似于Windows下的可执行文件,而BVT自动化的case运行环境都是基于iOS或者是iOS Simulator系统,这些差别所带来的影响会在第4节中详细说明。

(3)设计单测case

环境部署好了,剩下的就是根据之前的接口分析来设计单测case了。这里举一个简单的例子来进行说明,被测接口是getItem,代码逻辑比较简单,如下图3所示:

基于hook和gmock开展单元测试

图3被测接口

如何设计case呢?对这种既有入参,又有返回值的函数,相对是比较好设计case并进行结果验证的,我们重点关注入参i在不同取值的情况下,函数返回结果是否符合预期。测试代码的编写如下图4:

基于hook和gmock开展单元测试

图4测试用例

这样的case是不是很简单,但在写单测的过程中,我们所面对的测试对象往往复杂的超出你的想象。

四、遇到的问题与解决方案

(1)类的private、protected函数,外部测试类无法调用

开发在设计类时,对于不想让外部类访问的属性以及方法都可以定义为私有的,这并没有什么设计上的问题,但对于测试而言,就要突破这种访问限制,做到public和非public接口都可以在测试类中被访问到,对这个问题,最简洁快速的方法是:在测试类中将private、protected关键字重定义为public,之后在测试类中就可以访问到被测函数的所有方法以及属性。代码如下图5:

基于hook和gmock开展单元测试

图5private可访问

(2)对回调函数的测试

对于C++中的异步回调,可以采用异步变同步的方法,保证该调的时候可以正常的调用。

(3)static以及非虚函数,无法使用现有的框架进行mock

1)为什么无法mock static类型的函数?

在Google Mock的官方“常见问题”的回答中,Google是这样的:You can, but you need to make some changes.即如果你需要mock一个静态函数,那说明你的程序模块过于“紧耦合”了(并且灵活性不够、重用性不够、可测试性不够),你最好是定义一个小接口,通过这个接口来调用那个函数,然后就容易mock了。

2)为什么无法mock非虚函数?

C++ allows a subclass to change the access level of a virtual function in the base class。C++允许用基类的指针来调用子类的函数,举个例子,就很容易明白了,如图6:

基于hook和gmock开展单元测试

图6基类指针调子类函数

非虚函数不具备这样的特性,无法很方便的使用gmock。在实际开发过程中,我们不可能将所有的接口都定义为虚函数,那这个问题如何解呢?

方案一

见 google官方手册https://github.com/google/googletest/blob/master/googlemock/docs/CookBook.md,

Google Mock can mock non-virtual functions to be used in what we call hi-perf dependency injection,即依赖注入。该方案的原理是通过模板类的方式来实现,在开发代码中通过传入实际对象来调用真实接口,在测试代码中通过传入mock对象来调用mock出来的接口。Google官方提供的一个例子,如图7:

基于hook和gmock开展单元测试

图7 依赖注入

方案二

重新定义一个mock类B,该类并不继承被测类A,但是在mock类B中,需要实现和A中同样的函数接口,除了待mock的接口。即被测类A和mock类B之间没有任何关系,mock类B中同样实现了被测类A中的大部分接口,在测试代码中,通过声明mock类B的对象,来达到测试目的。

上述两种方案都可以解决gmock不能mock非虚函数的问题,但是都并不完美,均有其缺点:方案一最大的问题是需要修改开发源码,这对于老工程来讲,几乎是不可能的,除非赶上开发重构代码;方案二虽然不会修改开发源码,但是需要维护一套开发代码,当开发代码有变更时,mock的类B需要进行同步修改,无疑加大了测试的维护成本。

如何解决?——Hook

提到hook,就不得不提百度在11年开源的Baiduhook,其提供了linux平台下C/C++程序的hook功能, 可以解决gmock只能mock虚函数的限制。Linux上的hook和windows上的原理差不多,操作基本上是对目标函数进行劫持,替换成自己的函数,然后在自己的函数中进行一些用户预期的操作,比如修改函数返回值等。对hook原理比较感兴趣的可以拜读下源码:https://code.google.com/archive/p/baiduhook/

看起来似乎可以解决我们的问题了,但是不幸的是,目前该hook技术仅支持了Linux平台,而我们的测试框架是在MAC OSX系统下搭建的,MAC OSX是Unix系统,bhook无法在MAC下使用。综合考虑后,决定在Linux系统进行导航引擎的单测。百度以及公司内部都基于hook以及gmock,对gtest进行了二次封装,形成了自己的单元测试框架btest和ttest。

(4)ttest和btest

这两个测试框架的部署,也是废了一番周折……这两个测试框架都依赖Linux的底层系统库libbfd(二进制文件描述库)和libopcodes(程序调试,归档等)。

Øttest:须安装特定版本的binutils以及对应版本的gcc。

1) binutils版本不对

所有的case以及源码编译没有问题,但是在运行case的时候会出现如下图8所示的core:

基于hook和gmock开展单元测试

图8binutils版本错误引起的core

2)gcc版本不对

gcc5.1版本在编译gtest源码库时,会出现链接错误:spec-builders.h:754: undefined reference to `testing::internal::FormatFileLocation

Stack OverFlow上给的解释是:

基于hook和gmock开展单元测试 Øbtest:仍需要特定版本的Linux系统以及gcc版本。

1) 虚拟机centOS4.3+gcc3.4.5

该虚拟机上安装的btest也只有相应的lib和so文件,没有btest的源码,直接运行自带的samples,btest运行完好,没有相应的core。

注:实际运行过程中对gdb版本也有要求(6.7及以上版本),否则会出现:this=dwarf2_read_address: Corrupted DWARF expression。

2) 虚拟机centOS6.5

centOS4.3上整个测试框架运行没有问题,但是毕竟该版本的系统太老了,centOS官方已经停止维护了,各种软件包都没法通过yum来安装,这也给后续配置vim开发环境带来了一定程度的麻烦,所以,就想着能否用高版本的centOS来试下btest是否能运行,结果是不行的,同样会崩到系统库中。

总结,这两个测试框架都是基于Linux系统的hook技术,将hook和gmock完美结合,但是都依赖于Linux系统的底层库,需要特定版本的系统库。虽然有了btest或者ttest,可以很方便的mock接口,但方便的同时,我们就不会再去思考如何对复杂接口进行解耦和了。

(5)有些函数扇出太高,可测性太低

有些历史接口,其扇出达到了40+,代码行也有900+,圈复杂度更是达到了400+,对这样的一类接口,几乎不具可测性,如果这类接口又是业务中很重要的接口,建议开发一起从可测性角度出发重新设计,达到可测性后再来开展单元测试。

五、UT和SDK测试的差异

(1)SDK测试的对象是公开的API,这些API有详细的接口说明文档。UT的测试对象是内部函数,这些函数没有任何文档,需要测试通过debug或者找开发咨询去了解。

(2)SDK测试可能只需要了解某个API被设计来干什么,对其内部如何设计关心的并不多。UT不单需要知道被测函数的功能是什么,还要了解其是如何设计的,实现原理是什么,要求比SDK测试要高。

(3)SDK测试除了要保证接口本身的功能外,更多的还要关心第三方使用者会如何用,即调用场景。UT不需要关心外部如何调,更加聚焦函数本身。

(4)数据构造,UT深入到函数内部,构造的数据不仅仅包含函数入参,还包含函数内部用到的一些数据。

(5)如果代码发生了重构,UT的历史case大多数情况下也得跟着重新设计,测试后期的维护成本也很高。

版权所属,禁止转载


以上所述就是小编给大家介绍的《基于hook和gmock开展单元测试》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

宇宙涟漪中的孩子

宇宙涟漪中的孩子

谢云宁 / 四川科学技术出版社 / 2017-11 / 28.00元

近未来。日冕科技公司通过建造围绕太阳的光幕搜集了近乎无穷的能源,这些能源主要用于地球上的网络空间建设。随着全球网络时间频率的不断提升,越来越多的人选择接驳进虚拟空间,体验现实中难以经历的丰富人生。 网络互动小说作者宁天穹一直自认为是这些人中普通的一员,有一天却被一名读者带进反抗组织,了解到日冕公司的各种秘密,并被告知自己的小说将在抵抗运动中起到重要作用。 起初他拒绝参与,但看到地球被笼......一起来看看 《宇宙涟漪中的孩子》 这本书的介绍吧!

随机密码生成器
随机密码生成器

多种字符组合密码

XML、JSON 在线转换
XML、JSON 在线转换

在线XML、JSON转换工具

正则表达式在线测试
正则表达式在线测试

正则表达式在线测试