多年来,在航空电子工业领域,覆盖率分析技术已经在复杂的软件控制系统错误检测方面取得了非常有效的业绩。现在,汽车工业也在考虑在软件控制方面的测试质量和不断增加的费用之间选择。如果能够借鉴航空电子工业领域中的经验和教训将是一种明智的选择。
有很多不同类型和级别的覆盖率。经常被提及的术语是函数覆盖率,这是一个用来报告你是否调用过每一个函数的度量标准。这种覆盖率有助于在测试的初级阶段确保软件的所有部分达到一些基本的覆盖率和消除一些明显的不足。
语句覆盖——也称为线性覆盖、段覆盖或者基本块覆盖,它可用来说明每一个可执行语句是否被执行,基本块覆盖和语句覆盖是一致的,被测的代码单元是没有分支的序列。这大大的扩充了覆盖图的范围,虽然仅仅是平面图。
语句覆盖是最容易达到100%的覆盖度量指标。它能覆盖整个源代码并且帮助用户发现存在于程序中很少运行到的代码段中的错误。虽然它相对来说容易达到100%,不需要花费昂贵的费用和资源,但是它仍然能够显著的增强我们在源代码的正确性方面的信心。然而,简单的实现100%的语句
覆盖率意味者仍然有通过这些语句的潜在
的路径没有被执行,语句覆盖不能测量这些路径。
语句覆盖是不能够辨认很多的控制结构。例如,下面的这段C/C++代码,在没有一个测试用例使condition变量成为false的情况下,语句覆盖就认为这个代码被充分覆盖了;实际上,如果一旦condition为false,这个代码将出错。这是语句覆盖的一个非常严重的缺点。而If语句的使用是十分普遍的。
int* p = NULL;
if (condition)
p = &variable;
*p = 123;
语句覆盖也不能告诉我们循环是否在终止时退出,它仅仅说明执行了循环体。因为像do-while这样的循环会首先执行循环体一次,语句覆盖认为它们是没有分支的语句。
语句覆盖率也忽略逻辑操作符(||和$$)。而且,语句覆盖也不能辨认连续的开关语句标号。为了达到更加完整和精确的覆盖,至少应用二维覆盖图,显然需要分解判定条件。
if(condition1&&(condition2|| function1()))
statement1;
else
statement2;
在除安全苛刻性应用系统以外的其它所有情况下,分析单个的条件将可以有效的涵盖其它的覆盖所能达到的状况。考虑一下在真实测试中使用“白盒测试”技术,在错误预防方面所能带来的益处。
如何到达充分的覆盖
在我们以加快上市时间、快速改善产品品质为目的进行生产力提升的同时,怎样才能进行彻底的覆盖呢?
通常不需要太多的努力便能实现语句覆盖和分支覆盖的一致(尽管不可行分支和代码可能没被发现),但是路径测试覆盖率经常会滞后于语句和分支覆盖率,这是因为需要有苛刻的测试策略才能实现路径测试覆盖的最大化。
如果,路径测试覆盖能达到一致,这将大大的减少保留在程序中的未发现的错误。最大限度的路径测试覆盖对于一个程序的测试是比较彻底的,而且路径测试覆盖非常擅长发现存在于循环结构中的错误。实行对每一个语句的覆盖并不需要实现对整个循环的覆盖——而只需要一个直接贯穿的路径。对每个分支的测试能保证循环至少被执行了一次,但是如果要对每个路径进行测试却需要循环至少被执行两次。对于高可靠性代码,推荐路径测试覆盖的最大化。
显然在提高软件质量和可靠性方面,路径测试覆盖技术是最有效的。这样最大限度的路径测试覆盖将降低其它相关覆盖测试的成本。
越来越多的软件开发者在转向商业的软件测试工具来协助复杂的测试过程。有些软件也集成了规则分析和其它的静态分析技术来进行单元级(单个函数)到子系统级以及系统级(多文件)测试。
需要覆盖率工具提供什么功能呢?
1. 能进行全面地覆盖分析(最好能识别不可行代码和不可达代码)。
2. 能从单元级开始向上辅助自动测试用例/向量生成工具进行覆盖分析。
3. 能提供直观的可视化的报告以便于工程分析和指导。
结合工具的使用,开发者考虑按照下面的步骤将能更高效、低成本的到达覆盖分析的目的:
第一步:根据对被测程序的期望实现功能的了解来构造最有可能的功能测试。这些应该从软件需求说明书、软件规格或用户文档获得。在加载测试数据的情况下,应该能够通过测试工具监控源代码的执行。当实现功能测试的数据用完后,检查覆盖率来发现程序中的哪些部分仍然没有被测到。应该进一步的构造测试数据并进行执行、分析。覆盖率会随着每一组测试数据的执行而累加并且每一组测试数据都会显示执行了程序中的哪些部分。
上面的过程应该一直持续到进行功能测试的数据被执行完或者达到所需的测试度量标准。如果是前一种情况(测试数据执行完)那么进行第二步;否则的话(达到测试度量标准的要求)那么任务就完成了。
第二步:检查测试覆盖度量指标。如果语句覆盖率没有达到完全覆盖(也就是说不是每条语句都被执行了),那么可能是由于某个特殊的用例运行失败,错误退出,等造成的。由于这些可能性的存在,需要对执行历史剖面进行累积,因为为了将代码中的每一行都执行到,将程序执行多次通常是必须的。通常功能测试只能覆盖程序中40%到60%的可执行语句。
当语句覆盖达到完全,每一条语句都被执行时,便可进行第三步。
第三步:检查所有没有被执行的分支。通常这些分支中的一部分可以通过构造专门的用例实现对它们的测试。因为为找出程序中未执行分支而进行的程序分析和为了找出程序中的未执行路径所要做得分析工作很相似,所以在将分支测试这种策略充分实施后再进行第四步——测试路径覆盖,将更加节约成本。
第四步:有些没有执行到的分支和路径,是由于相关的测试用例只有在程序执行出错或者计算出错的错误状态下才能实现。这些通常是和程序的冗余保护设计相关,这样的路径应该完整的保留。
最后:
在进一步的检查后会发现,通常很多没有被执行的路径都是不可行的,也就是说,在使用任何测试数据的情况下这些路径都不可能被执行到。这意味着代码的相关部分应该重新写,因为这些代码要么是无效的要么是不正确的。此外,当这些代码重写后,和这些代码相关的其它一些测试路径可能也会被去掉。如果是由于上面已知的原因,导致测试路径不能被执行到,那么即使没有达到覆盖率标准,这些测试路径也是可以忽略不计的。
一旦不可行测试路径被去掉,那么源代码将会更加有效、健壮并且占用更少的空间。