单元测试的效益如此之高,为什么真正成功实施的单位却很少?因为很多看起来非常吸引人的方法或技术,只能应用于简单独立的代码,无法适应复杂项目,华而不实。单元测试工具必须“专而精”,才能在有限的时间内顺利完成测试。“专而精”表现在:深入复杂应用,发现并解决阻碍测试工作各种难题。在介绍Visual Unit 2之前,我们先探讨一下单元测试会遇到哪些难题:
可测性问题:如果测试无法进行,就归引咎于代码可测性差,要求改进开发流程,提高可测性,对吗?代码可测性差有其必然性,程序是客观事物的反映,客观事物本身互相关联、互相纠缠,代码间的多数耦合不可能消除,解决可测性问题是单元测试工具的首要职责。试图通过优化开发流程来提高可测性是不现实的,优化流程是一个长期、渐进的过程,不可能短期内实现飞跃,更不可能解决客观事物间的“耦合”。
可编程的桩:桩代码通常是脚本或源代码,当然是“可编程的桩”,但应用中会发现,编写桩代码并不容易,例如,子函数通过参数传递输出,要求第一个用例输出1,第二个用例输出2,第三个用例输出3,这是最简单和常见的应用,如何实现桩输出与用例的对应关系?VU2最初的设计是:用户可以给用例命名,桩代码中判断用例名来决定输出,但发现根本无法适应复杂项目,因为:一个桩可能被同一函数多次调用,每次要求输出不同;一个桩可能被多个函数调用;一个函数又可能调用多个桩;一个函数可能需要十几个用例,随时可能补充和修改用例。要维护用例名和桩行为的对应关系,无疑是一场噩梦。
白盒覆盖:工具统计出覆盖率就算完成任务了吗?不!如何实现覆盖才是大问题。传统的白盒方法(如用于语句覆盖的基路径法,用于MC/DC的真值表法)效率太低,工具如果不能提供快速设计白盒用例的方法,实现高覆盖根本做不到,仅仅统计出一些数据又有多少实际意义?
测试效果:如何保证测试效果?有了覆盖率就可以了吗?实践证明:
1)白盒覆盖基于现有代码,再高的覆盖率也不能发现代码缺失错误;
2)用例必须反映程序的功能,测试才有价值,单纯追求覆盖率容易导致“跟着代码走”,使测试失去意义;
3)程序员和测试员都容易遗漏边界、非法输入,这种错误常常导致程序崩溃。因此,测试工具不仅应能统计覆盖率,还应将数据集中,便于人工根据设计功能来检查,并且自动生成边界用例检查非法和边界输入。
还有很多问题可能使测试工作“卡住”,例如:
局部静态变量需要每个用例设定不同值,但却无法在用例中访问;
程序未把计算好的数据输出(如通讯程序中计算好的报文直接发送出去),如何判断计算结果是否正确?
底层函数的输出难于控制(如取环境温度的函数,需要即时返回各种不同温度);
嵌入式代码,中断可能在某个时刻改变了某个全局变量;
层函数的输入可能很复杂,难于初始化。
上述问题如果无法解决,测试工作必然会遇到大量的困难,要么付出巨大成本,要么被迫搁置。经过长期的实践和技术攻关,“十年磨一剑”, VU2已成功解决了这些难题,并已在众多企业和尖端行业成功应用。
VU2具有:自动解决可测性问题、高效建立和管理测试用例、超过航空标准的覆盖准则、高效完成白盒覆盖、黑盒/白盒/自动相结合实现彻底测试、方便的回归测试、快速排查错误、支持可视编程、完整的统计、易于浏览的报告等“雪中送炭”式功能,使单元测试可行、易行、高效率、高效益。
一、自动解决可测性问题
“可测”,包括两方面含义:测试任务能够独立运行,代码功能逻辑能够完整覆盖。“可测”的关键难点在于内部输入,即底层函数产生的数据,以及静态输入和中断输入。底层函数可能不存在、无法调用、不可控、打桩造成失真、难于初始化,只有便利地模拟底层函数的各种行为,并在用例中控制局部静态变量和模拟中断造成的全局变量修改,才能完整测试函数的功能逻辑。
1、自动将测试任务分离出来,实现独立测试
VU2可以一次性将一位测试人员的测试任务隔离出来,自动生成驱动代码和桩代码。下图为设置测试任务的界面,需测试的文件设为T,不测试但可能调用的文件设为N,其他文件自动打桩隔离。
2、自动解决嵌入式项目在PC上测试的难题
单元测试产生海量数据,且需要频繁高效执行,在目标机或模拟器上执行是低效和困难的。单元测试的目的是在隔离其他代码和底层系统的前提下,检测代码单元尤其是算法密集的代码单元的功能逻辑,这些功能逻辑在各种编译环境和各种平台上都是一样的,因此,嵌入项目的单元测试适于在PC上进行,但需要解决特殊关键字、数据长度不同等编译差异和平台差异,以及对底层调用包括嵌入式操作系统API的模拟,VU可以便利地解决这些问题。下图为自动解决编译差异和平台差异的界面,对底层调用的模拟将在后面说明。
3、无需编程的底层模拟,实现完整功能逻辑覆盖
测试时无需关注底层函数本身如何工作,但需测试对底层函数的各种输出是否做了合适处理。下图是VU2底层模拟器,无需编程,即可随意设定底层函数的返回值、输出参数、所改写的全局变量和成员变量,支持任意数据类型、支持多次调用同一子函数每次输出不同、支持自动判断调用次数。
底层模拟器用于解决底层函数的不可控、失真、难于初始化等问题,也解决局部静态变量的用例中控制,使复杂耦合的代码也易以实现完整的功能逻辑覆盖。底层模拟也用于模拟嵌入式操作系统的API,以及模拟中断对全局变量的修改,使嵌入式项目中在PC上高效实现可信的测试。
二、高效建立和管理测试用例
1、自动生成用例代码
支持人工及时介入,避免生成大量垃圾。对于复杂数据类型,只有被测函数真正读取的域才是输入,真正改写的域才是输出,人工及时介入可以指定真正需要的数据,忽略其他无关数据,以免生成垃圾及避免遗漏。下图为指定用例相关输入输出的界面。
下图为生成后的代码。测试代码可编辑,特殊情形可以手工添加代码,适应各种复杂应用。
2、自动将用例数据移到表格中
实现代码和数据分离,高效建立和管理众多用例。下图为用例数据表格。
3、根据设定值及组合自动生成用例
可以将每个参数或其他输入的正常值、边界值、非法值列出来,自动生成用例。数据可以另存和导入,方便重用。此功能将数据分类集中,也便于人工检查数据的完整性。如下图:
也可以根据有效范围、分段点、一些选项自动生成数据,如下图:
三、超过航空标准的覆盖准则、高效完成覆盖
1、支持MC/DC,路径等六种覆盖率
自动统计语句覆盖、条件(值)覆盖、分支覆盖、判定条件覆盖(C/DC)、修正判定条件覆盖(MC/DC),路径覆盖。MC/DC是DO-178B Level A认证标准中规定的,欧美民用航空器强制要求遵守该标准。MC/DC的核心意思是每个条件均须独立影响判定结果,侧重于检查判定中的逻辑操作符(&&、||)的正确性,不保证检查从函数入口到出口的代码组合的正确性,不足相当明显。路径覆盖检查从函数入口到出口的各种代码组合是否得于覆盖,MC/DC和路径覆盖的组合才是最高强度的覆盖。
2、未覆盖逻辑单位的标示清晰简明
如下图,代码中,未覆盖语句为红色字体并带淡红色背景,条件前的[T]表示真值未覆盖,[F]表示假值未覆盖,[M]表示MC/DC未覆盖(即该条件未独立影响判定结果)。逻辑结构图中,未覆盖分支带淡红色背景,未覆盖路径用红色画出。C/DC是判定与条件的组合,由条件和分支间接表示。
3、独创的用例设计器,高效完成白盒覆盖
仅提供覆盖统计是远远不够的,重要的是如何完成覆盖?VU的用例设计器提供了高效方法:自动从现有用例中计算出近似用例,并提出修改提示,按提示修改即可建立预期用例。新用例又可能成为下一逻辑单位的近似用例,使每次修改工作量最小。下图中,只需接提示将A改为大于1数即可。
MC/DC受到一些质疑,原因是太难完成了,但使用VU的用例设计器,完成如下超复杂判定的MC/DC只需要三分钟:
A && ( (B || C) && (D && E) ) || ( (F || G) && (H || I || J) || K )