《软件测试的艺术》原书第三版 - 第三章 - 代码检查、走查与评审

发布时间 2023-08-03 18:31:19作者: ^=

第三章 代码检查、走查与评审

发现了一句有趣的话:

从内部产生的压力似乎会急剧增长,并产生一个趋势,要“尽可能快地修正这个缺陷”。由于这些压力的存在,程序员在改正某个由基于计算机测试发现的错误时所犯的失误,要比改正早期发现的问题时所犯的失误更多一些。

太紧张了?

代码检查与走查

代码检查与走查是对过去桌面检查过程(在提交测试前由程序员阅读自己程序的过程)的改进。与原方法相比,代码检查与走查更为有效,同样是因为在实施过程中,除了软件编写者本人,还有其他人参与进来。

代码走查的另一个优点在于,一旦发现错误,通常就能在代码中对其进行精确定位,这就降低了调试(错误修正)的成本。

代码检查小组

一个代码检查小组通常由四人组成,其中一人发挥着协调作用。

协调人应该是个称职的程序员,但不是该程序的编码人员,不需要对程序的细节了解得很清楚。

协调人的职责包括以下几点:

  • 为代码检查分发材料、安排进程。

  • 在代码检查中起主导作用。·记录发现的所有错误。

  • 确保所有错误随后得到改正。

第二个小组成员是代码的作者。小组中的其他成员通常是程序的设计人员(如果设计人员不同于编码人员的话),以及一名测试专家。这

名测试专家应该具备较高的软件测试造诣并熟悉大部分的常见编码错误,下文会就这些常见编码错误进行讨论。

对事不对人,和人有关的注意事项

正确的做法是,程序员必须怀着非自我本位的态度来对待检查过程,对整个过程采取积极和建设性的态度:代码检查的目标是发现程序中的错误,从而改进软件的质量。正因为这个原因,大多数人建议应对代码检查的结果进行保密,仅限于参与者范围内部。尤其是如果管理人员想利用代码检查的结果,那么就与检查过程的目的背道而驰了。

"如果管理人员想利用代码检查的结果",用来评分考核么??

用于代码检查的错误列表

代码检查过程的一个重要部分就是对照一份错误列表,来检查程序是否存在常见错误。遗憾的是,有些错误列表更多地注重编程风格而不是错误(例如,“注释是否准确且有意义?”,“if-else代码段和do-while代码段是否缩进对齐?”),错误检查太过模糊而实际上没有用(例如,“代码是否满足设计需求?”)。

  • 数据引用错误
    • 是否有引用变量未赋值或未初始化(目前大部分IDE会有提示)
    • 对于所有的数组引用,是否有越界
    • 数组的引用下标的值是否都是整数.....
    • 对于所有的通过指针或引用变量的引用,当前引用的内存单元是否分配?这就是所谓的“虚调用”(dangling reference)错误
    • 如果一个内存区域具有不同属性的别名,当通过别名进行引用时,内存区域中的数据值是否具有正确的属性?
    • 变量值的类型或属性是否与编译器所预期的一致?
    • 在使用的计算机上,当内存分配的单元小于内存可寻址的单元大小时,是否存在直接或间接的寻址错误?
    • 当使用指针或引用变量时,被引用的内存的属性是否与编译器所预期的一致?这种错误的一个例子是,当一个指向某个数据结构的C++指针,被赋值为另外的数据结构的地址。
    • 假如一个数据结构在多个过程或子程序中被引用,那么每个过程或子程序对该结构的定义是否都相同?
    • 如果字符串有索引,当对数组进行索引操作或下标引用,字符串的边界取值是否有“仅差一个”(off-by-one)的错误?
    • 对于面向对象的语言,是否所有的继承需求都在实现类中得到了满足?
  • 数据声明错误
    • 是否所有的变量都进行了明确的声明?
    • 如果变量所有的属性在声明中没有明确说明,那么默认的属性能否被正确理解?
    • 如果变量在声明语句中被初始化,那么它的初始化是否正确?
    • 是否每个变量都被赋予了正确的长度和数据类型?
    • 变量的初始化是否与其存储空间的类型一致
    • 是否存在着相似名称的变量(如VOLT和VOLTS)?这种情况不一定是错误,但应被视为警告,这些名称可能会在程序中发生混淆。
  • 运算错误
    • 是否存在不一致的数据类型(如非算术类型)的变量间的运算?
    • 是否有混合模式的运算?例如,将浮点变量与一个整型变量做加法运算。
    • 是否有相同数据类型、不同字长变量间的运算?
    • 赋值语句的目标变量的数据类型是否小于右边表达式的数据类型或结果?
    • 在表达式的运算中是否存在表达式向上或向下溢出的情况?也就是说,最终的结果看起来是个有效值,但中间结果对于编程语言的数据类型可能过大或过小。
    • 除法运算中的除数是否可能为0?
    • 如果计算机表达变量的基本方式是基于二进制的,那么运算结果是否不精确?也就是说,在一个二进制计算机上,10×0.1很少会等于1.0。
    • 在特定场合,变量的值是否超出了有意义的范围?例如,对变量PROBABILITY赋值的语句可能需要进行检查,保证赋值始终为正且不大于1.0。
    • 对于包含一个以上操作符的表达式,赋值顺序和操作符的优先顺序是否正确?
    • 整数的运算是否有使用不当的情况,尤其是除法?举例来说,如果i是一个整型变量,表达式2*i/2==i是否成立,取决于i是奇数还是偶数,或是先运算乘法,还是先运算除法。
  • 比较错误
    • 是否有不同数据类型的变量之间的比较运算,例如,将字符串与地址、日期或数字相比较?
    • 是否有混合模式的比较运算,或不同长度的变量间的比较运算?如果有,应确保程序能正确理解转换规则。
    • 比较运算符是否正确?程序员经常混淆“至多”、“至少”、“大于”、“不小于”、“小于”和“等于”等比较关系。
    • 每个布尔表达式所叙述的内容是否都正确?在编写涉及“与”、“或”或“非”的表达式时,程序员经常犯错。
    • 布尔运算符的操作数是否是布尔类型的?比较运算符和布尔运算符是否错误地混在了一起?
    • 在二进制的计算机上,是否有用二进制表示的小数或浮点数的比较运算?由于四舍五入,以及用二进制表示十进制数的近似度,这往往是错误的根源。
    • 对于那些包含一个以上布尔运算符的表达式,赋值顺序以及运算符的优先顺序是否正确?
    • 编译器计算布尔表达式的方式是否会对程序产生影响?(短路运算)
  • 控制流程错误
    • 如果程序包含多条分支路径,比如有计算GO TO语句,索引变量的值是否会大于可能的分支数量?
    • 是否所有的循环最终都终止了?
    • 程序、模块或子程序是否最终都终止了?
    • 由于实际情况没有满足循环的入口条件,循环体是否有可能从未执行过?
    • 如果循环同时由迭代变量和一个布尔条件所控制(如一个搜索循环),如果循环越界(fall-through)了,后果会如何?
    • 是否存在“仅差一个”的错误,如迭代数量恰恰多一次或少一次?
    • 如果编程语言中有语句组或代码块的概念(例如do-while或{……}),是否每一组语句都有一个明确的while语句,并且do语句也与其相应的语句组对应?或者,是否每一个左括号都对应有一个右括号?目前的大多数编译器都能识别出这些不匹配的情况。
    • 是否存在不能穷尽的判断?举例来说,如果一个输入参数的预期值是1,2或3,当参数值不为1或2时,在逻辑上是否假设了参数必定为3?如果是这样的话,这种假设是否有效?
  • 接口错误
    • 被调用模块接收到的形参(parameter)数量是否等于调用模块发送的实参(argument)数量?另外,顺序是否正确?
    • 实参的属性(如数据类型和大小)是否与相应形参的属性相匹配?
    • 实参的量纲是否与对应形参的量纲相匹配?举例来说,是否形参以度为单位而实参以弧度为单位?
    • 此模块传递给彼模块的实参数量,是否等于彼模块期望的形参数量?
    • 此模块传递给彼模块的实参的属性,是否与彼模块相应形参的属性相匹配?
    • 此模块传递给彼模块的实参的量纲,是否与彼模块相应形参的量纲相匹配?
    • 如果调用了内置函数,实参的数量、属性、顺序是否正确?
    • 如果某个模块或类有多个入口点,是否引用了与当前入口点无关的形参?
    • 是否有子程序改变了某个原本仅为输入值的形参?
    • 如果存在全局变量,在所有引用它们的模块中,它们的定义和属性是否相同?
    • 常数是否以实参形式传递过?
  • 输入输出错误
    • 如果对文件明确声明过,其属性是否正确?
    • 打开文件的语句中各项属性的设置是否正确?
    • 格式规范是否与I/O语句中的信息相吻合?
    • 是否有足够的可用内存空间,来保留程序将读取的文件?
    • 是否所有的文件在使用之前都打开了?
    • 是否所有的文件在使用之后都关闭了
    • 是否判断文件结束的条件,并正确处理?
    • 对I/O出错情况处理是否正确?
    • 任何打印或显示的文本信息中是否存在拼写或语法错误?
    • 程序是否正确处理了类似于“File Not Found”这样的错误?
  • 其他检查
    • 如果编译器建立了一个标识符交叉引用列表,那么对该列表进行检查,查看是否有变量从未引用过,或仅被引用过一次。
    • 如果编译器建立了一个属性列表,那么对每个变量的属性进行检查,确保没有赋予过不希望的默认属性值。
    • 如果程序编译通过了,但计算机提供了一个或多个“警告”或“提示”信息,应对此逐一进行认真检查。“警告”信息指出编译器对程序某些操作的正确性有所怀疑;所有这些疑问都应进行检查。“提示”信息可能会罗列出没有声明的变量,或者是不利于代码优化的用法。
    • 程序或模块是否具有足够的鲁棒性?也就是说,它是否对其输入的合法性进行了检查?
    • 程序是否遗漏了某个功能?
      img
      img
      img

代码走查

代码走查与代码检查很相似,都是以小组为单位进行代码阅读,是一系列规程和错误检查技术的集合。

走查的过程与检查大体相同,但是规程稍微有所不同,采用的错误检查技术也不一样。就像代码检查一样,代码走查也是采用持续一至两个小时的不间断会议的形式。代码走查小组由三至五人组成,其中一个人扮演类似代码检查过程中“协调人”的角色,一个人担任秘书(负责记录所有查出的错误)的角色,还有一个人担任测试人员。关于这三到五个人的组成结构,有各种各样的建议。当然,程序员应该是其中之一。我们建议另外的参与者应该包括:

·一位极富经验的程序员;

·一位程序设计语言专家;

·一位程序员新手(可以给出新颖、不带偏见的观点);

·最终维护程序的人员;

·一位来自其他不同项目的人员;

·一位来自该软件编程小组的程序员。

被指定为测试人员的那个人会带着一些书面的测试用例(程序或模块具有代表性的输入集及预期的输出集)来参加会议。在会议期间,每个测试用例都在人们脑中进行推演。也就是说,把测试数据沿程序的逻辑结构走一遍。程序的状态(如变量的值)记录在纸张或白板上以供监视。

这些测试用例本身并不起到关键的作用;相反,它们的作用是提供了启动代码走查和质疑程序员逻辑思路及其设想的手段。在大多数的代码走查中,很多问题是在向程序员提问的过程中发现的,而不是由测试用例本身直接发现的。

桌面检查

人工查找错误的第三种过程是古老的桌面检查方法。桌面检查可视为由单人进行的代码检查或代码走查:由一个人阅读程序,对照错误列表检查程序,对程序推演测试数据。

同行评审

同行评审是一种依据程序整体质量、可维护性、可扩展性、易用性和清晰性对匿名程序进行评价的技术。该项技术的目的是为程序员提供自我评价的手段。