- 通常把编码和测试统称为实现
- 所谓编码就是把软件设计翻译成计算机可以理解的形式——用某种程序设计语言书写的程序
- 编码是设计的自然结果,程序的质量主要取决于软件设计的质量
- 但所选用的程序设计语言的特点和编码风格也会对程序的可靠性、可读性、可测试性和可维护性产生深远的影响。
- 软件测试对软件可靠性的影响是重要的(软件生命周期中,差错不可避免)
- 尽可能早地发现并纠正差错
- 测试的目的就是在软件投入生产性运行之前,尽可能多地发现软件中的错误
- 软件测试在软件生命周期中横跨两个阶段
- 单元测试(一般模块的编写者和测试者是同一个人)
- 综合测试(通常由专门的测试人员承担这项工作)
- 统计资料表明,软件测试的工作量往往占软件开发总工作量的40%以上,对于关键应用系统的测试,可能相当于软件工程其他步骤总成本的3~5倍
- 测试目标是发现软件中的错误,但不是最终目的。调试是诊断并改正错误,这是最终目的。调试是测试阶段最困难的工作
- 对测试结果进行收集和评价,确定系统的可靠性
编码 (understanding)
选择程序设计语言
- 总的来说,高级语言明显优于汇编语言。除了对程度执行时间和使用的空间都有很严格限制的情况;需要产生任意的甚至非法的指令序列;体系结构特殊的微处理机,以致在这类机器上通常不能实现高级语言编译程序;或者大型系统中执行时间非常关键的(或直接依赖于硬件的)一小部分代码;其他程序应该一律用高级语言书写
编码风格
源程序代码的逻辑简明清晰、易读易懂是好程序的一个重要标准,应该遵循下述规则
- 程序内部的文档(注释)
- 数据说明
- 语句构造
- 输入输出
- 效率
软件测试基础 (master)
软件工程其他阶段都是“建设性的”,而测试阶段的目的却是为了“破坏”已经建造好的软件系统————竭力证明程序中有错误,不能按照预定要求正确工作。
测试目标
- 测试是为发现程序中的错误而执行程序的过程
- 好的测试方案是极可能发现迄今为止尚未发现的错误的测试方案
- 成功的测试是发现了至今为止尚未发现的错误的测试
测试准则
- 所有的测试都应该能追溯到用户需求(因需制宜)
- 应该在测试开始之前的相当长时间,就制定出测试计划
- 把Pareto原理应用于软件测试:测试发现的错误中的80%很可能是由程序中20%的模块造成的(2-8定理)
- 测试应该从“小规模”开始,并逐步进行“大规模”测试(由小到大)
- 穷举测试是不可能的
- 为了达到最佳的测试效果,应该由独立的第三方来从事测试工作(心理学角度上来讲,让编码人员找自己的错误是不现实的)
测试方法
测试步骤
- 单元测试,也称模块测试
- 集成测试
- 确认测试
- 系统测试
ps:
- α测试:在受控环境下测试(实验室环境)
- β测试:在非受控环境下测试(实际工作环境)
单元测试
- 单元测试和编码属于软件工程过程同一阶段(同属于编码和单元测试,因为单元测试通常是在编码的过程中进行的)
- 在编写出源程序代码并通过了编译程序的语法检查之后,应用人工测试和计算机测试这样两种类型的测试,完成单元测试工作
- 这两种类型的测试各有所长,互相补充
- 单元测试主要采用白盒测试技术,而且对多个模块的测试可以并行地进行
测试重点
- 模块接口
- 局部数据结构
- 重要的执行通路
- 出错处理通路
- 边界条件
代码审查
人工测试源程序可以由编写者本人非正式地进行,也可以由审查小组正式进行,后者称为代码审查,是一种非常有效的程序验证技术,对于典型的程序来说,可以查出30%~70%的逻辑设计错误和编码错误
- 审查小组组成:
- 组长,应该是一个很有能力的程序员,而且没有直接参与这项工程
- 程序的设计者
- 程序的编写者
- 程序的测试者
- 审查小组组成:
计算机测试
- 模块并不是一个独立的程序,必须为每个单元测试开发驱动软件(负责调用本模块)和(或)存根软件(负责模拟被本模块调用的子模块,比如对于一个用于数据处理的子模块,本模块需要的是其输出一个数据结构,那么就可以直接用一个构造的数据实例来替代这个子模块)
- 驱动程序就是一个“主程序”,接收测试数据,把这些数据传送给被测试的模块,并且输出有关结果
- 存根程序代替被测试的模块所调用的模块,也可以称为“虚拟子程序”。(桩模块)
- 使用被它代替的模块的接口,做最少量的数据操作,输出对入口的检验或操作结果,并且把控制归还给调用它的模块
集成测试
集成测试是测试和组装软件的系统化技术,在把模块按照设计要求组装起来的同时进行测试,主要目标是发现与接口有关的问题
自顶向下集成
- 首先测试M1(需要写M2、M3、S4对应的桩模块),然后将M1作为M2、M3、S4的驱动程序,集成并测试。以此类推
- 不用写驱动模块,但是需要写大量桩模块
- 优点:
- 不需要测试驱动程序
- 能够在测试阶段的早期实现并验证系统的主要功能
- 能在早期发现上层模块的接口错误
- 缺点:
- 需要存根程序
- 低层关键模块中的错误发现较晚
- 早期不能充分展开人力(不利于团队分工测试)
自底向上集成
- 步骤:
- 把低层模块组合成实现某特定软件子功能的簇
- 写一个驱动程序(用于测试的控制程序),协调测试数据的输入和输出
- 对由模块组成的子功能簇进行测试
- 去掉驱动程序,沿软件结构自下向上移动,把子功能簇组合起来形成更大的子功能簇,重复2~4
- 优缺点与自顶向下集成相反
- 步骤:
回归测试
- 不是一种测试方法,也不是一个测试阶段。重新执行已经做过的测试的某个子集,以保证上述这些改变没有带来非预期的副作用
- 回归测试(regression)与累进测试(progression)
- 回归测试:用之前版本的测试方案,测试好现在的程序
- 累进测试:用新的测试方案,测试同一版本的程序
确认测试
- 确认测试也称为验收测试,目标是验证软件的有效性
- 使用了确认(Validation)和验证(Verification)这样两个不同的术语
- 验证 就是要用数据证明我们是不是在正确的制造产品。注意这里强调的是过程的正确性
- 确认 就是要用数据证明我们是不是制造了正确的产品。注意这里强调的是结果的正确性
- 确认测试的范围
- 软件配置复查
- Alpha和Beta测试
- α测试:在受控环境下测试(实验室环境)
- β测试:在非受控环境下测试(实际工作环境)
白盒测试技术 (master)
- 逻辑覆盖是设计白盒测试方案的技术。设计测试方案是测试阶段的关键技术问题
- 测试方案包括具体的测试目的(例如,要测试的具体功能),应该输入的测试数据和预期的输出结果
逻辑覆盖
语句覆盖
- 选择足够多的测试数据,使被测程序中每个语句至少执行一次
- 是很弱的逻辑覆盖标准
- 只关心判定表达式的值,不关心表达式内部具体每个条件取哪些值
- 举个栗子:
- 只需选取一组测试数据,覆盖路径ace即可
- 比如可以取
- 测试用例的格式:【输入的(A, B, X),输出的(A, B, X)】
- 满足语句覆盖的测试用例:【(2, 0, 4), (2, 0, 3)】
判定覆盖
- 不仅每个语句必须至少执行一次
- 而且每个判定的每种可能的结果都应该至少执行一次,也就是每个判定的每个分支都至少执行一次
- 同样只关心判定表达式的值,不关心表达式内部具体每个条件取哪些值
- 举个栗子:
- 测试用例的格式:【输入的(A, B, X),输出的(A, B, X)】
- 满足判定覆盖的测试用例:
- 【(2, 0, 4), (2, 0, 3)】覆盖ace
- 【(1, 1, 1), (1, 1, 1)】覆盖abd
条件覆盖
- 不仅每个语句至少执行一次,而且判定表达式中每个条件都取到各种可能的结果
- 条件覆盖不一定比判定覆盖强,甚至可能连语句覆盖都达不到
- 举个栗子:
- 本例中b处没有逻辑代码,如果有,则上面的第二种方案就达不到语句覆盖,但是能达到条件覆盖
判定/条件覆盖
- 判定覆盖不一定包含条件覆盖,条件覆盖也不一定包含判定覆盖,判定/条件覆盖能同时满足这两种覆盖标准
- 举个栗子:
条件组合覆盖
- 条件组合覆盖就是设计足够的测试用例,运行被测程序,使得每个判断的所有可能的条件取值组合至少执行一次
- 举个栗子:
- 所有可能的条件组合:
- 一个满足条件组合覆盖的测试方案
点覆盖
边覆盖
路径覆盖
控制结构测试
基本路径测试 (master)
- 根据过程设计结果画出相应的流图
- 计算流图的环形复杂度n
- 确定线性独立路径的基本集合(找到n条能覆盖整个流图的独立路径)
- 独立路径指至少引入程序一个新处理语句集合或一个新条件的路径,用流图术语描述,独立路径至少包含一条在定义该路径之前不曾用过的边
- 程序的环形复杂度决定了程序中独立路径的数量
- 在不考虑业务逻辑的情况下找到的路径有可能是无效的(多个判定条件之间可能有一定联系,导致某个独立路径上的判定条件无法同时满足)
- 设计可强制执行基本集合中每条路径测试用例
- 举个栗子:
- 环形复杂度:6
- 路径:
- 路径1:1-2-10-11-13
- 路径2:1-2-10-12-13
- 路径3:1-2-3-10-11-13
- 路径4:1-2-3-4-5-8-9-2···
- 路径5:1-2-3-4-5-6-8-9-2···
- 路径6:1-2-3-4-5-6-7-8-9-2···
- 路径4、5、6后面的省略号(…)表示,可以后接通过控制结构其余部分的任意路径(例如,10-11-13)
条件测试 (pass)
循环测试 (pass)
- 简单循环
- 串接循环
- 嵌套循环
黑盒测试技术 (master)
- 黑盒测试着重测试软件的功能需求,让软件工程师设计出能充分检查程序所有功能需求的输入条件集
- 黑盒测试和白盒测试两者互补(A代表需求,B代表实现)
- 白盒测试至多能覆盖实现
- 黑盒测试至多能覆盖需求
- 只有两者结合使用才能做到全方位的测试
- 白盒测试在测试过程的早期阶段进行,而黑盒测试主要用于测试过程的后期
- 黑盒测试力图发现下述类型的错误:① 功能不正确或遗漏了功能;② 界面错误;③ 数据结构错误或外部数据库访问错误;④ 性能错误;⑤ 初始化和终止错误
等价划分
目的
- 需要研究程序的功能说明,确定输入数据的有效等价类和无效等价类
经验
- 如果规定了输入值的范围,则可划分出一个有效的等价类(输入值在此范围内),两个无效的等价类(输入值小于最小值或大于最大值)
- 如果规定了输入数据的个数,则类似地也可以划分出一个有效的等价类和两个无效的等价类
- 如果规定了输入数据的一组值,而且程序对不同输入值做不同处理,则每个允许的输入值是一个有效的等价类,此外还有一个无效的等价类(任一个不允许的输入值)
- 如果规定了输入数据必须遵循的规则,则可以划分出一个有效的等价类(符合规则)和若干个无效的等价类(从各种不同角度违反规则)
- 如果规定了输入数据为整型,则可以划分出正整数、零和负整数等三个有效类
- 如果程序的处理对象是表格,则应该使用空表,以及含一项或多项的表
边界值分析
- 处理边界情况时程序最容易发生错误
- 边界值处测试数据的选取
- 刚好等于
- 刚刚小于
- 刚刚大于
- 栗子:
错误推测
- 错误推测法在很大程度上靠直觉和经验进行
- 基本想法是列举出程序中可能有的错误和容易发生错误的特殊情况,并且根据它们选择测试方案
调试
- 调试(也称为纠错)作为成功的测试的后果而出现,即调试是在测试发现错误之后排除错误的过程
- 调试不是测试,但是它总是发生在测试之后
- 途径:
- 蛮干法:打印内存数据、关键输出、断点
- 回溯法:由症状回推可能的原因
- 原因排除法:对分查找、归纳、演绎等方法
软件可靠性