首页 > 代码库 > 软件测试重点
软件测试重点
第1章 软件测试概述
什么是软件测试
ü 广义的概念:指软件生存周期中所有的检查、评审和确认工作,其中包括了对分析、设计阶段,以及完成开发后维护阶段的各类文档、代码的审查和确认
ü 狭义概念:识别软件缺陷的过程,即实际结果与预期结果的不一致
ü 软件测试通常包括验证(verification)和确认(validation):
- 验证指保证软件正确的实现了某一特定功能的一系列活动
- 确认指的是保证软件的实现满足了用户需求的一系列活动
软件测试的目的
ü 测试的目的就是发现软件中的各种缺陷
ü 测试只能证明软件存在缺陷,不能证明软件不存在缺陷
ü 测试可以使软件中缺陷降低到一定程度,而不是彻底消灭
ü 以较少的用例、时间和人力找出软件中的各种错误和缺陷,以确保软件的质量
测试的目标
ü 最终目的是确保软件的功能符合用户的需求,把尽可能多的问题在发布或交付前发现并改正:
- 确保软件完成了它所承诺或公布的功能
- 确保软件满足性能的要求
- 确保软件是健壮的和适应用户环境的
ü 为软件的质量评估提供依据
ü 为软件质量改进和管理提供帮助
软件测试原则
ü Good-enough: 一种权衡投入/产出比的原则
ü 保证测试的覆盖程度,但穷举测试是不可能的
ü 所有的测试都应追溯到用户需求
ü 越早测试越好,测试过程与开发过程应是相结合的
ü 测试的规模由小而大,从单元测试到系统测试
ü 为了尽可能地发现错误,应该由独立的第三方来测试
ü 不能为了便于测试擅自修改程序
ü 既应该测试软件该做什么也应该测试软件不该做什么
测试的规律
ü 木桶原理:
- 软件质量的关键因素是分析、设计和实现,测试应该是融于其中的补充检查手段,其他管理、支持、甚至文化因素也会影响最终软件的质量
- 测试是提高软件质量的必要条件,最直接、最快捷的手段,但决不是一种根本手段
ü Bug的80-20原则
- 在分析、设计、实现阶段的复审和测试工作能够发现和避免80%的Bug
- 而系统测试又能找出其余Bug中的80%
- 最后的5%的Bug可能只 有在用户的大范围、长时间使用后才会曝露出来
软件测试的重点
ü 测试用例的良好设计
- 测试用例的设计是整个软件测试工作的核心
- 测试用例反映对被测对象的质量要求,决定对测试对象的质量评估
ü 测试工作的管理
- 尤其是对包含多个子系统的大型软件系统,其测试工作涉及大量人力和物力,有效的测试工作管理是保证有效测试工作的必要前提
ü 测试环境的建立
- 测试环境应该与实际测试环境一致
软件缺陷含义
ü IEEE (1983) 729 软件缺陷一个标准的定义:
- 从产品内部看,软件缺陷是软件产品开发或维护过程中所存在的错误、毛病等各种问题;
- 从外部看,软件缺陷是系统所需要实现的某种功能的失效或违背。
软件缺陷的主要类型/现象:
- 功能、特性没有实现或部分实现
- 设计不合理,存在缺陷
- 实际结果和预期结果不一致
- 运行出错,包括运行中断、系统崩溃、界面混乱
- 数据结果不正确、精度不够
- 用户不能接受的其他问题,如存取时间过长、界面不美观
ü 软件缺陷的产生
① 技术问题:算法错误,语法错误,计算和精度问题,接口参数传递不匹配
② 团队工作:误解、沟通不充分
③ 软件本身:文档错误、用户使用场景(user scenario),时间上不协调、或不一致性所带来的问题。系统的自我恢复或数据的异地备份、灾难性恢复等问题
软件缺陷在不同阶段的分布
在真正的程序测试之前,通过审查、评审会可以发现更多的缺陷。规格说明书的缺陷会在需求分析审查、设计、编码、测试等过程中会逐步发现,而不能在需求分析一个阶段发现
软件测试的质量
ü 软件测试可以发现以下软件缺陷:
- 软件实现的功能不正确
- “缺少”:软件没有实现某项功能
- “多余”:软件实现的某项功能在需求中没有定义
ü 发现第一类软件缺陷的过程 --- “验证”
ü 发现后两类软件缺陷的过程 --- “确认”
ü 软件测试本身的质量在于:
- 发现软件缺陷并能区分其类型
- 提供关于软件质量和开发过程质量的信息
软件测试度量
ü 测试覆盖率
- 有多少需求、代码已经被测试了
ü 缺陷发现率
- 缺陷是何时被发现,并且有多少缺陷已经被发现。缺陷可以根据严重性来分类。需记录以下值:
? 缺陷数目
? 缺陷的严重性
软件测试的分类
ü 典型的软件测试类型
- 功能测试
- 可靠性测试
- 容错性测试
- 恢复测试
- 易用性测试
- 性能测试
- 可维护性测试
- 可移植性测试
- 安全性测试
- 用户文档测试
- 软件测试技术
ü 黑盒测试/白盒测试
ü 动态测试/静态测试
黑盒测试和白盒测试
ü 黑盒测试
- 又称功能测试或数据驱动测试,是针对软件的功能需求/实现进行测试
- 通过测试来检测每个功能是否符合需求,不考虑程序内部的逻辑结构
- 穷举输入测试
ü 黑盒测试方法
- 功能划分
- 等价类划分
- 边界值分析
- 因果图
- 错误推测等
- 白盒测试
- 白盒测试也称结构测试或逻辑驱动测试
- 必须知道软件内部工作过程,通过测试来检测软件内部是否按照需求、设计正常运行
- 通过逻辑覆盖、路径覆盖等方式选择测试用例,可以用测试覆盖率评价测试用例
ü 白盒测试的主要方法
- 对应于程序的一些主要结构:语句、分支、逻辑路径、变量
? 语句覆盖方法
? 分支覆盖方法
? 逻辑覆盖方法
动态测试和静态测试
ü 动态测试
- 动态测试需要在开发/测试环境或实际运行环境中运行软件,并使用测试用例去查找软件缺陷
- 动态测试包括功能确认与接口测试、覆盖率分析、性能分析、内存分析等
ü 静态测试
- 静态测试不实际运行软件,主要是对软件的编程格式、结构等方面进行评估
- 静态测试包括代码检查、程序结构分析、代码质量度量等。它可以由人工进行,也可以借助软件工具自动进行
黑盒白盒测试与动态静态测试的关系
ü 黑盒测试、白盒测试
- 测试用例设计阶段采用的方法
ü 动态测试、静态测试
- 测试执行阶段采用的方法
软件测试方法
手工测试和自动测试
ü 手工测试
ü 自动测试
ü 适合自动化的测试操作
ü 手工测试和自动测试的比较
ü 手工测试
ü 传统的测试方法
ü 由测试人员手工编写测试用例
ü 缺点在于测试工作量大,重复多,回归测试难以实现
自动测试
ü 利用软件测试工具自动实现全部或部分测试工作:管理、设计、执行和报告
ü 自动测试节省大量的测试开销,并能够完成一些手工测试无法实现的测试
适合自动化的测试操作
ü 测试用例的生成(包括测试输入,标准输出,测试操作指令等)
ü 测试的执行与控制(包括单机与网络多机分布运行;夜间及假日运行)
ü 测试对象、范围、版本等的控制
ü 测试结果与预期输出的对比
ü 不吻合的测试结果的分析、记录、分类、和通报
ü 测试的统计,报表的产生
手工测试和自动测试的比较
ü 手工完成测试的全部过程无法保证测试的科学性与严密性:
- 修改的缺陷越多,回归测试越困难
- 没有人能向决策层提供精确的数据以度量当前的工作进度及工作效率
- 反复测试带来的倦怠情绪及其他人为因素使得测试标准前后不一
- 测试花费的时间越长,测试的严格性也就越低
ü 自动测试将测试人员从反复、烦杂的测试执行中解放出来,用更多的时间进行测试设计和结果分析
ü 软件测试不可能完全自动化
ü 不能完成所有手工测试任务
ü 无创造性且灵活性差,不能改进测试的有效性
ü 过程中可能会遇到许多意想不到的问题,特别是当软件不稳定时
ü 测试脚本的维护高
测试流程
ü 单元测试
ü 集成测试
ü 系统测试
ü 用户验收测试
ü 回归测试
ü V模型示意图
单元测试
ü 完成对最小的软件设计单元—模块的验证工作
ü 目标是确保模块被正确地编码
ü 使用过程设计描述作为指南,对重要的控制路径进行测试以发现模块内的错误
ü 通常情况下是面向白盒的
ü 对代码风格和规则、程序设计和结构、业务逻辑等进行静态测试,及早地发现和解决不易显现的错误
单元测试
ü 单元测试的内容
- 接口测试
- 内部数据结构
- 全局数据结构
- 边界
- 语句覆盖
- 错误路径
集成测试
ü 通过测试发现与模块接口有关的问题
ü 目标是把通过了单元测试的模块拿来,构造一个在设计中所描述的程序结构
ü 应当避免一次性的集成(除非软件规模很小),而采用增量集成
系统测试
ü 根据软件需求规范的要求进行系统测试,确认系统满足需求的要求
ü 系统测试人员相当于用户代言人
ü 在需求分析阶段要确定软件的可测性,保证有效完成系统测试工作
ü 系统测试主要内容
- 所有功能需求得到满足
- 所有性能需求得到满足
- 其他需求(例如安全性、容错性、兼容性等)得到满足
用户验收/确认测试
ü 配置审查
- 确保已开发软件的所有文件资料均已编写齐全,并分类编目
ü Alpha测试
- 是由用户在开发者的场所来进行的,Alpha测试是在一个受控的环境中进行的
ü Beta测试
- 由软件的最终用户在一个或多个用户场所来进行的
- 开发者通常不在现场,用户记录测试中遇到的问题并报告给开发者
- 开发者对系统进行最后的修改,并开始准备发布最终的软件
回归测试
ü 当发现并修改缺陷后,或者在软件中添加新功能后,重新测试,用来检查被发现的缺陷是否被改正,并且所作的修改没有引发新的问题
ü 回归测试可以通过人工重新执行测试用例,也可以使用自动化的捕获回放工具来进行
ü 回归测试方式
- 再测试全部用例
? 选择基线测试用例库中的全部测试用例组成回归测试包,测试成本最高
- 基于风险选择测试
? 可以基于一定的风险标准来从基线测试用例库中选择回归测试包
各阶段测试所使用的方法技术
ü 单元测试
白盒、自动、静态
ü 集成测试
白盒、黑盒、自动、静态
ü 系统测试
黑盒、自动、手工
ü 用户验收/确认测试
黑盒、自动、手工
第2章 软件测试策略与过程
2.1 软件测试的复杂性分析
1、无法对程序进行完全测试
(1)测试所需要的输入量太大
(2)测试的输出结果太多
(3)软件实现的途径太多
(4)软件规格说明没有一个客观标准
2、测试无法显示潜在的软件缺陷和故障
——通过软件测试只能报告软件已被发现的缺陷和故障,无法报告隐藏的软件故障。
3、存在的故障现象与发现的故障数量成正比
——结论:应当对故障集中的程序段进行重点测试
4、不能修复所有的软件故障
——原因:没有足够的进行修复;修复的风险较大; 不值得修复;可不算做故障的一些缺陷;“杀虫剂现象”。
——结论:关键是要进行正确的判断、合理的取舍,根据风险分析决定哪些故障必须修复,哪些故障可以不修复。
5、软件测试的代价
——工作原则:就是如何将无边无际的可能性减小到一个可以控制的范围,以及如何针对软件风险做出恰当选择,去粗存精,找到最佳的测试量,使得测试工作量不多也不少,既能达到测试的目的,又能较为经济。
2.2 软件测试方法与策略
软件测试策略
l 什么是软件测试策略?
——是为软件工程过程定义的一个软件测试的模板,也就是把特定的测试用例方法放置进去的一系列步骤。
l 软件测试策略包含的特征:
(1)测试从模块层开始,然后扩大延伸到整个基于计算机的系统集合中。
(2)不同的测试技术适用于不同的时间点。
(3)测试是由软件的开发人员和(对于大型系统而言)独立的测试组来管理的。
(4)测试和调试是不同的活动,但是调试必须能够适应任何的测试策略。
软件测试充分性准则
l 对任何软件都存在有限的充分测试集合。
l 如果一个软件系统在一个测试数据集合上的测试是充分的,那么再多测试一些数据也应该是充分的。这一特性称为单调性。
l 即使对软件所有成分都进行了充分的测试,也并不表明整个软件的测试已经充分了。这一特性称为非复合性。
l 即使对软件系统整体的测试是充分的,也并不意味软件系统中各个成分都已经充分地得到了测试。这个特性称为非分解性。
l 软件测试的充分性应该与软件的需求和软件的实现都相关。
l 软件越复杂,需要的测试数据就越多。这一特性称为复杂性。
l 测试得越多,进一步测试所能得到的充分性增长就越少。这一特性称为回报递减率。
2.2.1 静态测试与动态测试
1、静态测试
l 静态测试不实际运行软件,主要是对软件的编程格式、结构等方面进行评估。
l 静态测试包括代码检查、静态结构分析、代码质量度量等。它可以由人工进行,也可以借助软件工具自动进行。
l 静态测试方法也可利用计算机作为对被测程序进行特性分析的工具,但与人工测试方式有着根本区别。另一方面,因它并不真正运行被测程序,只进行特性分析,这又与动态方法不同。所以,静态方法常常称为“分析”,静态测试是对被测程序进行特性分析方法的总称。
代码检查
l 代码检查包括代码走查、桌面检查、代码审查等,主要检查代码和设计的一致性,代码对标准的遵循、可读性,代码的逻辑表达的正确性,代码结构的合理性等方面。
l 代码检查的具体内容:变量检查、命名和类型审查、程序逻辑审查、程序语法检查和程序结构检查等。
l 代码检查的优点:在实际使用中,代码检查比动态测试更有效率,能快速找到缺陷,发现30%~70%的逻辑设计和编码缺陷;代码检查看到的是问题本身而非征兆。
l 代码检查的缺点:非常耗费时间,而且代码检查需要知识和经验的积累。
静态结构分析
l 静态结构分析主要是以图形的方式表现程序的内部结构。例如函数调用关系图、函数内部控制流图。其中:
——函数调用关系图以直观的图形方式描述一个应用程序中各个函数的调用和被调用关系;
——控制流图显示一个函数的逻辑结构,由许多节点组成,一个节点代表一条语句或数条语句,连接结点的叫边,边表示节点间的控制流向。
代码质量度量
l 软件质量包括六个方面:功能性、可靠性、易用性、效率、可维护性和可移植性。软件的质量是软件属性的各种标准度量的组合。
l 针对软件的可维护性,目前业界主要存在三种度量参数:Line复杂度、Halstead复杂度和McCabe复杂度。其中Line复杂度以代码的行数作为计算的基准。Halstead以程序中使用到的运算符与运算元数量作为计数目标(直接测量指标),然后可以据以计算出程序容量、工作量等。McCabe复杂度 一般称为圈复杂度,它将软件的流程图转化为有向图,然后以图论来衡量软件的质量。
静态测试与动态测试(续)
l 静态测试阶段的任务:
(1)检查算法的逻辑正确性。
(2)检查模块接口的正确性。
(3)检查输入参数是否有合法性检查。
(4)检查调用其他模块的接口是否正确。
(5)检查是否设置了适当的出错处理。
(6)检查表达式、语句是否正确,是否含有二义性。
(7)检查常量或全局变量使用是否正确。
(8)检查标识符的使用是否规范、一致。
(9)检查程序风格的一致性、规范性。
(10)检查代码是否可以优化,算法效率是否最高。
(11)检查代码注释是否完整,是否正确反映了代码的功能。
静态测试可以完成以下工作:
(1)发现下列程序的错误:错用局部变量和全局变量;未定义的变量、不匹配的参数;不适当的循环嵌套或分支嵌套、死循环、不允许的递归;调用不存在的子程序,遗漏标号或代码。
(2)找出以下问题的根源:从未使用过的变量;不会执行到的代码、从未使用过的标号;潜在的死循环。
(3)提供程序缺陷的间接信息:所用变量和常量的交叉应用表;是否违背编码规则;标识符的使用方法和过程的调用层次。
(4)为进一步查找做好准备。
(5)选择测试用例。
(6)进行符号测试。
2、动态测试
l 动态方法的主要特征是:
——计算机必须真正运行被测试的程序,通过输入测试用例,对其运行情况即输入与输出的对应关系进行分析,以达到检测的目的。
l 动态测试包括:
(1)功能确认与接口测试
(2)覆盖率分析
(3)性能分析
(4)内存分析
2.2.2 黑盒测试和白盒测试
l 若测试规划是基于产品的功能,目的是检查程序各个功能是否能够实现,并检查其中的功能错误,则这种测试方法称为黑盒测试(Black-box Testing)方法。
——黑盒测试又称为功能测试、数据驱动测试和基于规格说明的测试。它是一种从用户观点出发的测试,一般被用来确认软件功能的正确性和可操作性。
l 若测试规划基于产品的内部结构进行测试,检查内部操作是否按规定执行,软件各个部分功能是否得到充分使用,则这种测试方法称为白盒测试(White-box Testing)方法。
——白盒测试又称为结构测试、逻辑驱动测试或基于程序的测试,一般用来分析程序的内部结构。
1、黑盒测试
l 黑盒测试的基本观点是:任何程序都可以看作是从输入定义域映射到输出值域的函数过程,被测程序被认为是一个打不开的黑盒子,黑盒中的内容(实现过程)完全不知道,只明确要做到什么。
l 黑盒测试主要根据规格说明书设计测试用例,并不涉及程序内部构造和内部特性,只依靠被测程序输入和输出之间的关系或程序的功能设计测试用例。
l 黑盒测试的特点:
(1)黑盒测试与软件的具体实现过程无关,在软件实现的过程发生变化时,测试用例仍然可以使用。
(2)黑盒测试用例的设计可以和软件实现同时进行,这样能够压缩总的开发时间。
黑盒测试是在程序接口进行测试,它只是检查程序功能是否按照规格说明书的规定正常使用。也被称为用户测试。
l 黑盒测试主要是为了发现以下几类错误:
- 是否有不正确或遗漏了的功能?
- 在接口上,输入能否正确地接受?能否输出正确的结果?
- 是否有数据结构错误或外部信息访问错误?
- 性能上是否能够满足要求?
- 是否有初始化或终止性错误?
l 黑盒测试的难点:在哪个层次上进行测试?
l 黑盒测试的具体技术方法 :
①边界值分析法 ②等价类划分法 ③因果图法 ④决策表法
2、白盒测试
l 白盒测试将被测程序看作一个打开的盒子,测试者能够看到被测源程序,可以分析被测程序的内部结构,此时测试的焦点集中在根据其内部结构设计测试用例。
l 白盒测试要求是对某些程序的结构特性做到一定程度的覆盖,或者说这种测试是“基于覆盖率的测试”。
l 通常的程序结构覆盖有:
①语句覆盖 ②判定覆盖 ③条件覆盖 ④判定/条件覆盖 ⑤路径覆盖
白盒测试需要完全了解程序结构和处理过程,它按照程序内部逻辑测试程序,检验程序中每条通路是否按预定要求正确工作。也被称为程序员测试。
l 黑盒测试:
——以用户的观点,从输入数据与输出数据的对应关系,即根据程序外部特性进行测试,而不考虑内部结构及工作情况。
——黑盒测试技术注重于软件的信息域(范围),通过划分程序的输入和输出域来确定测试用例。
——若外部特性本身存在问题或规格说明的规定有误,则应用黑盒测试方法是不能发现问题的。
l 白盒测试:
——只根据程序的内部结构进行测试。
——测试用例的设计要保证测试时程序的所有语句至少执行一次,而且要检查所有的逻辑条件。
——如果程序的结构本身有问题,比如说程序逻辑有错误或者有遗漏,那也是无法发现的。
项目 | 黑盒测试 | 白盒测试 |
规划方面 | 功能的测试 | 结构的测试 |
有点方面 | 能确保从用户的角度出发进行测试 | 能对程序内部的特定部位进行覆盖测试 |
缺点方面 | 无法测试程序内部特定部位;当规格说明有误,则不能发现问题 | 无法检查程序的外部特性; 无法对未实现规格说明的程序内部欠缺部分进行测试 |
应用范围 | 边界分析法 等价类划分法 决策表测试 | 语句覆盖,判定覆盖,条件覆盖, 判定/条件覆盖,路径覆盖,循环覆盖, 模块接口测试 |
2.2.3 软件测试过程
软件测试的过程流程
l 单元测试:针对每个单元的测试, 以确保每个模块能正常工作为目标。
l 集成测试:对已测试过的模块进行组装,进行集成测试。目的在于检验与软件设计相关的程序结构问题。
l 确认(有效性)测试:是检验所开发的软件能否满足所有功能和性能需求的最后手段。
l 系统测试:检验软件产品能否与系统的其他部分(比如,硬件、数据库及操作人员)协调工作。
l 验收(用户)测试:检验软件产品质量的最后一道工序。主要突出用户的作用,同时软件开发人员也应有一定程度的参与。
2.3 单元测试
l 单元测试针对每个程序的模块,主要测试5个方面的问题:
——模块接口、局部数据结构、边界条件、独立的路径和错误处理。
模块接口
l 这是对模块接口进行的测试,检查进出程序单元的数据流是否正确。模块接口测试必须在任何其它测试之前进行。
l 模块接口测试至少需要如下的测试项目:
(1)调用所测模块时的输入参数与模块的形式参数在个数、属性、顺序上是否匹配;
(2)所测模块调用子模块时,它输入给子模块的参数与子模块中的形式参数在个数、属性、顺序上是否匹配;
(3)是否修改了只做输入用的形式参数;
(4)调用标准函数的参数在个数、属性、顺序上是否正确;
(5)全局变量的定义在各模块中是否一致。
局部数据结构
l 在模块工作过程中,必须测试模块内部的数据能否保持完整性,包括内部数据的内容、形式及相互关系不发生错误。
l 对于局部数据结构,应该在单元测试中注意发现以下几类错误:
(1)不正确的或不一致的类型说明。
(2)错误的初始化或默认值。
(3)错误的变量名,如拼写错误或书写错误。
(4)下溢、上溢或者地址错误。
路径测试
l 在单元测试中,最主要的测试是针对路径的测试。测试用例必须能够发现由于计算错误、不正确的判定或不正常的控制流而产生的错误。
l 常见的错误有:
误解的或不正确的算术优先级;混合模式的运算;错误的初始化;精确度不够精确;表达式的不正确符号表示。
l 针对判定和条件覆盖,测试用例还要能够发现如下错误:
不同数据类型的比较;不正确的逻辑操作或优先级;应当相等的地方由于精确度的错误而不能相等;不正确的判定或不正确的变量;不正确的或不存在的循环终止;当遇到分支循环时不能退出;不适当地修改循环变量。
边界条件
l 边界测试是单元测试的最后一步,必须采用边界值分析方法来设计测试用例,认真仔细地测试为限制数据处理而设置的边界处,看模块是否能够正常工作。
l 一些可能与边界有关的数据类型如数值、字符、位置、数量、尺寸等,还要注意这些边界的首个、最后一个、最大值、最小值、最长、最短、最高、最低等特征。
l 在边界条件测试中,应设计测试用例检查以下情况:
(1)在n次循环的第0次、1次、n次是否有错误。
(2)运算或判断中取最大值、最小值时是否有错误。
(3)数据流、控制流中刚好等于、大于、小于确定的比较值是否出现错误。
出错处理
l 测试出错处理的重点是模块在工作中发生了错误,其中的出错处理设施是否有效。
l 检验程序中的出错处理可能面对的情况有:
(1)对运行发生的错误描述难以理解。
(2)所报告的错误与实际遇到的错误不一致。
(3)出错后,在错误处理之前就引起系统的干预。
(4)例外条件的处理不正确。
(5)提供的错误信息不足,以至于无法找到错误的原因。
2.3.2 单元测试的执行过程
l 何时进行单元测试?单元测试常常是和代码编写工作同时进行的,在完成了程序编写、复查和语法正确性验证后,就应进行单元测试用例设计。
l 在单元测试时,如果模块不是独立的程序,需要设置一些辅助测试模块。辅助测试模块有两种:
(1)驱动模块(Drive) 用来模拟被测试模块的上一级模块,相当于被测模块的主程序。它接收数据,将相关数据传送给被测模块,启动被测模块,并打印出相应的结果。
(2)桩模块(Stub) 用来模拟被测模块工作过程中所调用的模块。它们一般只进行很少的数据处理。
l 驱动模块和桩模块都是额外的开销,虽然在单元测试中必须编写,但并不需要作为最终的产品提供给用户。
l 被测模块、驱动模块和桩模块共同构成了一个如下图所示的单元测试的测试环境:
2.4 集成测试
2.4.1 非增量式测试
l 实例 采用非增量式测试方法进行集成测试
l 非增量式测试是采用一步到位的方法来构造测试:
——对所有模块进行个别的单元测试后,按照程序结构图将各模块连接起来,把连接后的程序当作一个整体进行测试。
l 采用非增量式测试方法进行集成测试
l 非增量式测试的缺点:
——当一次集成的模块较多时,非增量式测试容易出现混乱,因为测试时可能发现了许多故障,为每一个故障定位和纠正非常困难,并且在修正一个故障的同时,可能又引入了新的故障,新旧故障混杂,很难判定出错的具体原因和位置。
2.4.2 增量式测试
l 增量式测试的集成是逐步实现的:
——逐次将未曾集成测试的模块和已经集成测试的模块(或子系统)结合成程序包,再将这些模块集成为较大系统,在集成的过程中边连接边测试,以发现连接过程中产生的问题。
l 按照不同的实施次序,增量式集成测试又可以分为三种不同的方法:
(1)自顶向下增量式测试
(2)自底向上增量式测试
(3)混合增量式测试
自顶向下增量式测试
l 自顶向下增量式测试表示逐步集成和逐步测试是按照结构图自上而下进行的,即模块集成的顺序是首先集成主控模块(主程序),然后依照控制层次结构向下进行集成。从属于主控模块的按深度优先方式(纵向)或者广度优先方式(横向)集成到结构中去。
l 深度优先方式的集成:
——首先集成在结构中的一个主控路径下的所有模块,主控路径的选择是任意的。
l 广度优先方式的集成:
——首先沿着水平方向,把每一层中所有直接隶属于上一层的模块集成起来,直到底层。
集成测试的整个过程由3个步骤完成:
(1)主控模块作为测试驱动器。
(2)根据集成的方式(深度或广度),下层的桩模块一次一次地被替换为真正的模块。
(3)在每个模块被集成时,都必须进行单元测试。重复第2步,直到整个系统被测试完成。
l 实例 按照广度优先方式进行集成测试
l 实例 按照深度优先方式进行集成测试
自底向上增量式测试
l 自底向上增量式测试表示逐步集成和逐步测试的工作是按结构图自下而上进行的,即从程序模块结构的最底层模块开始集成和测试。
l 由于是从最底层开始集成,对于一个给定层次的模块,它的子模块(包括子模块的所有下属模块)已经集成并测试完成,所以不再需要使用桩模块进行辅助测试。在模块的测试过程中需要从子模块得到的信息可以直接运行子模块得到。
l 实例 采用自底向上增量式测试方法进行集成测试
混合增量式测试
l 混合增量式测试是把自顶向下测试和自底向上测试这两种方式结合起来进行集成和测试。这样可以兼具两者的优点,而摒弃其缺点。
l 常见的两种混合增量式测试方式:
(1)衍变的自顶向下的增量式测试:基本思想是强化对输入/输出模块和引入新算法模块的测试,并自底向上集成为功能相对完整且相对独立的子系统,然后由主模块开始自顶向下进行增量式测试。
(2)自底向上-自顶向下的增量式测试:首先对含读操作的子系统自底向上直至根节点模块进行集成和测试,然后对含写操作的子系统做自顶向下的集成与测试。
2.4.3 不同集成测试方法的比较
1、非增量式测试与增量式测试的比较
l 非增量式测试的方法是先分散测试,然后集中起来再一次完成集成测试。假如在模块的接口处存在错误,只会在最后的集成测试时一下子暴露出来。
l 增量式测试是逐步集成和逐步测试的方法,把可能出现的差错分散暴露出来,便于找出问题和修改。而且一些模块在逐步集成的测试中,得到了较多次的考验,因此,可能会取得较好的测试效果。
结论:增量式测试要比非增量式测试具有一定的优越性。
2、自顶向下与自底向上增量式测试的比较
l 自顶向下增量式测试:
——主要优点在于它可以自然的做到逐步求精,一开始就能让测试者看到系统的框架。
——主要缺点是需要提供桩模块,并且在输入/输出模块接入系统以前,在桩模块中表示测试数据有一定困难。
l 自底向上增量式测试:
——优点在于,由于驱动模块模拟了所有调用参数,即使数据流并未构成有向的非环状图,生成测试数据也无困难。
——主要缺点在于,直到最后一个模块被加进去之后才能看到整个程序(系统)的框架。
2.4.4 回归测试
l 什么是回归测试?
——在集成测试策略的环境中,回归测试是对某些已经进行过的测试的某些子集再重新进行一遍,以保证上述改变不会传播无法预料的副作用或引发新的问题。
——在更广的环境里,回归测试就是用来保证(由于测试或其他原因的)改动不会带来不可预料的行为或另外的错误。
l 回归测试可以通过重新执行所有的测试用例的一个子集人工地进行,也可以使用自动化的捕获回放工具来进行。
l 回归测试集包括三种不同类型的测试用例:
(1)能够测试软件的所有功能的代表性测试用例
(2)专门针对可能会被修改而影响软件功能的附加测试
(3)针对修改过的软件成分的测试
2.5 确认测试
1、确认测试的准则
l 确认测试也称为合格性测试,是检验所开发的软件是否能按用户提出的要求进行。软件确认要通过一系列证明软件功能和要求一致的黑盒测试来完成。
l 经过确认测试,应该为已开发的软件给出结论性评价:
(1)经过检验的软件的功能、性能及其他要求均已满足需求规格说明书的规定,则可被认为是合格的软件。
(2)经过检验发现与需求说明书有相当的偏离,得到一个各项缺陷清单。
2、配置审查的内容
l 确认测试过程的重要环节就是配置审查工作。其目的在于确保已开发软件的所有文件资料均已编写齐全,并得到分类编目,足以支持运行以后的软件维护工作。
l 配置审查的文件资料包括用户所需的以下资料:
(1)用户手册
(2)操作手册
(3)设计资料——如:设计说明书、源程序以及测试资料(测试说明书、测试报告)等
2.6 系统测试
l 为什么要进行系统测试?
——由于软件只是计算机系统中的一个组成部分,软件开发完成之后,最终还要和系统中的硬件系统、某些支持软件、数据信息等其他部分配套运行。因此,在投入运行前要完成系统测试,以保证各组成部分不仅能单独的得到检验,而且在系统各部分协调工作的环境下也能正常工作。
l 尽管每一个检验有特定的目标,然而所有的检测工作都要验证系统中每个部分均已得到正确的集成,并能完成指定的功能。
l 严格的说,系统测试超出了软件工程范围。通常这项工作并不由系统开发人员或系统开发组织来承担,而是由软件用户或软件开发机构委托独立测试机构来完成。
恢复测试
l 恢复测试是通过各种手段,强制性地使软件出错,使其不能正常工作,进而检验系统的恢复能力。
l 恢复测试包含的内容:
——如果系统恢复是自动的(由系统自身完成),则应该检验:重新初始化、检验点设置机构、数据恢复以及重新启动是否正确。
——如果这一恢复需要人为干预,则应考虑平均修复时间是否在限定的、可以接受的范围之内。
安全测试
l 安全测试的目的在于验证安装在系统内的保护机制能否在实际中保护系统且不受非法入侵,不受各种非法干扰。
l 在安全测试中,测试者扮演着试图攻击系统的个人角色:
— 尝试去通过外部的手段来获取系统的密码
— 使用可以瓦解任何防守的客户软件来攻击系统
— 把系统“瘫痪”,使得其他用户无法访问
— 有目的地引发系统错误,期望在恢复过程中侵入系统
— 通过浏览非保密的数据,从中找到进入系统的钥匙
l 系统的安全测试要设置一些测试用例试图突破系统的安全保密措施,检验系统是否有安全保密的漏洞。
强度测试
l 从本质上来说,强度测试(也称压力测试-Stree Testing)的目的是要检测非正常的情形,测试是想要破坏程序。
强度测试需要在反常规数据量、频率或资源的方式下运行系统,以检验系统能力的最高实际限度。
l 举例:
— 如果正常的中断频率为每秒5次,强度测试设计为每秒50次中断。
— 把输入数据的量提高一个数量级来测试输入功能会如何响应。
— 若某系统正常运行可支持200个终端并行工作,强度测试则检验1000个终端并行工作的情况。
— 运行大量的消耗内存或其他系统资源的测试实例。
性能测试
l 性能测试用来测试软件在系统集成中的运行性能,特别是针对实时系统和嵌入式系统,仅提供符合功能需求但不符合性能需求的软件是不能被接受的。
l 性能测试可以在测试过程的任意阶段进行,但只有当整个系统的所有成分都集成在一起后,才能检查一个系统的真正性能。
l 性能测试常常和强度(压力)测试结合起来进行,而且常常需要硬件和软件测试设备,这就是说,常常有必要在一种苛刻的环境中衡量资源的使用(比如,处理器周期)。
正确性测试
l 正确性测试检查软件的功能是否符合规格说明。
l 正确性测试的方法:
——枚举法,即构造一些合理输入,检查是否得到期望的输出。测试时应尽量设法减少枚举的次数,关键在于寻找等价区间,因为在等价区间中,只需用任意值测试一次即可。
——边界值测试,即采用定义域或者等价区间的边界值进行测试。因为程序设计容易疏忽边界情况,程序也容易在边界值处出错。
可靠性测试
l 可靠性测试是从验证的角度出发,检验系统的可靠性是否达到预期的目标,同时给出当前系统可能的可靠性增长情况。
l 对可靠性性测试来说,最关键的测试数据包括失效间隔时间,失效修复时间,失效数量,失效级别等。根据获得的测试数据,应用可靠性模型,可以得到系统的失效率及可靠性增长趋势。
l 可靠性指标有时很难测试,通常采用平均无故障时间或系统投入运行后出现的故障不能大于多少数量这些指标来对可靠性进行评估。
兼容性测试
l 软件兼容性测试是检测各软件之间能否正确地交互和共享信息,其目标是保证软件按照用户期望的方式进行交互,使用其它软件检查软件操作的过程。
l 兼容性的测试通常需要解决以下问题:
(1)新开发的软件需要与哪种操作系统、Web浏览器和应用软件保持兼容,如果要测试的软件是一个平台,那么要求应用程序能在其上运行。
(2)应该遵守哪种定义软件之间交互的标准或者规范。
(3)软件使用何种数据与其它平台、与新的软件进行交互和共享信息。
2.7 验收测试
2.7.1 验收测试的内容
l 软件验收测试应完成的工作内容包括:
(1)明确验收项目,规定验收测试通过的标准。
(2)确定测试方法。
(3)决定验收测试的组织机构和可利用的资源。
(4)选定测试结果分析方法。
(5)指定验收测试计划并进行评审。
(6)设计验收测试所用的测试用例。
(7)审查验收测试准备工作。
(8)执行验收测试。
(9)分析测试结果。
(10)做出验收结论,明确通过验收或不通过验收。
在验收测试计划当中,可能包括的检验方面有以下几种:
l 功能测试。如完整的工资计算过程。
l 逆向测试。如检验不符合要求数据而引起出错的恢复能力。
l 特殊情况。如极限测试、不存在的路径测试。
l 文档检查。
l 强度检查。如大批量的数据或者最大用户并发使用。
l 恢复测试。如硬件故障或用户不良数据引起的一些情况。
l 可维护性的评价。
l 用户操作测试。如启动、退出系统等。
l 用户友好性检验。
l 安全测试。
2.7.2 软件配置和文档资料测试
l 对文档的测试包括以下内容:
(1)检查产品说明书属性
(2)检查是否完整
(3)检查是否准确
(4)检查是否精确
(5)检查是否一致
(6)检查是否贴切
(7)检查是否合理
(8)检查代码无关
(9)检查可测试性
2.8 测试后的调试
l 软件调试和软件测试有完全不同的含义:
——测试的目的是显示存在错误。
——调试的目的是发现错误或导致程序失效的错误原因,并修改程序以修正错误。
l 通常情况是在测试以后紧接着要进行调试,调试是在测试发现错误后消除错误的过程。实际上这两项工作是交叉进行的。
2.9 面向对象的软件测试
1、面向对象的软件测试
l 面向对象软件测试的目标与传统测试一样,即用尽可能低的测试成本和尽可能少的测试用例,发现尽可能多的软件缺陷。面向对象的测试策略也遵循从“小型测试”到“大型测试”,即从单元测试到最终的功能性测试和系统性测试。
l 但面向对象技术所独有的封装、继承、多态等新特点给测试带来一系列新的问题,增加了测试的难度。与传统的面向过程程序设计相比,面向对象程序设计产生错误的可能性增大,或者使得传统软件测试中的重点不再那么突出,或者使得原来测试经验和实践证明的次要方面成为了主要问题。
2、面向对象的单元测试
l 与传统的单元不同,面向对象软件测试中的单元是封装的类和对象。每个类和类的实例(对象)包含了属性和操作这些属性的方法。
l 类包含一组不同的操作,并且某个或某些特殊操作可能作为一组不同的类的一部分而存在,测试时不再测试单个孤立的操作,而是测试操作类及类的一部分,单元测试的意义发生了较大的变化。
l 对面向对象软件的类测试等价于对面向过程软件的单元测试。传统的单元测试主要关注模块的算法和模块接口间数据的流动,即输入和输出;而面向对象软件的类测试主要是测试封装在类中的操作以及类的状态行为。
3、面向对象的集成测试
l 面向对象的集成测试通常需要进行两级集成:
(1)将成员函数集成到完整类中;
(2)将类与其它类集成。
l 对面向对象的集成测试有两种不同的策略:
(1)基于线程的测试。集成针对回应系统的一个输入或事件所需的一组类,每个线程被集成并分别进行测试。
(2)基于使用的测试。首先测试独立的类,并开始构造系统,然后测试下一层的依赖类(使用独立类的类),通过依赖类层次的测试序列逐步构造完整的系统。
4、面向对象的确认测试
l 与传统的确认测试一样,面向对象软件的有效性集中在用户可见的动作(事件驱动与过程)和用户可识别的系统输出(结果),通过测试检验软件是否满足用户的需求。
l 在面向对象的确认测试中,通常采用传统的黑盒测试方法,以证明软件功能和需求的一致性。
第3章 黑盒测试及其用例的设计
3.1 测试用例设计概述
3.1.1 测试用例的定义和特征
l 测试用例的定义:
(1)测试用例是为特定的目的而设计的一组测试输入、 执行条件和预期的结果。
(2)测试用例是执行的最小实体。
l 测试用例的特征:
(1)最有可能抓住错误的;
(2)不是重复的、多余的;
(3)一组相似测试用例中最有效的;
(4)既不是太简单,也不是太复杂。
3.1.2 设计测试用例的基本准则
l 测试用例的代表性
能够代表并覆盖各种合理的和不合理的、合法的和非法的、边界的和越界的以及极限的输入数据、操作和环境设置等。
l 测试结果的可判定性
即测试执行结果的正确性是可判定的,每一个测试用例都应有相应的期望结果。
l 测试结果的可再现性
即对同样的测试用例,系统的执行结果应当是相同的。
3.1.3 设计测试用例的着眼点
l 根据产品规格,测试基本功能;
l 考虑设计一般用户(非专业人员)的使用方案;
l 考虑设计稀有或特殊的使用方案;
l 与系统其他组成部分的配合(如FAX和上网可能要用到MODEM,测试中考虑对设备的共享);
l 考虑特殊情况(如内存和硬件的冲突等);
l 设计极端情况(如内存泄漏、破坏性测试等);
l 好的测试用例集能花费最小的代价(人力、物力、财力、时间)做最好的测试。
3.1.4 测试用例设计书写标准
在ANSI/IEEE829-1983标准中列出了和测试设计相关的测试用例编写规范和模板。标准模板中主要元素如下:
l 标识符——惟一标识每一个测试用例
l 测试项——准确的描述所需要测试的项及其特征
l 测试环境要求——表征执行该测试用例需要的测试环境
l 输入标准——执行测试用例的输入需求(这些输入可能包括数据、文件或者操作)
l 输出标准——按照指定的环境和输入标准得到的期望输出结果
l 测试用例之间的关联——标识该测试用例与其它的测试(或其它测试用例)之间的依赖关系
3.2 黑盒测试法的概念
l 黑盒测试被称为功能测试或数据驱动测试。在测试时,把被测程序视为一个不能打开的黑盒子,在完全不考虑程序内部结构和内部特性的情况下进行。
l 采用黑盒测试的目的主要是在已知软件产品所应具有的功能的基础上,进行:
(1)检查程序功能能否按需求规格说明书的规定正常使用,测试各个功能是否有遗漏,检测性能等特性要求是否满足。
(2)检测人机交互是否错误,检测数据结构或外部数据库访问是否错误,程序是否能适当地接收输入数据而产生正确的输出结果,并保持外部信息(如数据库或文件)的完整性。
(3)检测程序初始化和终止方面的错误。
3.3 三角形问题与NextDate函数
1、三角形问题
输入三个整数a、b、c,分别作为三角形的三条边,现通过程序判断由三条边构成的三角形的类型为等边三角形、等腰三角形、一般三角形(特殊的还有直角三角形),以及构不成三角形。
现在要求输入三个整数a、b、c,必须满足以下条件:
条件1 1≤a≤100 条件4 a<b+ c
条件2 1≤b≤100 条件5 b<a+ c
条件3 1≤c≤100 条件6 c<a+ b
l 如果输入值a、b、c不满足条件1、条件2和条件3 ,程序给出“边的取值超出允许范围”的信息。
l 如果输入值a、b、c 满足条件1、条件2和条件3,则输出下列四种情况之一:
(1)如果不满足条件4、条件5和条件6中的一个,则程序输出为“非三角形”。
(2)如果三条边相等,则程序输出为“等边三角形”。
(3)如果恰好有两条边相等,则程序输出为“等腰三角形”。
(4)如果三条边都不相等,则程序输出为“一般三角形”。
l 结论:三角形问题的复杂之处在于输入与输出之间的关系比较复杂。
2、NextDate函数
NextDate函数说明另一种复杂的关系,即输入变量之间逻辑关系的复杂性。
NextDate函数包含三个变量month、day和year,函数的输出为输入日期后一天的日期。 要求输入变量month、day和year均为整数值,并且满足下列条件:
条件1 1≤ month≤12
条件2 1≤ day ≤31
条件3 1912≤ year≤2050
l 结论:在NextDate函数中有两种复杂性的输入来源,一是输入域的复杂性,二是确定闰年的规则并要增加“额外天”。
3.4 等价类划分法
l 等价类划分法是一种重要的、常用的黑盒测试方法,它将不能穷举的测试过程进行合理分类,从而保证设计出来的测试用例具有完整性和代表性。
l 举例:设计这样的测试用例,来实现一个对所有实数进行开平方运算( y =sqrt(x) )的程序的测试。
l 思考方向:由于开平方运算只对非负实数有效,这时需要将所有的实数(输入域x)进行划分,可以分成:正实数、0 和 负实数。假设我们选定+1.4444代表正实数,-2.345代表负实数,则为该程序设计的测试用例的输入为+1.4444、 0 和 -2.345。
l 等价类划分法是把所有可能的输入数据,即程序的输入域划分为若干部分(子集),然后从每一个子集中选取少数具有代表性的数据作为测试用例。
l 所谓等价类是指某个输入域的子集合。在该子集合中,各个输入数据对于揭露程序中的错误都是等效的,它们具有等价特性,即每一类的代表性数据在测试中的作用都等价于这一类中的其它数据。这样,对于表征该类的数据输入将能代表整个子集合的输入。因此,可以合理的假定:——测试某等价类的代表值就是等效于对于这一类其它值的测试。
3.4.1 等价类的划分原则
l 等价类是输入域的某个子集合,而所有等价类的并集就是整个输入域。因此,等价类对于测试有两个重要的意义:
? 完备性——整个输入域提供一种形式的完备性
? 无冗余性——若互不相交则可保证一种形式的无冗余性
l 如何划分?——先从程序的规格说明书中找出各个输入条件,再为每个输入条件划分两个或多个等价类,形成若干的互不相交的子集。
l 采用等价类划分法设计测试用例通常分两步进行:
(1)确定等价类,列出等价类表。
(2)确定测试用例。
l 划分等价类可分为两种情况:
(1)有效等价类
是指对软件规格说明而言,是有意义的、合理的输入数据所组成的集合。利用有效等价类,能够检验程序是否实现了规格说明中预先规定的功能和性能。
(2)无效等价类
是指对软件规格说明而言,是无意义的、不合理的输入数据所构成的集合。利用无效等价类,可以鉴别程序异常处理的情况,检查被测对象的功能和性能的实现是否有不符合规格说明要求的地方。
l 进行等价类划分的依据:
(1)按照区间划分——在输入条件规定了取值范围或值的个数的情况下,可以确定一个有效等价类和两个无效等价类。
例:程序输入条件为小于100大于10的整数x,则有效等价类为10<x<100,两个无效等价类为x≤10和x≥100。
(2)按照数值划分——在规定了一组输入数据(假设包括n个输入值),并且程序要对每一个输入值分别进行处理的情况下,可确定n个有效等价类(每个值确定一个有效等价类)和一个无效等价类(所有不允许的输入值的集合)。
例:程序输入x取值于一个固定的枚举类型{1,3,7,15},且程序 中对这4个数值分别进行了处理,则有效等价类为x=1、x=3、x=7、x=15,无效等价类为x≠1,3,7,15的值的集合。
(3)按照数值集合划分——在输入条件规定了输入值的集合或规定了“必须如何”的条件下,可以确定一个有效等价类和一个无效等价类(该集合有效值之外)。
例:程序输入条件为取值为奇数的整数x,则有效等价类为x的值为奇数的整数,无效等价类为x的值不为奇数的整数。
(4)按照限制条件或规则划分——在规定了输入数据必须遵守的规则或限制条件的情况下,可确定一个有效等价类(符合规则)和若干个无效等价类(从不同角度违反规则)。
例:程序输入条件为以字符‘a’开头、长度为8的字符串,并且字符串不包含‘a’~ ‘z’之外的其它字符,则有效等价类为满足了上述所有条件的字符串,无效等价类为不以‘a’开头的字符串、长度不为8的字符串和包含了‘a’~ ‘z’之外其它字符的字符串。
(5)细分等价类——在确知已划分的等价类中各元素在程序中的处理方式不同的情况下,则应再将该等价类进一步划分为更小的等价类,并建立等价类表。
3.4.2 等价类划分法的测试用例设计
l 在设计测试用例时,应同时考虑有效等价类和无效等价类测试用例的设计。
l 根据已列出的等价类表可确定测试用例,具体过程如下:
(1)首先为等价类表中的每一个等价类分别规定一个唯一的编号。
(2)设计一个新的测试用例,使它能够尽量覆盖尚未覆盖的有效等价类。重复这个步骤,直到所有的有效等价类均被测试用例所覆盖。
(3)设计一个新的测试用例,使它仅覆盖一个尚未覆盖的无效等价类。重复这一步骤,直到所有的无效等价类均被测试用例所覆盖。
3.4.3 常见等价类划分测试形式
l 针对是否对无效数据进行测试,可以将等价类测试分为 标准等价类测试和健壮等价类测试。
? 标准等价类测试——不考虑无效数据值,测试用例使用 每个等价类中的一个值。
? 健壮等价类测试——主要的出发点是考虑了无效等价类。对有效输入,测试用例从每个有效等价类中取一个值; 对无效输入,一个测试用例有一个无效值,其他值均取 有效值。
健壮等价类测试存在两个问题:
(1)需要花费精力定义无效测试用例的期望输出
(2)对强类型的语言没有必要考虑无效的输入
3.4.4 使用等价类划分法测试的实例
实例1 三角形问题
分析:
在多数情况下,是从输入域划分等价类的,但并非不能从被测程序的输出域反过来定义等价类,事实上,这对于三角形问题却是最简单的划分方法。 在三角形问题中,有四种可能的输出:等边三角形、等腰三角形、一般三角形和非三角形。利用这些信息能够确定下列输出(值域)等价类。
R1 = { <a,b,c>: 边为a,b,c的等边三角形 }
R2 = { <a,b,c>: 边为a,b,c的等腰三角形 }
R3 = { <a,b,c>: 边为a,b,c的一般三角形 }
R4 = { <a,b,c>: 边为a,b,c不能组成三角形 }
三角形问题的4个标准等价类测试用例
测试用例 | a | b | c | 预期输出 |
Test1 | 10 | 10 | 10 | 等边三角形 |
Test2 | 10 | 10 | 5 | 等腰三角形 |
Test3 | 3 | 4 | 5 | 一般三角形 |
Test4 | 4 | 1 | 2 | 非三角形 |
三角形问题的7个健壮等价类测试用例
测试用例 | a | b | c | 预期输出 |
Test1 | 5 | 6 | 7 | 一般三角形 |
Test2 | -1 | 5 | 5 | a值超出输入值定义域 |
Test3 | 5 | -1 | 5 | b值超出输入值定义域 |
Test4 | 5 | 5 | -1 | c值超出输入值定义域 |
Test5 | 101 | 5 | 5 | a值超出输入值定义域 |
Test6 | 5 | 101 | 5 | b值超出输入值定义域 |
Test7 | 5 | 5 | 101 | c值超出输入值定义域 |
实例2 保险公司计算保费费率的程序
某保险公司的人寿保险的保费计算方式为:投保额×保险费率
其中,保险费率依点数不同而有别,10点及10点以上保险费率为0.6%,10点以下保险费率为0.1%;而点数又是由投保人的年龄、性别、婚姻状况和抚养人数来决定,具体规则如下:
年龄 | 性别 | 婚姻 | 抚养人数 | ||||
20~39 | 40~59 | 其它 | M | F | 已婚 | 未婚 | 1人扣0.5点,最多扣3点 (四舍五入取整) |
6点 | 4点 | 2点 | 5点 | 3点 | 3点 | 5点 |
计算保费费率的程序
(1)分析程序规格说明中给出和隐含的对输入条件的要求,列出等价类表(包括有效等价类和无效等价类)。
l 年龄:一位或两位非零整数,值的有效范围为1~99
l 性别:一位英文字符,只能取值‘M’或’F’
l 婚姻:字符,只能取值‘已婚’或‘未婚’
l 抚养人数:空白或一位非零整数(1~9)
l 点数 :一位或两位非零整数,值的范围为1~99
输入条件 | 有效等价类 | 编号 | 无效等价类 | 编号 |
年龄 | 20~39岁 | 1 |
|
|
40~59岁 | 2 |
|
| |
1~19岁 60~99岁 | 3 | 小于1 | 12 | |
大于99 | 13 | |||
性别 | 单个英文字符 | 4 | 非英文字符 | 14 |
非单个英文字符 | 15 | |||
M | 5 | 除‘M’和‘F’之外的其它单个字符 | 16 | |
F | 6 | |||
婚姻 | 已婚 | 7 | 除‘已婚’和‘未婚’之外的其它字符 | 17 |
未婚 | 8 | |||
抚养人数 | 空白 | 9 | 除空白和数字之外的其它字符 | 18 |
1~6人 | 10 | 小于1 | 19 | |
6~9人 | 11 | 大于9 | 20 |
(2)根据(1)中的等价类表,设计能覆盖所有等价类的测试用例。
测试用例编号 | 输入数据 | 预期输出 | |||
年龄 | 性别 | 婚姻 | 抚养人数 | 保险费率 | |
1 | 27 | F | 未婚 | 空白 | 0.6% |
2 | 50 | M | 已婚 | 2 | 0.6% |
3 | 70 | F | 已婚 | 7 | 0.1% |
4 | 0 | M | 未婚 | 空白 | 无法推算 |
5 | 100 | F | 已婚 | 3 | 无法推算 |
6 | 99 | 男 | 已婚 | 4 | 无法推算 |
7 | 1 | Child | 未婚 | 空白 | 无法推算 |
8 | 45 | N | 已婚 | 5 | 无法推算 |
9 | 38 | F | 离婚 | 1 | 无法推算 |
10 | 62 | M | 已婚 | 没有 | 无法推算 |
11 | 18 | F | 未婚 | 0 | 无法推算 |
12 | 40 | M | 未婚 | 10 | 无法推算 |
3.5 边界值分析法
3.5.1 边界值分析法概要
l 边界值分析法就是对输入或输出的边界值进行测试的一种黑盒测试方法。通常边界值分析法是作为对等价类划分法的补充,这种情况下,其测试用例来自等价类的边界。
l 为什么使用边界值分析法?
无数的测试实践表明,大量的故障往往发生在输入定义域或输出值域的边界上,而不是在其内部。因此,针对各种边界情况设计测试用例,通常会取得很好的测试效果。
l 怎样用边界值分析法设计测试用例?
(1)首先确定边界情况。通常输入或输出等价类的边界就是应该着重测试的边界情况。
(2)选取正好等于、刚刚大于或刚刚小于边界的值作为测试数据,而不是选取等价类中的典型值或任意值。
边界值分析
l 边界值分析使用与等价类划分法相同的划分,只是边界值分析假定错误更多地存在于划分的边界上,因此在等价类的边界上以及两侧的情况设计测试用例。
l 例:测试计算平方根的函数
——输入:实数
——输出:实数
——规格说明:当输入一个0或比0大的数的时候,返回其正平方根;当输入一个小于0的数时,显示错误信息“平方根非法-输入值小于0”并返回0;库函数Print-Line可以用来输出错误信息。
实例分析
l 等价类划分:
- 可以考虑作出如下划分:
ü 输入 (i)<0 和 (ii)>=0
ü 输出 (a)>=0 和 (b) Error
- 测试用例有两个:
ü 输入4,输出2。对应于 (ii) 和 (a) 。
ü 输入-10,输出0和错误提示。对应于(i)和(b)
l 边界值分析:
- 划分(ii)的边界为0和最大正实数;划分(i)的边界为最小负实数和0。由此得到以下测试用例:
ü 输入 {最小负实数}
ü 输入 {绝对值很小的负数}
ü 输入 0
ü 输入 {绝对值很小的正数}
ü 输入 {最大正实数}
l 通常情况下,软件测试所包含的边界检验有几种类型:
数字、字符、位置、质量、大小、速度、方位、尺寸、空间等
l 相应地,以上类型的边界值应该在:
最大/最小、首位/末位、上/下、最快/最慢、最高/最低、最短/最长、空/满等情况下
选择测试用例的原则
(1)如果输入条件规定了值的范围,则应取刚达到这个范围的边界值以及刚刚超过这个范围边界的值作为测试输入数据。
(2)如果输入条件规定了值的个数,则用最大个数、最小个数和比最大个数多1个、比最小个数少1个的数作为测试数据。
(3)根据程序规格说明的每个输出条件,使用原则 (1)。
(4)根据程序规格说明的每个输出条件,使用原则 (2) 。
(5)如果程序的规格说明给出的输入域或输出域是有序集合(如有序表、顺序文件等),则应选取集合中的第一个和最后一个元素作为测试用例。
(6)如果程序中使用了一个内部数据结构,则应当选择这个内部数据结构的边界上的值作为测试用例。
(7)分析程序规格说明,找出其它可能的边界条件。
3.5.2 边界值分析法测试用例
l 采用边界值分析测试的基本思想是:故障往往出现在输入变量的边界值附近。
——因此,边界值分析法利用输入变量的最小值(min)、略大于最小值(min+)、输入值域内的任意值(nom)、略小于最大值(max-)和最大值(max)来设计测试用例。
l 边界值分析法是基于可靠性理论中称为“单故障”的假设,即有两个或两个以上故障同时出现而导致软件失效的情况很少,也就是说,软件失效基本上是由单故障引起的。
——因此,在边界值分析法中获取测试用例的方法是:
(1) 每次保留程序中一个变量,让其余的变量取正常值,被保留的变量依次取min、min+、nom、max-和max。
(2) 对程序中的每个变量重复 (1) 。
例:有二元函数f(x,y),其中x∈[1,12],y∈[1,31]。则采用边界值分析法设计的测试用例是:
{ <1,15>, <2,15>, <11,15>,<12,15>, <6,15>, <6,1>, <6,2>, <6,30>, <6,31>}
? 推论:对于一个含有n个变量的程序,采用边界值分析法测试程序会产生4n+1个测试用例。
健壮性测试
l 健壮性测试是作为边界值分析的一个简单的扩充,它除了对变量的5个边界值分析取值外,还需要增加一个略大于最大值(max+)以及略小于最小值(min-)的取值,检查超过极限值时系统的情况。因此,对于有n个变量的函数采用健壮性测试需要6n+1个测试用例。
3.5.3 边界值分析法测试举例
实例1 三角形问题的边界值分析测试用例
在三角形问题描述中,除了要求边长是整数外,没有给出其它的限制条件。在此,我们将三角形每边边长的取范围值设值为[1, 100] 。
说明:如果程序规格说明中没有显式地给出边界值,则可以在设计测试用例前先设定取值的下限值和上限值。
测试用例 | a | b | c | 预期输出 |
Test1 | 60 | 60 | 1 | 等腰三角形 |
Test2 | 60 | 60 | 2 | 等腰三角形 |
Test3 | 60 | 60 | 60 | 等边三角形 |
Test4 | 50 | 50 | 99 | 等腰三角形 |
Test5 | 50 | 50 | 100 | 非三角形 |
Test6 | 60 | 1 | 60 | 等腰三角形 |
Test7 | 60 | 2 | 60 | 等腰三角形 |
Test8 | 50 | 99 | 50 | 等腰三角形 |
Test9 | 50 | 100 | 50 | 非三角形 |
Test10 | 1 | 60 | 60 | 等腰三角形 |
Test11 | 2 | 60 | 60 | 等腰三角形 |
Test12 | 99 | 50 | 50 | 等腰三角形 |
Test13 | 100 | 50 | 50 | 非三角形 |
实例2 NextDate函数的边界值分析测试用例
在NextDate函数中,隐含规定了变量mouth和变量day的取值范围为1≤mouth≤12和1≤day≤31,并设定变量year的取值范围为1912≤year≤2050 。
测试用例 | Month | Day | Year | 预期输出 |
Test1 | 6 | 15 | 1911 | 1911.6.16 |
Test2 | 6 | 15 | 1912 | 1912.6.16 |
Test3 | 6 | 15 | 1913 | 1913.6.16 |
Test4 | 6 | 15 | 1975 | 1975.6.16 |
Test5 | 6 | 15 | 2049 | 2049.6.15 |
Test6 | 6 | 15 | 2050 | 2050.6.15 |
Test7 | 6 | 15 | 2051 | 2051.6.15 |
Test8 | 6 | -1 | 2001 | day超出[1…31] |
Test9 | 6 | 1 | 2001 | 2001.6.1 |
Test10 | 6 | 2 | 2001 | 2001.6.2 |
Test11 | 6 | 30 | 2001 | 2001.6.30 |
Test12 | 6 | 31 | 2001 | 输入日期超界 |
Test13 | 6 | 32 | 2001 | day超出[1…31] |
Test14 | -1 | 15 | 2001 | Mouth超出[1…12] |
Test15 | 1 | 15 | 2001 | 20011.15 |
Test16 | 2 | 15 | 2001 | 2001.2.15 |
Test17 | 11 | 15 | 2001 | 2001.11.15 |
Test18 | 12 | 15 | 2001 | 2001.12.15 |
Test19 | 13 | 15 | 2001 | Mouth超出[1…12] |
3.6 因果图法
3.6.1 因果图法的简介
l 因果图法产生的背景:
等价类划分法和边界值分析方法都是着重考虑输入条件,但没有考虑输入条件的各种组合、输入条件之间的相互制约关系。这样虽然各种输入条件可能出错的情况已经测试到了,但多个输入条件组合起来可能出错的情况却被忽视了。
如果在测试时必须考虑输入条件的各种组合,则可能的组合数目将是天文数字,因此必须考虑采用一种适合于描述多种条件的组合、相应产生多个动作的形式来进行测试用例的设计,这就需要利用因果图(逻辑模型)。
l 因果图法是基于这样的一种思想:一些程序的功能可以用判定表(或称决策表)的形式来表示,并根据输入条件的组合情况规定相应的操作。
l 因果图法的定义:是一种利用图解法分析输入的各种组合情况,从而设计测试用例的方法,它适合于检查程序输入条件的各种组合情况。
l 采用因果图法设计测试用例的步骤:
(1)根据程序规格说明书描述,分析并确定因(输入条件)和果(输出结果或程序状态的改变),画出因果图。
(2)将得到的因果图转换为判定表。
(3)为判定表中每一列所表示的情况设计一个测试用例。
l 使用因果图法的优点:
(1)考虑到了输入情况的各种组合以及各个输入情况之间的相互制约关系。
(2)能够帮助测试人员按照一定的步骤,高效率的开发测试用例。
(3)因果图法是将自然语言规格说明转化成形式语言规格说明的一种严格的方法,可以指出规格说明存在的不完整性和二义性。
3.6.2 因果图
l 因果图中用来表示4种因果关系的基本符号:
l 因果图中的4种基本关系
在因果图的基本符号中,图中的左结点ci表示输入状态(或称原因),右结点ei表示输出状态(或称结果)。ci 与 ei 取值0或1,0表示某状态不出现,1则表示某状态出现。
? 恒等:若c1是1,则e1也为1,否则e1为0。
? 非:若c1是1,则e1为0,否则e1为1。
? 或:若c1或c2或c3是1,则e1为1,否则为0。
? 与:若c1和c2都是1,则e1为1,否则e1为0。
l 因果图中的约束
在实际问题中输入状态相互之间、输出状态相互之间可能存在某些依赖关系,称为“约束”。对于输入条件的约束有E、I、O、R四种约束,对于输出条件的约束只有M约束。
? E约束(异):a和b中最多有一个可能为1,即a和b不能同时 为1。
? I 约束(或):a、b、c中至少有一个必须为1,即 a、b、c不能同时为0。
? O约束(唯一):a和b必须有一个且仅有一个为1。
? R约束(要求):a是1时,b必须是1,即a为1时,b不能为0。
? M约束(强制):若结果a为1,则结果b强制为0。
l 因果图中用来表示约束关系的约束符号:
l 因果图法最终生成的是决策表。利用因果图生成测试用例的基本步骤如下:
(1)分析软件规格说明中哪些是原因(即输入条件或输入条件的等价类),哪些是结果(即输出条件),并给每个原因和结果赋予一个标识符。
(2)分析软件规格说明中的语义,找出原因与结果之间、原因与原因之间对应的关系, 根据这些关系画出因果图。
(3)由于语法或环境的限制,有些原因与原因之间、原因与结果之间的组合情况不可能出现。为表明这些特殊情况,在因果图上用一些记号表明约束或限制条件。
(4)把因果图转换为决策表。
(5)根据决策表中的每一列设计测试用例。
3.6.3 因果图法测试举例
l 实例 用因果图法测试以下程序。
程序的规格说明要求:输入的第一个字符必须是#或*,第二个字符必须是一个数字,此情况下进行文件的修改;如果第一个字符不是#或*,则给出信息N,如果第二个字符不是数字,则给出信息M。
? 解题步骤:
(1)分析程序的规格说明,列出原因和结果。
(2)找出原因与结果之间的因果关系、原因与原因之间的约束关系,画出因果图。
(3)将因果图转换成决策表。
(4)根据(3)中的决策表,设计测试用例的输入数据和预期输出。
因果图法测试举例(续)
(1)分析程序规格说明中的原因和结果:
原因 | 结果 |
c1:第一个字符是# | e1:给出信息N |
c2:第一个字符是* | e2:修改文件 |
c3:第二个字符是一个数字 | e3:给出信息M |
(2)画出因果图(编号为10的中间结点是导出结果的进一步原因):
(3)将因果图转换成如下所示的决策表:
规则 选项 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |
条件:C1 | 1 | 1 | 1 | 1 | 0 | 0 | 0 | 0 |
C2 | 1 | 1 | 0 | 0 | 1 | 1 | 0 | 0 |
C3 | 1 | 0 | 1 | 0 | 1 | 0 | 1 | 0 |
10 |
|
| 1 | 1 | 1 | 1 | 0 | 0 |
动作:e1 |
|
|
|
|
|
| √ | √ |
e2 |
|
| √ |
| √ |
|
|
|
e3 |
|
|
| √ |
| √ |
| √ |
不可能 | √ | √ |
|
|
|
|
|
|
测试用例 | #3 | #A | *6 | *B | A1 | GT |
(4)根据决策表中的每一列设计测试用例:
测试用例编号 | 输入数据 | 预期输出 |
1 | #3 | 修改文件 |
2 | #A | 给出信息M |
3 | *6 | 修改文件 |
4 | *B | 给出信息M |
5 | A1 | 给出信息N |
6 | GT | 给出信息N和信息M |
3.7 决策表法
3.7.1 决策表
l 在所有的黑盒测试方法中,基于决策表(也称判定表)的测试是最为严格、最具有逻辑性的测试方法。
l 决策表的概念:决策表是分析和表达多逻辑条件下执行不同操作的情况的工具。
l 决策表的优点:能够将复杂的问题按照各种可能的情况全部列举出来,简明并避免遗漏。因此,利用决策表能够设计出完整的测试用例集合。
l 在一些数据处理问题当中,某些操作的实施依赖于多个逻辑条件的组合,即:针对不同逻辑条件的组合值,分别执行不同的操作。决策表很适合于处理这类问题。
决策表实例——“阅读指南”决策表
规则 选项 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | |
问题 | 觉得疲倦? | Y | Y | Y | Y | N | N | N | N |
感兴趣吗? | Y | Y | N | N | Y | Y | N | N | |
糊涂吗? | Y | N | Y | N | Y | N | Y | N | |
建议 | 重读 |
|
|
|
| √ |
|
|
|
继续 |
|
|
|
|
| √ |
|
| |
跳下一章 |
|
|
|
|
|
| √ | √ | |
休息 | √ | √ | √ | √ |
|
|
|
|
决策表的组成
l 决策表通常由以下4部分组成:
? 条件桩—列出问题的所有条件
? 条件项—针对条件桩给出的条件列出所有可能的取值
? 动作桩—列出问题规定的可能采取的操作
? 动作项—指出在条件项的各组取值情况下应采取的动作
决策表的生成
l 构造决策表的5个步骤:
(1) 确定规则的个数。
? 有n个条件的决策表有2n个规则(每个条件取真、假值)。
(2) 列出所有的条件桩和动作桩。
(3) 填入条件项。
(4) 填入动作项,得到初始决策表。
(5) 简化决策表,合并相似规则。
? 若表中有两条以上规则具有相同的动作,并且在条件项之间存在极为相似的关系,便可以合并。
? 合并后的条件项用符号“-”表示,说明执行的动作与该条件的取值无关,称为无关条件。
三角形问题的决策表
规则 选项 | 规则1-8 | 规则9 | 规则10 | 规则11 | 规则12 | 规则13 | 规则14 | 规则15 | 规则16 |
条件: |
|
|
|
|
|
|
|
|
|
c1:a,b,c构成三角形? | N | Y | Y | Y | Y | Y | Y | Y | Y |
c2:a=b? | - | Y | Y | Y | Y | N | N | N | N |
c3:a=c? | - | Y | Y | N | N | Y | Y | N | N |
c4:b=c? | - | Y | N | Y | N | Y | N | Y | N |
动作: |
|
|
|
|
|
|
|
|
|
a1:非三角形 | √ |
|
|
|
|
|
|
|
|
a2:一般三角形 |
|
|
|
|
|
|
|
| √ |
a3:等腰三角形 |
|
|
|
| √ |
| √ | √ |
|
a4:等边三角形 |
| √ |
|
|
|
|
|
|
|
a5:不可能 |
|
| √ | √ |
| √ |
|
|
|
3.7.2 决策表应用
l NextDate函数的决策表测试用例设计
? 问题分析:NextDate函数的三个变量之间在输入定义域中存在一定的逻辑依赖关系,由于等价类划分和边界值分析测试都假设了变量是独立的,如果采用上述两种方法设计测试用例,那么这些依赖关系在机械的选取输入值时可能会丢失。而采用决策表法则可以通过使用“不可能动作”的概念表示条件的不可能组合,来强调这种依赖关系。
? 说明:当决策表规模(指规则的数目,n个条件的决策表有2n个规则)较大时,可以通过扩展条目决策表(条件使用等价类)、代数简化表、将大表“分解”为小表等方法。
实例说明
NextDate函数
l 为了获得下一个日期,NextDate函数执行如下操作:
? 如果输入日期不是当月最后一天,则把day变量的值加1;
? 如果输入日期是1~11月份中某月的最后一天,则把day变量的值复位为1,month变量的值加1;
? 如果输入日期是12月的最后一天,则day变量和month变量的值都复位为1,year变量的值加1。
l 关于最后一天的判断:
? 如果是有31天的月份(1,3,5,7,8,10,12),day变量值为31;
? 如果是有30天的月份(4,6,9,11), day变量值为30;
? 如果是有29天的月份(闰年的2月),day变量值为29;
? 如果是有28天的月份(非闰年的2月),day变量值为28。
NextDate函数的动作桩和条件桩
l 根据所执行的操作,可列出NextDate函数的动作桩:
a1: 不可能;a2:day加1;a3: day复位;a4: month加1;a5: month复位;a6: year加1
l 考虑到决策表的规模,条件使用month、day、year变量的等价类,在以下等价类集合上建立决策表:
? 对于month变量的取值
M1: {mouth: mouth有30天}; M2:{mouth:mouth有31天,12月除外};
M3: {mouth:mouth有12月}; M4:{mouth:mouth是2月};
? 对于day变量的取值
D1:{day:1≤day≤27}; D2: {day:day=28};
D3: {day:day=29}; D4: {day:day=30}; D5: {day:day=31};
? 对于year变量的取值
Y1:{year:year是闰年}; Y2:{year:year不是闰年}
决策表应用(续)
l 决策表测试法适用于具有以下特征的应用程序:
if-then-else逻辑突出;输入变量之间存在逻辑关系;涉及输入变量子集的计算;输入与输出之间存在因果关系。
l 适用于使用决策表设计测试用例的条件:
? 规格说明以决策表形式给出,或较容易转换为决策表。
? 条件的排列顺序不会也不应影响执行的操作。
? 规则的排列顺序不会也不应影响执行的操作。
? 当某一规则的条件已经满足,并确定要执行的操作后,不必检验别的规则。
? 如果某一规则的条件要执行多个操作,这些操作的执行顺序无关紧要。
3.7.3 决策表测试应用案例
l 用决策表测试法测试以下程序:
该程序有三个输入变量month、day、year(month、day和year均为整数值,并且满足:1≤month≤12和1≤day≤31),分别作为输入日期的月份、日、年份,通过程序可以输出该输入日期在日历上隔一天的日期。例如,输入为2004年11月29日,则该程序的输出为2000年12月1日。
(1)分析各种输入情况,列出为输入变量month、day、year划分的有效等价类。
(2)分析程序规格说明,结合以上等价类划分的情况给出问题规定的可能采取的操作(即列出所有的动作桩)。
(3)根据(1)和(2),画出简化后的决策表。
案例分析
l month变量的有效等价类:
M1: {month=4,6,9,11} M2:{month=1,3,5,7,8,10} M3: {month=12} M4: {month=2}
l day变量的有效等价类:
D1: {1≤day≤26} D2: {day=27} D3: {day=28} D4: {day=29} D5:{day=30} D6: {day=31}
l year变量的有效等价类:
Y1: {year是闰年} Y2: {year不是闰年}
l 考虑各种有效的输入情况,程序中可能采取的操作有以下六种:
a1: day+2 a2: day=2 a3: day=1 a4:month+1 a5: month=1 a6: year+1
3.8 错误推测法
l 错误推测法的概念:基于经验和直觉推测程序中所有可能存在的各种错误,从而有针对性的设计测试用例的方法。
l 错误推测方法的基本思想:列举出程序中所有可能有的错误和容易发生错误的特殊情况,根据它们选择测试用例。例如:
? 在单元测试时曾列出的许多在模块中常见的错误、以前产品测试中曾经发现的错误等,这些就是经验的总结。
? 还有,输入数据和输出数据为0的情况、输入表格为空格或输入表格只有一行等。这些都是容易发生错误的情况,可选择这些情况下的例子作为测试用例。
第4章 白盒测试及其用例的设计
4.1 白盒测试方法
l 为什么要进行白盒测试?
如果所有软件错误的根源都可以追溯到某个唯一原因,那么问题就简单了。然而,事实上一个bug 常常是由多个因素共同导致的。
假设此时开发工作已结束,程序送交到测试组,没有人知道代码中有一个潜在的被 0 除的错误。若测试组采用的测试用例的执行路径没有同时经过x=0和y=5/x进行测试,显然测试工作似乎非常完善,测试用例覆盖了所有执行语句,也没有被 0 除的错误发生。
l 白盒测试也称结构测试或逻辑驱动测试,是针对被测单元内部是如何进行工作的测试。它根据程序的控制结构设计测试用例,主要用于软件或程序验证。
l 白盒测试法检查程序内部逻辑结构,对所有逻辑路径进行测试,是一种穷举路径的测试方法。但即使每条路径都测试过了,仍然可能存在错误。因为:
? 穷举路径测试无法检查出程序本身是否违反了设计规范,即程序是否是一个错误的程序。
? 穷举路径测试不可能查出程序因为遗漏路径而出错。
? 穷举路径测试发现不了一些与数据相关的错误。
l 采用白盒测试方法必须遵循以下几条原则,才能达到测试的目的:
? 保证一个模块中的所有独立路径至少被测试一次。
? 所有逻辑值均需测试真 (true) 和假 (false) 两种情况。
? 检查程序的内部数据结构,保证其结构的有效性。
? 在上下边界及可操作范围内运行所有循环。
l 白盒测试主要是检查程序的内部结构、逻辑、循环和路径。常用测试用例设计方法有:
? 逻辑覆盖法(逻辑驱动测试)
? 基本路径测试方法
4.2 白盒测试的基本概念
4.2.1 控制流图
l 控制流图(可简称流图)是对程序流程图进行简化后得到的,它可以更加突出的表示程序控制流的结构。
l 控制流图中包括两种图形符号:节点和控制流线。
? 节点由带标号的圆圈表示,可代表一个或多个语句、一个处理框序列和一个条件判定框(假设不包含复合条件)。
? 控制流线由带箭头的弧或线表示,可称为边。它代表程序中的控制流。
l 对于复合条件,则可将其分解为多个单个条件,并映射成控制流图。
常见结构的控制流图
其中,包含条件的节点被称为判定节点(也叫谓词节点),由判定节点发出的边必须终止于某一个节点,由边和节点所限定的范围被称为区域。
4.2.2 环形复杂度
l 环形复杂度也称为圈复杂度,它是一种为程序逻辑复杂度提供定量尺度的软件度量。
l 环形复杂度的应用——可以将环形复杂度用于基本路径方法,它可以提供:程序基本集的独立路径数量;确保所有语句至少执行一次的测试数量的上界。
? 独立路径是指程序中至少引入了一个新的处理语句集合或一个新条件的程序通路。采用流图的术语,即独立路径必须至少包含一条在本次定义路径之前不曾用过的边。
l 测试可以被设计为基本路径集的执行过程,但基本路径集通常并不唯一。
计算环形复杂度的方法
l 环形复杂度以图论为基础,为我们提供了非常有用的软件度量。可用如下三种方法之一来计算环形复杂度:
? 控制流图中区域的数量对应于环形复杂度。
? 给定控制流图G的环形复杂度—V(G),定义为V(G) =E-N+2
其中,E是控制流图中边的数量,N是控制流图中的节点数量。
? 给定控制流图G的环形复杂度—V(G),也可定义为 V(G) = P+1
其中,P是控制流图G中判定节点的数量。
4.2.3 图矩阵
l 图矩阵是控制流图的矩阵表示形式。
l 图矩阵是一个方形矩阵,其维数等于控制流图的节点数。矩阵中的每列和每行都对应于标识的节点,矩阵元素对应于节点间的边。
l 通常,控制流图中的结点用数字标识,边则用字母标识。如果在控制流图中从第 i 个结点到第 j 个结点有一个标识为 x 的边相连接,则在对应图矩阵的第 i 行第 j 列有一个非空的元素 x 。
4.3 覆盖测试
4.3.1 测试覆盖率
l 测试覆盖率:用于确定测试所执行到的覆盖项的百分比。其中的覆盖项是指作为测试基础的一个入口或属性,比如语句、分支、条件等。
l 测试覆盖率可以表示出测试的充分性,在测试分析报告中可以作为量化指标的依据,测试覆盖率越高效果越好。但覆盖率不是目标,只是一种手段。
l 测试覆盖率包括功能点覆盖率和结构覆盖率:
? 功能点覆盖率大致用于表示软件已经实现的功能与软件需要实现的功能之间的比例关系。
? 结构覆盖率包括语句覆盖率、分支覆盖率、循环覆盖率、路径覆盖率等等。
4.3.2 逻辑覆盖法
l 根据覆盖目标的不同,逻辑覆盖又可分为语句覆盖、判定覆盖、条件覆盖、判定/条件覆盖、组合覆盖和路径覆盖。
? 语句覆盖:选择足够多的测试用例,使得程序中的每个可执行语句至少执行一次。
? 判定覆盖:通过执行足够的测试用例,使得程序中的每个判定至少都获得一次“真”值和“假”值, 也就是使程序中的每个取“真”分支和取“假”分支至少均经历一次,也称为“分支覆盖”。
? 条件覆盖:设计足够多的测试用例,使得程序中每个判定包含的每个条件的可能取值(真/假)都至少满足一次。
l 判定/条件覆盖:设计足够多的测试用例,使得程序中每个判定包含的每个条件的所有情况(真/假)至少出现一次,并且每个判定本身的判定结果(真/假)也至少出现一次。
——满足判定/条件覆盖的测试用例一定同时满足判定覆盖和条件覆盖。
l 组合覆盖:通过执行足够的测试用例,使得程序中每个判定的所有可能的条件取值组合都至少出现一次。
——满足组合覆盖的测试用例一定满足判定覆盖、条件覆盖和判定/条件覆盖。
l 路径覆盖:设计足够多的测试用例,要求覆盖程序中所有可能的路径。
语句覆盖
l 要实现DoWork函数的语句覆盖,只需设计一个测试用例就可以覆盖程序中的所有可执行语句。
? 测试用例输入为:{ x=4、y=5、z=5 }
? 程序执行的路径是:abd
l 分析:
语句覆盖可以保证程序中的每个语句都得到执行,但发现不了判定中逻辑运算的错误,即它并不是一种充分的检验方法。例如在第一个判定((x>3)&&(z<10))中把“&&”错误的写成了“||”,这时仍使用该测试用例,则程序仍会按照流程图上的路径abd执行。可以说语句覆盖是最弱的逻辑覆盖准则。
判定覆盖
l 要实现DoWork函数的判定覆盖,需要设计两个测试用例。
? 测试用例的输入为:{x=4、y=5、z=5};{x=2、y=5、z=5}
? 程序执行的路径分别是:abd;ace
l 分析:
上述两个测试用例不仅满足了判定覆盖,同时还做到语句覆盖。从这点看似乎判定覆盖比语句覆盖更强一些,但仍然无法确定判定内部条件的错误。例如把第二个判定中的条件y>5错误写为y<5,使用上述测试用例,照样能按原路径执行而不影响结果。因此,需要有更强的逻辑覆盖准则去检验判定内的条件。
l 说明:以上仅考虑了两出口的判断,我们还应把判定覆盖准则扩充到多出口判断(如Case语句)的情况。因此,判定覆盖更为广泛的含义应该是使得每一个判定获得每一种可能的结果至少一次。
条件覆盖
l 在实际程序代码中,一个判定中通常都包含若干条件。 条件覆盖的目的是设计若干测试用例,在执行被测程序后,要使每个判定中每个条件的可能值至少满足一次。
l 对DoWork函数的各个判定的各种条件取值加以标记。
? 对于第一个判定((x>3)&&(z<10) ):
条件x>3 取真值记为T1,取假值记为-T1
条件z<10 取真值记为T2,取假值记为-T2
? 对于第二个判定( (x==4)||(y>5) ):
条件x==4 取真值记为T3,取假值记为-T3
条件y>5 取真值记为T4,取假值记为-T4
l 根据条件覆盖的基本思想,要使上述4个条件可能产生的8种情况至少满足一次,设计测试用例如下:
测试用例 | 执行路径 | 覆盖条件 | 覆盖分支 |
x=4、y=6、z=5 | abd | T1、T2、T3、T4 | bd |
x=2、y=5、z=15 | ace | -T1、-T2、-T3、-T4 | ce |
l 分析:上面这组测试用例不但覆盖了4个条件的全部8种情况,而且将两个判定的4个分支b、c、d、e也同时覆盖了,即同时达到了条件覆盖和判定覆盖。
l 说明:虽然前面的一组测试用例同时达到了条件覆盖和判定覆盖,但是,并不是说满足条件覆盖就一定能满足判定覆盖。如果设计了下表中的这组测试用例,则虽然满足了条件覆盖,但只是覆盖了程序中第一个判定的取假分支c 和第二个判定的取真分支d,不满足判定覆盖的要求。
测试用例 | 执行路径 | 覆盖条件 | 覆盖分支 |
x=2、y=6、z=5 | acd | -T1、T2、-T3、T4 | cd |
x=4、y=5、z=15 | acd | T1、-T2、T3、-T4 | cd |
判定/条件覆盖
l 判定/条件覆盖实际上是将判定覆盖和条件覆盖结合起来的一种方法,即:设计足够的测试用例,使得判定中每个条件的所有可能取值至少满足一次,同时每个判定的可能结果也至少出现一次。
l 根据判定/条件覆盖的基本思想,只需设计以下两个测试用例便可以覆盖4个条件的8种取值以及4个判定分支
测试用例 | 执行路径 | 覆盖条件 | 覆盖分支 |
x=4、y=6、z=5 | abd | T1、T2、T3、T4 | bd |
x=2、y=5、z=15 | ace | -T1、-T2、-T3、-T4 | ce |
l 分析:从表面上看,判定/条件覆盖测试了各个判定中的所有条件的取值,但实际上,编译器在检查含有多个条件的逻辑表达式时,某些情况下的某些条件将会被其它条件所掩盖。因此,判定/条件覆盖也不一定能够完全检查出逻辑表达式中的错误。
? 例如:对于第一个判定(x>3)&&(z<10)来说,必须x>3和z<10这两个条件同时满足才能确定该判定为真。如果x>3为假,则编译器将不再检查z<10这个条件,那么即使这个条件有错也无法被发现。对于第二个判定(x==4)||(y>5)来说,若条件x==4满足,就认为该判定为真,这时将不会再检查y>5,那么同样也无法发现这个条件中的错误。
组合覆盖
l 组合覆盖的目的是要使设计的测试用例能覆盖每一个判定的所有可能的条件取值组合。
l 对DoWork函数中的各个判定的条件取值组合加以标记:
1、x>3, z<10 记做T1 T2,第一个判定的取真分支
2、x>3, z>=10 记做T1 -T2,第一个判定的取假分支
3、x<=3, z<10 记做-T1 T2,第一个判定的取假分支
4、x<=3, z>=10 记做-T1 -T2,第一个判定的取假分支
5、x==4, y>5 记做T3 T4,第二个判定的取真分支
6、x==4, y<=5 记做T3 -T4,第二个判定的取真分支
7、x!=4, y>5 记做-T3 T4,第二个判定的取真分支
8、x!=4, y<=5 记做-T3 -T4,第二个判定的取假分支
l 根据组合覆盖的基本思想,设计测试用例如下:
测试用例 | 执行路径 | 覆盖条件 | 覆盖组合号 |
x=4、y=6、z=5 | abd | T1、T2、T3、T4 | 1和5 |
x=4、y=5、z=15 | acd | T1、-T2、T3、-T4 | 2和6 |
x=2、y=6、z=5 | acd | -T1、T2、-T3、T4 | 3和7 |
x=2、y=5、z=15 | ace | -T1、-T2、-T3、-T4 | 4和8 |
l 分析:上面这组测试用例覆盖了所有8种条件取值的组合,覆盖了所有判定的真假分支,但是却丢失了一条路径abe。
路径覆盖
l 前面提到的5种逻辑覆盖都未涉及到路径的覆盖。事实上,只有当程序中的每一条路径都受到了检验,才能使程序受到全面检验。路径覆盖的目的就是要使设计的测试用例能覆盖被测程序中所有可能的路径。
l 根据路径覆盖的基本思想,在满足组合覆盖的测试用例中修改其中一个测试用例,则可以实现路径覆盖:
测试用例 | 执行路径 | 覆盖条件 |
x=4、y=6、z=5 | abd | T1、T2、T3、T4 |
x=4、y=5、z=15 | acd | T1、-T2、T3、-T4 |
x=2、y=5、z=15 | ace | -T1、-T2、-T3、-T4 |
x=5、y=5、z=5 | abe | T1、T2、-T3、-T4 |
l 分析:虽然前面一组测试用例满足了路径覆盖,但并没有覆盖程序中所有的条件组合(丢失了组合3和7),即满足路径覆盖的测试用例并不一定满足组合覆盖。
l 说明:
? 对于比较简单的小程序,实现路径覆盖是可能做到的。但如果程序中出现较多判断和较多循环,可能的路径数目将会急剧增长,要在测试中覆盖所有的路径是无法实现的。为了解决这个难题,只有把覆盖路径数量压缩到一定的限度内,如程序中的循环体只执行一次。
? 在实际测试中,即使对于路径数很有限的程序已经做到路径覆盖,仍然不能保证被测试程序的正确性,还需要采用其他测试方法进行补充。
4.3.3 面向对象的覆盖
l 继承上下文覆盖
? 由于传统的结构化度量没有考虑面向对象的一些特性(如多态、继承和封装等),所以在面向对象领域,传统的结构化覆盖必须被加强,以满足面向对象特性。
? 继承上下文覆盖考虑在每个类的上下文内获得的覆盖率级别。它是扩展到面向对象领域里的一种覆盖率度量方法,用于度量在系统中的多态调用被测试得多好。
? 继承上下文定义将基类上下文内例行程序的执行作为独立于继承类上下文内例行程序的执行。同样,它们在考虑继承类上下文内例行程序的执行也独立于基类上下文内例行程序的执行。为了获得100%继承上下文覆盖,代码必须在每个适当的上下文内被完全执行。
l 基于状态的上下文覆盖
? 在绝大多数面向对象的系统中存在这样的一些类:这些类的对象可以存在于众多不同状态中的任何一种,并且由于类的行为依赖于状态,每个类的行为在每个可能的状态中其性质是不同的。
? 基于状态的上下文覆盖对应于被测类对象的潜在状态。
? 这样基于状态的上下文覆盖把一个状态上下文内的一个例行程序的执行认为是独立于另一个状态内相同例行程序的执行。为了达到100%的基于状态的上下文覆盖,例行程序必须在每个适当的上下文(状态)内被执行。
4.3.4 测试覆盖准则
l 逻辑覆盖的出发点是合理的、完善的。所谓“覆盖”,就是想要做到全面而无遗漏,但逻辑覆盖并不能真正做到无遗漏。
l 例如:我们不小心将前面提到的程序段中的if (x>3 &&Z<10) { …… }错写成if (x>=3 &&Z<10) { …… }
按照我们前面设计的测试用例(x的值取2或4)来看,逻辑覆盖对这样的小问题都无能为力。分析出现这一情况的原因在于:错误区域仅仅在x=3这个点上,即仅当x的值取3时,测试才能发现错误。面对这类情况,我们应该从中吸取的教训是测试工作要有重点,要多针对容易发生问题的地方设计测试用例。
l ESTCA覆盖准则:在容易发生问题的地方设计测试用例,即重视程序中谓词(条件判断)的取值。
l ESTCA覆盖准则是一套错误敏感用例分析规则。这一规则虽然并不完备,但在普通程序中却是有效的。原因在于这是一种经验型的覆盖准则,规则本身针对了程序编写人员容易发生的错误,或是围绕着发生错误的频繁区域,从而提高了发现错误的命中率。具体规则如下:
? [规则1] 对于A rel B型 (rel可以是<、= 或 >) 的分支谓词,应适当的选择A与B的值,使得测试执行到该分支语句时,A<B、A=B、A>B的情况分别出现一次。
——这是为了检测逻辑符号写错的情况,如将“A<B”错写为“A>B”。
测试覆盖准则(续)
? [规则2] 对于A rel C型 (rel可以是>或<, A是变量,C是常量)的分支谓词:当rel为<时,应适当的选择A的值,使A=C-M (M是距C最小的机器容许正数,若A和C都为正整数时,M=1);当rel为>时,应适当的选择A的值,使A=C+M。
——这是为了检测“差1”之类的错误,如“A>1”错写成“A>0”。
? [规则3] 对外部输入变量赋值,使其在每一个测试用例中均有不同的值与符号,并与同一组测试用例中其他变量的值与符号不同。
——这是为了检测程序语句中的错误,如应该引用某一变量而错成引用另一个常量。
l 关于LCSAJ
? LCSAJ (Linear CodeSequence and Jump) 的字面含义是线性代码序列与跳转。在程序中,一个LCSAJ是一组顺序执行的代码,以控制跳转为其结束点。
? LCSAJ的起点是根据程序本身决定的。它的起点可以是程序第一行或转移语句的入口点,或是控制流可跳达的点。
? 如果有几个LCSAJ首尾相接,且第一个LCSAJ起点为程序起点,最后一个LCSAJ终点为程序终点,这样的LCSAJ串就组成了程序的一条路径(LCSAJ路径)。一条LCSAJ程序路径可能是由2个、3个或多个LCSAJ组成的。
l 基于LCSAJ与路径的关系,提出了层次LCSAJ覆盖准则。它是一个分层的覆盖准则,可以概括的描述为:
? 第一层 — 语句覆盖。
? 第二层 — 分支覆盖。
? 第三层 — LCSAJ覆盖,即程序中的每一个LCSAJ都至少在测试中经历过一次。
? 第四层 — 两两LCSAJ覆盖,即程序中的每两个相连的LCSAJ组合起来在测试中都要经历一次。
? 第n+2层 — 每n个首尾相连的LCSAJ组合在测试中都要经历一次。
l 在实施测试时,若要实现上述的层次LCSAJ覆盖,需要产生被测程序的所有LCSAJ。
l 例:找出前面DoWork函数的所有LCSAJ和LCSAJ路径。
? LCSAJ(5个):
(1)int k=0,j=0; if ( (x>3)&&(z<10) )
(2)k=x*y-1; j=sqrt(k); if( (x==4)||(y>5) )
(3)if ( (x==4)||(y>5) )
(4)j=x*y+10; j=j%3
(5)j=j%3
? LCSAJ路径(4条):
(1)-(2)-(4) (1)-(2)-(5) (1)-(3)-(4) (1)-(3)-(5)
4.4 路径测试
4.4.1 路径表达式
l 为了满足路径覆盖,必须首先确定具体的路径以及路径的个数。我们通常采用控制流图的边(弧)序列和节点序列表示某一条具体路径,更为概括的表示方法为:
(1)弧a和弧b相乘,表示为ab,它表明路径是先经历弧a,接着再经历弧b,弧a和弧b是先后相接的。
(2)弧a和弧b相加,表示为a+b,它表明两条弧是“或”的关系,是并行的路段。
l 路径数的计算:
在路径表达式中,将所有弧均以数值1来代替,再进行表达式的相乘和相加运算,最后得到的数值即为该程序的路径数。
4.4.2 基本路径测试方法
l 路径测试就是从一个程序的入口开始,执行所经历的各个语句的完整过程。从广义的角度讲,任何有关路径分析的测试都可以被称为路径测试。
l 完成路径测试的理想情况是做到路径覆盖,但对于复杂性大的程序要做到所有路径覆盖(测试所有可执行路径)是不可能的。
l 在不能做到所有路径覆盖的前提下,如果某一程序的每一个独立路径都被测试过,那么可以认为程序中的每个语句都已经检验过了,即达到了语句覆盖。这种测试方法就是通常所说的基本路径测试方法。
l 基本路径测试方法是在控制流图的基础上,通过分析控制结构的环形复杂度,导出执行路径的基本集,再从该基本集设计测试用例。基本路径测试方法包括以下4个步骤:
(1)画出程序的控制流图。
(2)计算程序的环形复杂度,导出程序基本路径集中的独立路径条数,这是确定程序中每个可执行语句至少执行一次所必须的测试用例数目的上界。
(3)导出基本路径集,确定程序的独立路径。
(4)根据(3)中的独立路径,设计测试用例的输入数据和预期输出。
4.4.3 循环测试方法
l 从本质上说,循环测试的目的就是检查循环结构的有效性。
l 通常,循环可以划分为简单循环、嵌套循环、串接循环和 非结构循环4类。
(1)测试简单循环。设其循环的最大次数为n ,可采用以下测试集:
? 跳过整个循环;
? 只循环一次;
? 只循环两次;
? 循环 m 次,其中m<n;
? 分别循环 n-1、n 和 n+1 次。
(2)测试嵌套循环。如果将简单循环的测试方法用于嵌套循环,可能的测试次数会随嵌套层数成几何级数增加。 此时可采用以下办法减少测试次数:
? 测试从最内层循环开始,所有外层循环次数设置为最小值;
? 对最内层循环按照简单循环的测试方法进行;
? 由内向外进行下一个循环的测试,本层循环的所有外层循环仍取最小值,而由本层循环嵌套的循环取某些“典型”值;
? 重复上一步的过程,直到测试完所有循环。
(3)测试串接循环。若串接的各个循环相互独立,则可分别采用简单循环的测试方法;否则采用嵌套循环的测试方法。
(4)对于非结构循环这种情况,无法进行测试,需要按结构化程序设计的思想将程序结构化后,再进行测试。
Z路径覆盖下的循环测试方法
l Z路径覆盖是路径覆盖的一种变体,它是将程序中的循环结构简化为选择结构的一种路径覆盖。
l 循环简化的目的是限制循环的次数,无论循环的形式和循环体实际执行的次数,简化后的循环测试只考虑执行循环体一次和零次(不执行)两种情况,即考虑执行时进入循环体一次和跳过循环体这两种情况。
4.4.4 产生测试用例
l 在实践中,除了前面给出的各种方法外,通常还可以采用以下三种方法来补充设计测试用例:
(1)通过非路经分析得到测试用例
——这种方法得到的测试用例是在应用系统本身的实践中提供的,基本上是测试人员凭工作经验的得到,甚至是猜测得到的。
(2)寻找尚未测试过的路径并生成相应的测试用例
——这种方法需要穷举被测程序的所有路径,并与前面已测试路径进行对比。
(3)通过指定特定路径并生成相应的测试用例
4.5 最少测试用例数计算
l 为实现测试的逻辑覆盖,必须设计足够多的测试用例,并使用这些测试用例执行被测程序,实施测试。我们关心的是:对于某个具体的程序来说,至少需要设计多少个测试用例。这里提供一种估算最少测试用例数的方法。
l 我们知道,结构化程序是由 3 种基本控制结构组成:顺序型(构成串行操作)、选择型(构成分支操作)和重复型(构成循环操作)。
l 为了把问题化简,避免出现测试用例极多的组合爆炸,把构成循环操作的重复型结构用选择结构代替。这样,任一循环便改造成进入循环体或不进入循环体的分支操作了。
l 用N-S图表示程序的3种基本控制结构:
? 图中A、B、C、D、S均表示要执行的操作,P是可取真假值的谓词,Y表真值,N表假值。
? 图中的 (c) 和 (d) 两种重复型结构代表了两种循环。在做了简化循环的假设以后,对于一般的程序控制流,我们只考虑选择型结构。事实上它已经能体现顺序型和重复型结构了。
l 例如,右图表达了两个顺序执行的分支结构。当两个分支谓词P1和P2取不同值时,将分别执行a或b及c或d操作。
l 显然,要测试这个小程序,需要至少提供4个测试用例才能作到逻辑覆盖,使得ac、ad、bc及bd操作均得到检验。其实,这里的4是图中的第1个分支谓词引出的两个操作,及第2个分支谓词引出的两个操作组合起来而得到的,即2×2=4。并且,这里的2是由于两个并列的操作,即1+1=2 而得到的。
l 对于一般的、更为复杂的问题,估算最少测试用例个数的原则也是同样的:
? 如果在N-S图中存在有并列的层次A1、A2,A1和A2的最少测试用例个数分别为a1、a2,则由 A1、A2 两层所组合的 N-S图对应的最少测试用例数为a1×a2。
? 如果在N-S图中不存在有并列的层次,则对应的最少测试用例数由并列的操作数决定,即N-S图中除谓词之外的操作框的个数。
l 例:如下图所示的N-S图,至少需要多少个测试用例完成逻辑覆盖?
? 由于图中并不存在并列的层次
? 最少测试用例数由并列的操作数决定
? 即为1+1+1=3。
? 由于图中没有包含并列的层次
? 最少测试用例数仍由并列的操作数决定
? 即为1+1+1+1+1=5。
? 图中的2345和67是并列的两层。
? 2345层对应的最少测试用例数为1+1+1+1+1=5
? 67层对应的测试用例数为1+1+1=3
? 2345和67这两层组合后对应的测试用例数为5×3=15
? 最后,由于两层组合后的部分是不满足谓词1时所要做的操作,还要加上满足谓词1要做的操作,因此整个程序所需测试用例数为15+1=16。