软件测试 (四)静态测试与动态测试
本章要点 静态测试的定义与内容 静态测试的分类及方法 代码检查方法及应用 静态结构分析方法及应用 动态测试的定义与内容 动态测试的分类及方法 主动测试与被动测试
静态测试技术概述 静态测试是不执行被分析的程序,而是 通过对模块源代码进行研读、找出其中 的错误或可疑之处,收集一些度量数据。 静态测试包括对软件产品的需求和设计 规格说明书的评审、对程序代码的复审 等。 静态测试的查错和分析功能是其他方法 所不能替代的,可以采用人工或者计算 机辅助静态测试手段进行检测。
代码检查 主要检查代码和设计的一致性,代码对 标准的遵循,可读性,代码逻辑表达正 确性,代码结构合理性等方面;发现程 序中不安全、不明确和模糊部分,找出 程序中不可移植部分;发现违背程序编 写风格问题。其中包括变量检查、命名 和类型审查、程序逻辑审查、程序语法 检查和程序结构检查等内容。 包括桌面检查、代码审查和走查等
代码检查 桌面检查 桌面检查是一种传统的检查方法,由程序 员自己检查编写的程序。 由于程序员熟悉自己的程序和程序设计风 格,桌面检查由程序员自己进行可以节省 很多检查时间。 桌面检查需要首先运行拼写检查器、语法 检查器、句法检查器等进行字面检查,现 在大多数集成开发环境集成了这些相应的 工具帮助程序员在编写代码的同时就注意 这些可能存在的缺陷。
代码检查 Eg: 第28行:返回数据类 型应该为int,写成了 Int; 第33行:缺少标点符 号“;”; 第37行:返回的关键 字“return”拼写错误; 第41行:关键字“this”, 写成了“that”。
代码检查 代码审查 代码审查是由若干程序员和测试人员组成 一个审查小组,通过阅读、讨论和争议, 对程序进行静态分析的过程。代码审查分 为两步: 小组负责人提前把设计规格说明书、控制流程图、程序文本以及有关要求、规范等分发给小组成员,作为审查的依据; 小组成员在充分阅读这些材料后,召开程序审查会,在会上首先由程序员逐行讲解程序逻辑,在此过程中程序员或其他小组成员可以提出问题展开讨论,审查错误是否存在。
代码检查 走查 走查与代码审查基本相同,其过程分为两 步: 把材料先发给走查小组的每个成员,认真研究程序,然后开会; 开会的程序与代码审查不同,不是简单的读程序和对照错误检查表进行检查,而是让与会者充当计算机,即首先由测试组成员为所测程序准备一批有代表性的测试用例,提交给走查小组,走查小组开会扮演计算机角色,让测试用例沿程序逻辑运行一遍,随时记录程序的跟踪,供分析和讨论用。
Java代码的走查与审查中常见错误: 多次拷贝字符串 测试所不能发现的一个错误是生成不可变 (immutable)对象的多份拷贝。不可变对象是不可改 变的,因此不需要拷贝它。最常用的不可变对象 是String。如果你必须改变一个String对象的内容, 你应该使用StringBuffer。下面的代码可以正常工作: String s = new String ("Text here"); 但是,这段代码性能差,而且没有必要这么复杂。 可以用以下的方式来重写上面的代码: String temp = "Text here"; String s = new String (temp); 但是,这段代码包含额外的String。更好的代码为: String s = "Text here";
Java代码的走查与审查中常见错误: 没有克隆(clone)返回的对象 封装(encapsulation)是面向对象编程的重要 概念。但是Java为打破这种封装提供了方 便——Java允许返回私有数据的引用 (reference)。
Java代码的走查与审查中常见错误: 检查new 操作的结果是否为null 检查虽然没有错误,但却没有必要。C/C++程 序员在开始写Java程序的时候常常会这么做, 这是由于检查C/C++中malloc()的返回结果是必 要的,不这样做就可能产生错误。但在Java中, new操作不允许返回null,如果真的返回null, 很可能是虚拟机崩溃了,这时候即便检查返回 结果也是无济于事。
Java代码的走查与审查中常见错误: 用==替代equals 在Java中,有两种方式检查两个数据是否相等:通 过使用==操作符,或者使用所有对象都实现的 equals方法。原子类型(int,float,char等)不是 对象,因此他们只能使用==操作符,如下所示: 对象更复杂些,==操作符检查两个引用是否指向 同一个对象,而equals方法则实现更专门的相等性 检查。
Java代码的走查与审查中常见错误: 没有正确实现equals,hashCode,或者 clone等方法 方法equals,hashCode,和clone是由 java.lang.Object提供的缺省实现。但是,这 些缺省实现在大部分时候毫无用处,因此 许多类覆盖其中的若干个方法以提供更有 用的功能。而当继承一个覆盖了若干个这 些方法的父类时,子类通常也需要覆盖这 些方法。
代码检查常用检查项 通过代码检查法可以获得软件组成的重要 基本因素,例如变量标识符、过程标识符、 常量等,组合这些基本因素就可以得到软 件的基本信息。 通过这些软件的基本信息可以实现以下功 能: 直接从表中查出说明和使用错误,如循环层次 表、标号交叉引用表和变量交叉引用表; 为用户提供辅助信息,如子程序、宏和函数表、 等价表和常数表; 用来做错误预测和程序复杂度的计算,如操作 符合操作数表等。
代码检查常用检查项 检查变量的交叉引用表,重点检查未说明变量和违反了类型规定的变量,还要对照 源程序,逐个检查变量的引用、变量的使用序列、临时变量在某条路径上的重写情 况,局部变量、全局变量与特权变量的使用; 检查标号的交叉引用表,验证所有标号的正确性,检查所有标号的命名是否正确, 转向指定位置的标号是否正确; 检查子程序、宏和函数表,调用每次调用和所调用位置是否正确,确定每次调用的 子程序、宏和函数是否存在,检验调用序列中调用方式与参数顺序、个数、类型上 的一致性; 等价性检查,检查所有等价变量类型的一致性,解释所包含的类型差异; 标准检查,用标准检查工具软件或手工检查程序中违反标准的问题; 风格检查,检查发现程序在设计风格方面的问题; 比较控制流,比较由程序员设计的控制流图和由程序生成的实际控制流图,寻找和 解释每个差异,修改文档并修正错误; 选择、激活路径,在程序员设计的控制流图上选择路径,再到实际控制流图上激活 这条路径,如果选择的路径在实际控制流图上不能被激活,则源程序可能存在错误; 对照程序说明,阅读程序源代码,逐行进行分析思考,比较实际的代码和期望的代 码,从它们的差异中发现程序的错误和问题; 充分文档,代码检查的文档是一种过渡性文档,不是公开的正式文档,通过编写文 档,也是对程序的一种下意识的检查和测试,可以帮助程序员发现更多的错误,管 理部门也可以通过检查文档,了解模块质量、完全性、测试方法和程序员能力。
代码检查案例 猜数字游戏 当玩家输入四个不重复的数字,游戏服务 器给出结果,告诉猜对与否。输出结果为: “[n]A[m]B”。如果数字相同并且位置正确, 返回A;如果数字正确但位置不同,返回B; 全部正确打印:“Win!”;尝试超过六次, 游戏结束。
代码检查常见错误表 语句/功能的分布/规模 Y 通过 包含复合语句的{}是否成对出现并符合规范? 不通过 Y 通过 包含复合语句的{}是否成对出现并符合规范? 不通过 是否给单个的循环、条件语句也加了{}? if/if-else/if-else if-else/do-while/switch-case语句的格式是否符合规范? 单个变量是否只做单个用途? 重要 单行是否只有单个功能?(不要使用;进行多行合并) 单个函数是否执行了单个功能并与其命名相符? 操作符++和——操作符的应用是否复合规范? 规模 单个函数不超过规定行数? 缩进层数是否不超过规定? 是否已经消除了所有警告? 常数变量是否声明为final? 对象使用前是否进行了检查? 局部对象变量使用后是否被复位为NULL? 对数组的访问是否是安全的?(合法的index取值为[0, MAX_SIZE-1])。 是否确认没有同名变量局部重复定义问题? 程序中是否只使用了简单的表达式? 是否已经用()使操作符优先级明确化? 所有判断是否都使用了(常量==变量)的形式? 是否消除了流程悬挂? 是否每个if-else if-else语句都有最后一个else以确保处理了全集? 是否每个switch-case语句都有最后一个default以确保处理了全集? for循环是否都使用了包含下限不包含上限的形式?(k=0; k<MAX) XML标记书写是否完整,字符串的拼写是否正确? 对于流操作代码的异常捕获是否有finally操作以关闭流对象? 退出代码段时是否对临时对象做了释放处理? 对浮点数值的相等判断是否是恰当的?(严禁使用==直接判断) 重要性 激活 结果 检查项 总计 命名 重要 Y 通过 命名规则是否与所采用的规范保持一致? 是否遵循了最小长度最多信息原则? has/can/is前缀的函数是否返回布尔型? 注释 注释是否较清晰且必要? 复杂的分支流程是否已经被注释? 不通过 距离较远的}是否已经被注释? 非通用变量是否全部被注释? 函数是否已经有文档注释?(功能、输入、返回及其他可选) 特殊用法是否被注释? 声明、空白、缩进 每行是否只声明了一个变量?(特别是那些可能出错的类型) 变量是否已经在定义的同时初始化? 类属性是否都执行了初始化? 代码段落是否被合适地以空行分隔? 是否合理地使用了空格使程序更清晰? 代码行长度是否在要求之内? 折行是否恰当?
代码检查常见错误表 可靠性(函数) 重要 Y 通过 入口对象是否都被进行了判断不为空? 入口数据的合法范围是否都被进行了判断?(尤其是数组) 重要 Y 通过 入口对象是否都被进行了判断不为空? 入口数据的合法范围是否都被进行了判断?(尤其是数组) 是否对有异常抛出的方法都执行了try...catch保护? 是否函数的所有分支都有返回值? int的返回值是否合理?(负值为失败,非负值成功) 对于反复进行了int返回值判断是否定义了函数来处理? 关键代码是否做了捕获异常处理? 是否确保函数返回CORBA对象的任何一个属性都不能为null? 是否对方法返回值对象做了null检查,该返回值定义时是否被初始化? 是否对同步对象的遍历访问做了代码同步? 是否确认在对Map对象使用迭代遍历过程中没有做增减元素操作? 线程处理函数循环内部是否有异常捕获处理,防止线程抛出异常而退出? 原子操作代码异常中断,使用的相关外部变量是否恢复先前状态? 函数对错误的处理是恰当的? 可维护性 实现代码中是否消除了直接常量?(用于计数起点的简单常数例外) 是否消除了导致结构模糊的连续赋值?(如a= (b=d+c )) 是否每个return前都要有日志记录? 是否有冗余判断语句?(如:if (b) return true; else return false;) 是否把方法中的重复代码抽象成私有函数?
代码检查结果 通过对照代码检查常见错误表对源代码 进行评审,可以看到源代码基本符合要 求,但是依然存在一些问题。如: 注释不够清楚、充分; 语句结构不够完整,尤其是if语句,存在大 量省略括号及else块的情况; 对异常的处理不够充分等。 这些问题应该在代码审查后反馈给程序 员,并由程序员对问题进行修改,或对 代码进行重构,以提高代码质量。
静态结构分析 静态结构分析主要是以图的形式表现程 序的内部结构,供测试人员对程序结构 进行分析。 静态结构分析是一种对代码机械性的、 程式化的特性进行分析的方法。 常用的关系图主要有函数调用关系图和 模块控制流图。
函数调用关系图 函数调用关系图列出所有函数,用连线表 示调用关系,通过应用程序各函数之间的 调用关系展示了系统的结构,利用函数调 用关系图可以检查函数的调用关系是否正 确,是否存在孤立的函数而没有被调用, 明确函数被调用的频繁度,对调用频繁的 函数可以重点检查。通过查看函数调用关 系图,可以发现系统是否存在结构缺陷, 发现哪些函数是重要的,哪些是次要的, 需要使用什么级别的覆盖要求等;
模块控制流图 模块控制流图是由许多节点和连接节点 的边组成的图形,其中每个节点代表一 条或多条语句,边表示控制流向,模块 控制流图可以直观的反映出一个函数的 内部结构,通过检查这些模块控制流图 可以很快的发现软件错误与缺陷。
模块控制流图图元符号
类型和单元分析 为了强化在源程序中数据类型的检查, 在程序设计语言中扩展了一些新的数据 类型,例如仅能在数组中使用的下标类 型和在循环语句中当作控制变量使用的 计数器类型,这样就可以静态预处理程 序,分析程序中的类型错误。
引用分析 在静态错误分析中,最广泛使用的技术就是发现引用异 常。如果沿着程序的控制路径,变量在赋值以前被引用, 或变量在赋值以后未被引用,这时就发生引用异常。 为了检测引用异常,需要检查通过程序的每一条路径。 通常采用类似深度优先的方法遍历程序流程图的每一条 路径,也可以建立引用异常的探测工具,这类工具包含 两个表:定义表和未引用表。每张表都包含一组变量表。 未引用表包含已被赋值但未必引用的一些变量。 当扫描达到一个出度大于1的节点V时,深度优先探测算 法要求先检查最左分支的那部分程序流图,然后再检查 其他分支。在最左分支检查完之前,应把定义表和未引 用表的当前内容用一个栈暂时存储起来,当最左分支检 查完之后,算法控制返回到节点V,从栈中恢复该节点的 定义表和未引用表的旧的副表,然后再去遍历该节点的 下一个分支,这个过程要继续到全部分支检查完为止。
表达式分析 对表达式进行分析,以发现和纠正在表 达式中出现的错误,包括: 在表达式中不正确的使用括号造成的错误; 数组下标越界造成的错误; 除数为零造成的错误; 对负数开平方,或者对Π求正切造成的错误。 最复杂的一类表达式分析是对浮点数计 算造成的误差的检查。由于使用二进制 数不能精确的表示十进制浮点数,常常 使计算结果出乎意料。
接口分析 接口分析是程序的静态错误分析和设计 分析共同研究的问题。接口一致性的设 计可以分析检查模块之间接口的一致性 和外部数据库之间接口的一致性。 程序关于接口的静态错误分析检查过程 与实参在类型、函数过程接口之间的一 致性,因此要检查形参与实参在类型、 数量、维数、顺序、使用上的一致性; 检查全局变量和公共数据区在使用上的 一致性。
Eg:猜数字游戏 从该函数调用关系图中,可以得到以下信息: 函数之间的调用关系符合设计规格说明书的要求; 不存在递归调用; 调用层次最深为4层; 不存在独立的和没有被调用的函数; 比较重要的函数有validate(),compareTwoStr(),validateStr() 等。
动态测试技术概述 动态测试是软件测试中使用最为普遍的 方法,通过运行程序发现错误,通过观 察代码运行过程来获取系统行为、变量 实时结果、内存、堆栈、线程以及测试 覆盖率等各方面的信息,从而判断系统 是否存在问题,或者通过有效的测试用 例、对应的输入输出关系来分析被测程 序的运行情况,从中发现缺陷。
动态测试过程 可以把程序看成一个函数,它描述了输入 和输出之间的关系。输入的全体叫做程序 的定义域,输出的全体叫做程序的值域。 一个动态测试过程可分为5步: 选取在定义域中的有效值或定义域外的无效值。 对已选的值决定其预期的结果。 用选取的值执行程序。 观察程序的行为,并获取其结果。 将结果与预期的结果相比较,如果不吻合,则 证明程序存在错误。
动态测试分类 按生产测试数据的不同方式 按照是否对程序内部结构进行分析 功能测试和结构测试。功能测试又叫“黑盒测试”,它从 系统的需求分析说明书出发,按程序的输入、输出特 性和类型选择测试数据。 结构测试又叫“白盒测试”,测试数据的产生设计程序的 具体结构,所以它应反映程序的结构性质。 按照是否对程序内部结构进行分析 测试的分析方法,是通过分析程序的内部逻辑来设计 测试用例,它适用于设计阶段对软件详细设计表示的 测试,包括白盒测试方法和静态测试方法。 测试的非分析方法,即为黑盒方法,它是根据程序的 功能来设计相应的测试用例,适用于需求分析阶段对 软件需求说明书的测试。
主动测试与被动测试 在软件测试中,比较常见的是主动测试方法,测 试人员主动向被测试对象发送请求,或借助数据、 事件驱动被测试对象的行为,来验证被测试对象 的反应或输出结果。在主动测试中,测试人员和 被测试对象之间发生直接相互作用,而且被测试 对象完全受测试人员的控制,被测试对象处于测 试状态,而不是实际工作状态。 由于主动测试中被测试对象受人为因素影响较大, 而且一般是在测试环境中进行,而非软件产品的 实际运行环境,所以主动测试不适应产品的在线 测试。为了解决产品在线测试,这就需要用到被 动测试。在被动测试方法中,软件产品在实际环 境中运行,测试人员被动地监控产品的运行,通 过一定的机制来获取系统运行的数据,包括输入、 输出数据。
主动测试与被动测试 在主动测试中,测试人员需要设计测试用例、设法输入各种数 据;而在被动测试中,系统运行过程中的各种数据自然生成, 测试人员不需要设计测试用例,只要设法获取系统运行的各种 数据,但是数据的完整性得不到保证。被动测试的关键是建立 监控程序,并通过数据分析掌握系统的状态。
小结 根据测试过程是否在实际应用环境中运 行可以将传统的测试技术分为静态测试 和动态测试。本章主要介绍了静态测试、 动态测试的定义与内容以及静态测试、 动态测试的分类及方法。
The End 谢谢!