现在的位置: 首页 > 综合 > 正文

C/C++单元测试理论精要(七)

2014年04月05日 ⁄ 综合 ⁄ 共 2583字 ⁄ 字号 评论关闭

2.2 解决可测性问题

 

    上一节具体分析了可测性问题,接下来,我们来看看如何解决这些问题。下图中,彩色的部分是需要解决的可测性的具体的问题。

 

   

 

 

与其他代码隔离
    为了提高效率,应该一次性将一位工程师负责的测试任务隔离出来。隔离时,应把源文件分为三类:被测文件,外围文件,其他文件。被测文件是测试目标,外围文件是指与被测文件关联密切的底层或相关文件,这些在测试时最好直接调用实际代码,减少打桩造成的失真,其他文件则完全隔离。
   

    
    

    上图是隔离测试任务的示意界面,只要将要测试的文件设为T,不需要测试或者是别的工程师负责测试,但可能需要实际调用的文件设N。其他文件是缺省的X,工具会视需要自动打桩。这样就将测试任务一次性隔离出来了,现在,不管原来的项目多大,测试任务都可以独立编译运行。

 

解决编译差异和平台差异
    跨平台单元测试,例如在PC上测试嵌入式项目,或在windows上测试Linux项目,由于编译环境可能不同,代码中通常会出现一些非标准的特殊关键字,以及数据长度可能不同。这些差异可以用工具自动解决。

 

   

 

    上图是解决编译差异和平台差异的示意界面。工具可以自动屏蔽特殊关键字或代码片断,也可以转定义关键字来解决数据长度差异。这样,在不修改产品代码的前提下,可以比较便利地解决编译差异和平台差异。

 

“可编程的桩”不能解决内部输入
    也许很多人都会认为,编写桩代码可以解决内部输入问题,内部输入有六种情形,我们来具体分析一下。

 

   

 

    自然输入:自然输入调用实际代码,是不需要特别解决的,跟桩无关。
    不可控:不可控调用的也是实际代码,不是调用桩代码,因此也不能解决。也许有人会问,另外编写桩代码来代替实际代码行不行?在应该调用实际代码的时候,要想调用桩代码可能是很麻烦的,例如,底层函数位于同一个文件,或同一个类,如何去调用桩?
    难于初始化:也是调用实际代码。
    静态输入:静态输入只涉及到局部静态变量,没有调用底层函数,当然也不能用桩来代替。
    中断输入:中断输入在不确定位置,中断调用不确定的代码,也不能用桩来代替。
    失真:失真是打桩造成的,调用的是桩代码。编写桩代码可以解决一部分失真,我们来具体分析一下。在比较简单的情形下,可以用命名法来控制桩代码的输出,即给每个用例命名,桩代码中判断用例名来决定输出,VU2.0最初的设计就是采用这种方法,但是经过应用,
发现只能解决简单情形,所以放弃了。如果在同一个用例中,多次调用同一个桩,每次要求输出不同,命名法就无效了。这种情形是很常见的,例如在循环中调用桩代码。一个被测函数可能调用多个桩,一个桩又可能被多个被测函数调用,这种多对多的关系下,很难维护用例与桩的对应。用例可能很多,还可能要不断增加和修改,维护用例与桩输出的关系也很麻烦。

 

    从前面的分析来看,即使不考虑工作量的增加,编写桩代码也只能解决一小部分失真,很难适应复杂的应用,也无法解决不可控、难于初始化、静态输入、中断输入,因此,编写桩代码不能解决内部输入问题。

 

    模拟对象(Mock Object)是解决内部输入的一种方法,不过对于C++,尤其是C,这种方法比较麻烦,增加大量的额外工作,也不能解决静态输入、中断输入、难于实始化。比较便利的方式是底层模拟。模拟对象的工作原理是替换对象,底层模拟的工作方式是控制函数,即直接控制函数的行为,对C和C++同样适用。对于静态输入和中断输入,由于没有函数调用或函数调用不确定,可以用自动修改被测试代码的方式调用自动生成的函数来实现控制(测试时应使用的拷贝的产品代码,这样不会破坏实际的产品代码)。

    

   

 

     底层模拟的实现技术比较复杂,这里就不详细介绍了,只介绍实际应用。不可控、失真、难于初始化都是调用底层函数形成的,可以用同样方法解决。上图是底层模拟的应用示意,不需要编程,可以设定底层函数的返回值,输出参数,底层函数所改写的全局变量或成员变量的值,支持任意复杂类型。同一用例多次调用同一底层函数,每次可以输出不同,用逗号分隔就可以了。不管底层函数调用的是实际代码还是桩代码,底层模拟都是有效的,也就是说,如果一个用例不设定底层模拟的话,就调用实际代码或桩代码,桩代码也是可以修改的,只不过通常不需要修改。

     

   

 

    静态输入:上图是模拟静态输入的界面,一般不用填写什么,直接确定就行了,然后每个用例就可以为局部静态变量设定不同值了。

    

   

   

    中断输入:上图是模拟中断输入的界面,可在被测代码的任意位置,模拟中断造成对某个全局变量的修改。只要填写可能被中断修改,并会影响被测程序的功能逻辑的全局变量就行了。

   

   

 

    内部输入问题解决之后,内部输入就可以像参数一样,在用例中直接设定,例如,在上图所示的例子中,可以在用例中直接设定环境的温度,这样,我们就可以检测程序是否对各种环境温度都做了合适的判断和处理。从类别上来说,底层函数可能是操作系统API,支持库的函数,其他模块的接口函数,或者是本模块的其他函数。从状态上来说,底层函数可能未实现,也就是说不存在,或者被隔离用桩来代替,或者有错误,或者难于初始化,当然也可能是普通调用。内部输入问题解决之后,无论底层函数是什么样的,我们都可以完整测试代码的功能逻辑,做到不管底层函数是怎么样的,被测函数都对各种输入,包括外部和内部输入,都做了合适判断和处理,这样就保证被测函数本身是没有错的。

 

    在内部输入问题解决之后,嵌入式代码就可以在PC上进行可信的测试。单元测试是在与其他代码和相关系统,当然也包括平台,隔离的前提下,检测代码单元的功能逻辑,这些功能逻辑是与平台无关的。在PC上测试嵌入式项目,效率要比在目标机或模拟器上测试高得多,因为不需要反复的上传下载。跨平台测试嵌入式项目的关键在于嵌入式API,这些API可能无法调用实际代码。跟功能逻辑无关的API,也就是说,没有产生会影响功能逻辑的数据的API,可以不必处置。对于影响功能逻辑的API,则可以用内部输入的方式处置。

对于嵌入式项目,测试的关键仍然在于内部输入,如果内部输入问题无法解决,则无论在PC上还是目标机或模拟器上,都难于测试,解决了内部输入,在哪里都可以测试。

抱歉!评论已关闭.