作者简介:司艳艳 (1979-),女,中国空空导弹研究院工程师,研究方向为软件测试。
0引言
随着计算机应用范围的日益广泛,软件系统作为计算机系统的神经中枢,也早已延伸到了产品中的各个方面。为了能够适应各种复杂的背景环境和完成复杂的任务,近年来软件系统的应用规模、复杂度以及重要性程度均呈急剧上升趋势。
随着软件应用范围和规模的不断扩大,软件设计的复杂程度不断提高,软件开发中出现错误或缺陷的机会越来越多。同时,人们对软件质量的要求也越来越高,要保证产品的质量并确保其有效性,提高软件的质量与可靠性是最关键的一个途径,这已成为航空、航天等领域的共识。此时,作为软件质量保证手段之一的软件测试越来越受到重视,对其要求也越来越高。因此,要根据软件故障产生和发展的规律,检测出软件存在的故障和可能存在的隐患,控制和减少软件故障所造成的影响,构建一个可以支撑高质量、高可靠性软件研制的技术保障体系,从而全面提高软件系统在产品关键应用中的可靠性和可用性。
1工程实例
1.1测试过程
笔者所在院系的软件产品通常要经历4个阶段的开发研制,在软件开发的每个阶段,软件内部测试都要进行以下几个方面的测试工作:静态分析、代码审查、单元测试、部件测试、配置项测试。
(1)静态分析。软件静态分析主要是通过专业软件静态分析工具对程序结构、数据结构、代码品质等在非运行状态下进行分析,提取软件大量的静态内部信息,为代码审查及动态测试提供辅助参考信息,依据现有的度量模型定量评价软件的内在质量[1]。静态分析中需关注的指标有圈复杂度、基本复杂度、扇出数和模块行数。
(2)代码审查。代码审查主要是检查代码和设计的一致性;检查代码执行标准的情况;检查代码的可读性;检查代码逻辑表达的正确性和完整性;检查代码结构的合理性等。
(3)单元测试。单元测试的依据是软件详细设计说明文档,单元测试对模块内所有的控制路径设计测试用例,针对被测单元生成驱动模块,将被测单元需调用的其它函数打桩,模拟实现被测单元的接口,执行插桩后的测试用例,得到最终的测试结果。单元测试的目的是检查每个软件模块能否正确地实现设计说明中的功能、性能、接口和其它设计约束等要求,并发现模块内可能存在的各种错误。
(4)部件测试。部件测试中,根据测试计划和被测试软件的设计文档,采用自顶向下或自底向上的模式把各个模块逐步装配成所需的功能模块并进行测试,部件测试对部件内所有的接口设计测试用例,针对被测部件生成驱动,将被测部件需调用的函数真实调用,实现被测单元的真实接口,执行测试用例,得到最终的测试结果。部件测试的目的是检验软件单元和软件部件之间的接口关系,并验证软件部件是否符合设计要求。
(5)配置项测试。软件配置项是为独立的配置管理而设计的并且能满足最终用户功能的一组软件。在这种测试中,首先根据测试计划和被测试软件的设计文档,细化分解软件需求,形成测试需求,然后再针对每一个测试需求,采用功能分解法、边界值分析法、错误猜测法、程序插桩法、等价类划分法等方法逐一设计测试用例,最后在实际测试环境中运行测试用例并取得测试结果,并将实际结果与预期结果进行比较分析,根据一致性原则判定测试用例是否通过。软件配置项测试的目的是检验软件配置项与软件需求规格说明的一致性。
1.2问题现象
某产品软件到了后期阶段仍在进行频繁更改,通过对其分析,得出软件复杂度高是其存在的主要问题。
GJB/Z 102-97《软件可靠性和安全性设计准则》中对软件编程要求做了如下规定:
(1)除中断情形外,模块应使用单入口和单出口的控制结构。
(2)在设计软件时,将模块在逻辑上构成分层次的结构,在不同的层次上可有不同的扇入扇出数。模块的实际结构形态应满足下述准则:①模块的扇出一般应控制在7以下;②为避免某些程序代码的重复,可适当增加模块的扇入;③应使高层模块有较高的扇出,低层模块有较高的扇入。
(3)软件单元的圈复杂度(即McCabe 指数)应小于10。
(4)对于用高级语言实现的软件单元,每个软件单元的源代码最多不应超过200行,一般不超过60行。
据统计,某产品软件某版本总模块数251个,行数超标的模块数有11个,基本复杂度超标的模块数有15个,圈复杂度超标的模块数有87个,占到了总模块数的34.7%,最高的圈复杂度达到了89,超出了规定值的8倍,软件中频繁更改的模块大多是复杂度比较高的模块。
1.3问题分析
在软件经过几个阶段的测试之后,所发现的软件错误和缺陷均已得到了更正,但静态分析结果中的软件圈复杂度、基本复杂度等指标超高的现象一直没有被重视,到了后期软件越来越复杂导致软件潜在风险发生。
软件复杂度是衡量软件质量的一个因素,圈复杂度大说明程序代码质量低且难于测试和维护。经验表明,程序的可能错误和高的圈复杂度有着很大关系,基本复杂度高意味着程序模块的结构“不够良好”,难以理解和模块化,软件质量的可维护性低。从图1和图2中可以看出,复杂度越高,内部结构越复杂,其潜在的出错可能性也就越大。
2测试指导设计
2.1软件质量的pareto原理
pareto原理[2] 指出,20%的软件模块包含了软件中80%的缺陷,20%的软件改进,需花费80%的适应性维护费用。通过软件测试问题分析可以看出,少部分的高复杂度模块引起了大部分的软件错误,且经常出错的模块改错后还会经常出错。复杂度超标问题修正越
晚,修正的难度越大,并且会出现修改了一个错误,却引出了更多错误的情况,或是修改了有缺陷的代码,却又导致先前正确的功能模块出现错误等。因此,必须在发现软件复杂度超标的早期阶段就及时进行修正。
2.2降低软件圈复杂度
2.2.1圈复杂度定义
在软件测试的概念里,圈复杂度用来衡量一个模块判定结构的复杂程度,数量上表现为独立线性路径条数,即合理的预防错误所需测试的最少路径条数。1976年Thomas McCabe提出了圈复杂度(Cyclomatic Complexity)的概念,依据圈复杂度定义了软件的复杂性。1977年Halstead提出了软件科学复杂度度量。文献[3]通过实验证实
了嵌入式软件错误的位置与软件的复杂度紧密相关,并且建立了动态复杂度模型,提出通过软件复杂度度量可以识别程序中哪些代码存在错误倾向。研究表明,软件的缺陷不是随机地分布在软件中,软件缺陷的存在与软件独特的、可度量的特性有关[4]。
2.2.2复杂度计算方法
C语言常用的软件模块逻辑结构(结构流图)有如下几种,如图3所示。
2.2.3降低圈复杂度
由圈复杂度计算方法可以看出,在圈复杂度超过标准需要降低的情况下,可以将复杂的判断条件提前计算,保证出现在if语句中的判断条件的单一性。或是将重复代码或相似代码提取为一个新的函数,将过长的函数按功能拆分成小的函数。
2.3降低软件基本复杂度
2.3.1基本复杂度定义
基本复杂度ev(G)为软件通过结构化(用单一结点替换控制流图中的基本逻辑结构)流程简化后,再计算出的圈复杂度。基本复杂度计算方法为先画出结构流图,然后将其中的基本结构替换为一个结点,全部基本结构替换完毕后计算基本复杂度。结构流图的简化过程如图9所示。
2.3.2降低基本复杂度
只要语句中运用的为“正常”的编程语句使程序结构保持单入口、单出口,无goto、break等异常跳转语句,那么基本复杂度应该都为1。
如果程序中出现了非结构化的语句,比如多入口、多出口等,则会导致最终结构化完成后的基本复杂度不为1,如图10所示的几种结构。
由以上分析可知,只要在编程中应用正常的基本结构(主要特点为单入口、单出口),减少break、goto等非正常出口、入口语句,就可以控制基本复杂度。
2.4降低软件扇出数
一个过程/函数调用其它过程/函数的个数,称为该模块的扇出。扇出过小会增加程序结构深度,过大会增加程序结构宽度,并且导致过程或函数复杂性高。所以扇出最好为3或4个,最高不超过7个。
一个过程/函数被其它过程/函数调用的个数,称为该模块的扇入。扇入越大表明模块通用性越好,但扇入过大则程序的聚合性会变差。如果发现某个模块的扇入、扇出过大,可考虑重新分解调整。
静态分析的复杂度结果可以预测软件潜在错误的比率,可以指出过于复杂需要分解的模块,可以预测维护代码和分接代码所需的工作量,还可以揭示代码的质量。这些结果除了可以指导测试过程外,还可以用于指导软件开发,使软件复杂度从早期阶段就得到控制,避免软件中的错误倾向。在后续其它产品的软件开发和测试过程中,静态分析结果指导设计得到了很好的效果。
3结语
本文通过软件测试问题分析研究了通过测试结果指导软件设计的方法,以提高软件的质量和可靠性。软件静态分析可以度量软件的基本质量属性,但目前其中的复杂度信息往往得不到设计人员的重视。某产品软件测试实践结果表明,静态分析的结果能从软件内部结构信息层面帮助设计人员开展代码优化工作,在软件早期阶段有意识地对复杂度等质量指标进行限制,后期就能避免软件复杂度过高带来的风险,从而提高软件的质量和可靠性。
参考文献:
[1]尹平,许聚常,张慧颖.软件测试与软件质量评价[M].北京:国防工业出版社,2008.
[2]SCHULMEYER G G.软件质量保证[M].北京: 机械工业出版社,2003.
.Information and Software Technology,1996(2).
.IEEE Journal on Selected Areas in Communications,1990.