基于hook和gmock开展单元测试

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

内容简介:基于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-5 / 69

《人人都是架构师:分布式系统架构落地与瓶颈突破》并没有过多渲染系统架构的理论知识,而是切切实实站在开发一线角度,为各位读者诠释了大型网站在架构演变过程中出现一系列技术难题时的解决方案。《人人都是架构师:分布式系统架构落地与瓶颈突破》首先从分布式服务案例开始介绍,重点为大家讲解了大规模服务化场景下企业应该如何实施服务治理;然后在大流量限流/消峰案例中,笔者为大家讲解了应该如何有效地对流量实施管制,避......一起来看看 《人人都是架构师:分布式系统架构落地与瓶颈突破》 这本书的介绍吧!

在线进制转换器
在线进制转换器

各进制数互转换器

HTML 编码/解码
HTML 编码/解码

HTML 编码/解码

HEX CMYK 转换工具
HEX CMYK 转换工具

HEX CMYK 互转工具