新增软件测试

pull/2/head
吴尤 4 years ago
parent 3a63d1b38e
commit fbb31b8c2d

@ -0,0 +1,208 @@
## 软件测试背景
- 随着计算机技术的迅速发展和越来越广泛深入地应用于国民经济与社会生活的各个方面,软件系统的规模和复杂性与日俱增。
- 由于软件本身的特性,软件中的错误是不可避免的,因此为了保证软件质量,必须对软件进行测试。
- 当软件业不断成熟,走入工业化阶段的同时,软件测试在软件开发领域的 地位也越来越重要。
### 可靠性
- 软件质量的一个重要标志是软件可靠性IEEE将其定义为系统在特定的环境下在给定的时间内无故障地运行的概率。
- 运行软件的驻留故障密度各不相同,然而,正是由于软件可靠性的大幅度提高才使得计算机得以广泛应用于社会的各个方面。软件测试则是保证软件质量,提高软件可靠性的最重要手段。
### 软件缺陷
软件是一种逻辑思维的产品,不断改进的开发技术和工具只能减少错误的发生,却无法完全避免软件会存在各种各样的缺陷。
案例“千年虫”问题、Win2000 中文输入法漏洞
因此为了保证软件质量,软件测试是软件开发中必不可少的环节
> 定义和种类
软件缺陷是软件产品中所存在的问题,最终表现为用户所需要的功能没有完全实现,没有满足用户的需求。
主要类型有:
- 未达到产品说明书中已经标明的功能;
- 出现了产品说明书中指明不会出现的错误;
- 未达到产品说明书中虽未指出但应当达到的目标;
- 功能超出了产品说明书中指出的范围;
- 测试人员认为软件难以理解、不易使用,或者最终用户认为该软件使 用效果不良。
软件公司对缺陷严重性级别的定义一般可概括为以下几种:
- **致命的**:致命的错误,造成系统或应用程序崩溃、死机、系统悬挂,或造成数据丢失、主要功能完全丧失等。
- **严重的**:严重错误,指功能或特性没有实现,主要功能丧失,导致严重的问题,或致命的错误声明。
- **一般的**:不太严重的错误,这样的软件缺陷虽然不影响系统的基本使用, 但没有很好地实现功能,没有达到预期效果。如次要功能丧失,提示信息 不太准确,或用户界面差,操作时间长等。
- **微小的**:一些小问题,对功能几乎没有影响,产品及属性仍可使用,如有个别错误字、文字排列不整齐等。
> 软件缺陷的产生
软件缺陷的产生是不可避免的,那么造成软件缺陷的原因是什么呢?
- 需求解释有错误;
- 用户需求定义错误;
- 需求记录错误;
- 设计说明有误;
- 编码说明有误;
- 程序代码有误;
- 数据输入有误,问修改不正确。
> 修复费用
缺陷并不只是在编程阶段产生,在需求分析和设计阶段同样会产生,所以修正错误的代价是随时间成指数级增长的。
测试人员应当把“尽早和不断地测试”作为其座右铭,从需求分析时就介 入进去,尽早发现和改正错误。
平均而言,如果在 需求阶段修正一个错误的代价是 1那么在设计阶段就是它的 3 到 6 倍,在编程阶段 是它的 10 倍,而到了产品发布出去时,这个数字就是 40 到 1000 倍。
### 发展与现状
20 世纪 50——60 年代,软件测试相对于开发工作仍然处于次要位置。
70 年代以后规模和复杂度加大,逐渐形成了一套完整的体系,开始走向规范化。
1982 年在美国北卡罗莱纳州大学召开了首次软件测试技术会议,是重要里程碑。
尽管软件测试技术与实践都有了很大进展,但是就目前软件工程发展状况而言,软件测试仍然是较为薄弱的一个方面。 国内软件测试工作起步较晚,需要进一步提高 对软件测试重要性的认识,研究与采用先进的测试管理与应用技术,建立完善的软件 质量保证的管理体系。
## 软件测试基础理论
### 软件测试的定义
软件测试就是在软件投入运行前,对软件需求分析、设计规格说明和编码实现的最终审查,它是软件质量保证的关键步骤。
软件测试是为了尽快尽早地发现在软件产品中所存在的各种软件缺陷而展开的贯穿整个软件开发生命周期、对软件产品(包括阶段性产品)进行验证和确认的活动过程。
> 软件测试的基本问题
一个软件生命周期包括制定计划、需求分析定义、软件设计、程序编码、软件测试、软件运行、软件维护、软件停用等8个阶段。软件测试的根本目的是为了保证软件质量。。
的全体”。软件质量反映以下三个方面:
- 软件需求是度量质量的基础。
- 在各种标准中定义开发准则,用来知道软件人员用于工程化的方法来开发软件。
- 往往会有一些隐含的需求没有明确地提出。
软件质量内涵包括:正确性、可靠性、可维护性、可读性(文档、注释)、结构化、可测试性、可移植性、可扩展性、用户界面友好性、易学、易用、健壮性。
软件测试的对象:软件开发的整个过程。因此,软件开发过程中产生的需求分析、概要设计、详细设计以及编码等各个阶段所得到的文档,包括需求规格说明书、概要设计规格说明书、详细设计规格说明书以及源代码,都是软件测试的对象。
软件测试设计的关键问题包括以下四个方面:
- 测试由谁来执行。
- 测试什么。
- 什么时候进行测试。
- 怎样进行测试。
### 软件测试的目的和原则
证明、检测和预防是一个良好测试的重要目标。
软件测试过程中应注意和遵循的原则可以概括为10项
- 测试不是为了证明程序的正确性,而是为了证明程序不能工作。
- 测试应当有重点。
- 事先定义好产品的质量标准。
- 软件项目一启动,软件测试也就开始。
- 穷举测试是不可能的。
- 第三方进行测试会更客观,更有效。
- 软件测试计划是做好软件测试工作的前提。
- 测试用例是设计出来的,不是写出来的。
- 对发现错误较多的程序段,应进行更深入的测试。
- 重视文档,妥善保存一切测试过程文档。
### 测试在开发各个阶段的任务
- **项目规划阶段**:负责从单元测试到系统测试的整个测试阶段的监控。
- **需求分析阶段**:确定测试需求分析、系统测试计划的制定,评审后成为管理项目。测试需求分析是对产品生命周期中测试所需的资源、配置、每阶段评判通过的规约;系统测试计划则是依据软件的需求规格说明书,制定测试计划和设计相应的测试用例。
- **详细设计和概要设计阶段**:确保集成测试计划和单元测试计划完成。
- **编码阶段**:由开发人员进行自己负责部分的代码测试。在项目较大时,由专人进行编码阶段的测试任务。
- **测试阶段(单元测试、集成测试、系统测试)**:依据测试代码进行测试,并提交相应的测试报告和测试结束报告。
### 测试信息流
- 软件配置:指测试对象。通常包括软件需求规格说明、软件设计规格说明、源代码等。
- 测试配置:通常包括测试计划、测试步骤、测试用例以及实施测试的测试程序、测试工具等。
对测试结果与预期的结果进行比较以后,即可判断是否存在错误,决定是否进入排错阶段,进行调试任务。由于修改可能会带来新的问题,我们需要对修改以后的程序重新测试,即进行回归测试。
![image-20211201164435770](软件测试概述.assets/image-20211201164435770.png)
### 软件测试停止标准
在实际工作中常用的停止测试标准有5类
- 第一类标准测试超过了预定的时间,停止测试。
- 第二类标准执行了所有测试用例但没有发现故障,停止测试。
- 第三类标准使用特定的测试用例设计方法作为判断测试停止的基础。
- 第四类标准正面指出测试停止的要求比如发现并修改70个软件故障。
- 第五类标准根据单位时间内查出故障的数量决定是否停止测试。
### 软件测试技术概要
策略:软件测试要经过单元测试、集成测试、确认测试、系统测试以及验收测试。
软件测试方法和技术:
- 根据执行测试的主体不同,可分为人工测试和自动化测试。
- 根据软件测试针对系统的内部结构还是具体实现功能的角度而论,可分为白盒测试法和黑盒测试法;
- 根据软件测试是否执行程序而论,可分为静态测试和动态测试;
- 按照测试的对象进行分类涉及面向开发的单元测试、GUI和捕获/回放测试、基于WEB应用的测试、C/C++/JAVA应用测试、负载和性能测试、数据库测试、软件测试和QA管理等各类工具测试
- 其他测试方法,如回归测试、压力测试、恢复测试、安全测试和兼容性测试等。
## 软件开发
### 软件产品的组成
客户需求:编写软件的目的是满足客户的需求。
产品说明书:对哪些要做、哪些不做以及客户所需要的产品功能进行综合描述,并包括用户没 有提出但软件产品本身必须实现的要求,从而针对产品进行定义并确定其功能。
进度表通常应用Gantt图表来描述开发进度。
> 设计文档:
- 构架
- 数据流示意图
- 状态变化示意图
- 流程图
- 注释代码
>测试文档:
- 测试计划
- 测试案例
- 软件缺陷报告
- 归纳、统计和总结
> 软件产品的其他组成部分:
- 帮助文件
- 用户手册
- 样本和示例
- 标签
- 产品支持信息
- 图标和标志
- 错误信息
- 广告和宣传材料
- 软件的安装
- 软件说明文件
- 测试错误提示信息
### 开发人员角色
- **项目经理**:项目经理负责管理业务应用开发或者软件和系统开发项目。
- **业务分析人员**:理解和描绘客户的需求,业务分析人员的任务是理解和描绘客户的需求,引导和 协调用户和业务需求的收集和确认,文档化和组织系统的需求,或者向整个团队 传达需求。
- **架构师**:架构师负责理解系统的业务需求,并创建合理、完善的系统体系架构。
- **数据设计人员**:定义详细的数据库设计。
- **开发人员**:设计和实现可执行的代码方案、测试开发出的组件和分析运行时情况,以去除可能存在的错误。
- **测试人员**:系统测试人员负责制定测试计划并依照测试计划进行测试。这些测试包括功能性
### 软件开发模式
- **大棒模式**:大棒模式的优点是简单。计划、进度安排和正规开发过程几乎没有。
- **边写边改模式**:边写边改模式是项目小组在未刻意采用其它开发模式时常用的一种开发模式。
- **瀑布模式**:瀑布模式是将软件生命周期的各项活动规定为按照固定顺序相连的若干个阶段性工作。
- **快速原型模式**:快速原型模式是一种以计算机为基础的系统开发方法,它首先构造一个功能简单 的原型系统,然后通过对原型系统逐步求精,不断扩充完善得到最终的软件系统。
- **螺旋模式**:螺旋模式是瀑布模式与边写边改模式演化、结合的形式,并加入了开发风险评估所建立 的软件开发模式。
## 软件测试过程
该测试过程包括6个主要活动如下所述
- 测试计划,确定测试基本原则、生成测试概要设计
- 测试需求分析
- 测试设计,包括测试用例设计和测试规程规格说明
- 测试规程实现
- 测试执行
- 总结生成报告

@ -0,0 +1,358 @@
## 软件测试复杂性与经济性
### 软件测试的复杂性
人们常常以为,开发一个程序是困难的,测试一个程序则比较容易。然而事实并非如此。在软件测试当中,由于各种原因,不能实现对软件进行完全的测试并找出所 有的软件缺陷,使软件达到完美无缺的理想状态。设计测试用例是一项细致并需要高 度技巧的工作,稍有不慎就会顾此失彼,发生不应有的疏漏。除此之外,要通过测试 找出软件中的所有故障也是不现实、不可能的,因为这涉及到软件测试的复杂性、充分性和经济性。
- **无法对程序进行完全的测试**
- 不论是黑盒测试方法还是白盒测试方法,由于测试情况数量巨大,都不可能进行彻底的测试。所谓彻底测试,就是让被测程序在一切可能的输入情况下全部执行一遍。通常也称这种测试为“穷举测试”。 “黑盒”法是穷举输入测试,只有把所有可能的输入都作为测试情况使用,才能以这种方法查出程序中所有的错误。实际上测试情况有无穷多个,人们不仅要测试所有合法的输入,而且还要对那些不合法但是可能的输入进行测试。 “白盒”法是穷举路径测试,贯穿程序的独立路径数是天文数字,要使每条路径都得到测试是不现实的。
- 软件工程的总目标是充分利用有限的人力和物力资源,高效率、高质量地完成测试。为了降低测试成本,选择测试用例时应注意遵守“经济性”的原则。第一,要根据程序的重要性和一旦发生故障将造成的损失来确定它的测试等级;第二,要认真研究测试策略,以便能使用尽可能少的测试用例,发现尽可能多的程序错误。掌握好测试量是至关重要的,一位有经验的软件开发管理人员在谈到软件测试时曾这样说过:“不充分的测试是愚蠢的,而过度的测试是一种罪孽”。测试不足意味着让用户承担隐藏错误带来的危险,过度测试则会浪费许多宝贵的资源。
- **测试无法保证被测程序中无遗留错误**
- 软件测试工作与传染病疫情员的工作是很相似的疫情员只是报告已经发现的疫情却无法报告潜伏的疫情状况。同样通过软件测试只能报告软件已经被发现的缺陷和故障也不能保证经测试后发现的是全部的软件缺陷即无法报告隐藏的软件故障。若能继续进行测试工作可能会发现一些新的问题。在实际测试中穷举测试工作量太大实践上行不通这就注定了一切实际测试都是不彻底的。当然就不能够保证被测试程序中不存在遗留的错误。而且在“白盒”法中即使实施了穷举路径测试程序仍然可能有错误。第一穷举路径测试决不能查出程序违反了设计规范即程序本身是个错误的程序。第二穷举路径测试不可能查出程序中因遗漏路径而出错。第三穷举路径测试可能发现不了一些与数据相关的错误。EWDijkstra 的一句名言对此作了很好的注解:“程序测试只能证明错误的存在,但不能证明错误不存在”。
- **不能修复所有的软件故障**
- 在软件测试中,严峻的现实是:即使付出再多的时间和代价,也不能够使所有的软件故障都得到修复。但这并不说明测试没有达到目的,关键是要进行正确的判断、合理的取舍,根据风险分析决定哪些故障必须修复,哪些故障可以不修复。
- 通常不能 修复软件故障的理由是:
- 没有足够的时间进行修复;
- 修复的风险较大。修复了旧的故障可能产生更多的故障;
- 不值得修复。主要是在不常用的功能中的故障,或对运行影响不大的故障;
- 可不算做故障的一些缺陷。在某些场合,错误理解或者软件规格说明变更 可以将软件故障当作附加的功能而不作为故障来对待。
### 软件测试的经济性
如果不能做到测试软件所有的情况,则该软件就是有风险的。软件测试不可能对软件使用中所有的情况进行测试,但有可能客户会在使用软件的时候遇到,并且可能发现软件的缺陷。等到这个时候再进行软件缺陷的修复,代价是很高的。
软件测试的一个主要工作原则就是如何将无边无际的可能性减小到一个可以控制的范围,以及如何针对软件风险做出恰当的选择,去粗存精,找到最佳的测试量,使得测试工作量不多不少,既能达到测试的目的,又能较为经济。
测试是软件生存期中费用消耗最大的环节。测试费用除了测试的直接消耗外,还包括其它的相关费用。能够决定需要做多少次测试的主要影响因素如下:
- **系统的目的**:系统的目的的差别在很大程度上影响所需要进行的测试的数量。那些可能产生严重后果的系统必须要进行更多的测试。一台在 Boeing 757 上的系统应该比一个用于公共图书馆中检索资料的系统需要更多的测试。一个用来控制密封燃气管道的系统应该比一个与有毒爆炸物品无关的系统有更高的可信度。一个安全关键软件的开发组比一个游戏软件开发组要有苛刻得多的查找错误方面的要求。
- **潜在的用户数量**:一个系统的潜在用户数量也在很大程度上影响了测试必要性的程度。这主要是由于用户团体在经济方面的影响。一个在全世界范围内有几千个用户的系统肯定比一个只在办公室中运行的有两三个用户的系统需要更多的测试。如果不能使用的话,前一个系统的经济影响肯定比后一个系统大。除此而外,在分配处理错误的时候,所花的代价的差别也很大。如果在内部系统中发现了一个严重的错误,在处理错误的时候的费用就相对少一些,如果要处理一个遍布全世界的错误就需要花费相当大的财力和精力。
- **信息的价值**:在考虑测试的必要性时,还需要将系统中所包含的信息的价值考虑在内,一个支持许多家大银行或众多证券交易所的客户机/服务器系统中含有经济价值非常高的内容。很显然这一系统需要比一个支持鞋店的系统要进行更多的测试。这两个系统的用户都希望得到高质量、无错误的系统,但是前一种系统的影响比后一种要大得多。因此我们应该从经济方面考虑,投入与经济价值相对应的时间和金钱去进行测试。
- **开发机构**:一个没有标准和缺少经验的开发机构很可能开发出充满错误的系统。在一个建立了标准和有很多经验的开发机构中开发出来的系统,错误不会很多,因此,对于不同的开发机构来说,所需要的测试的必要性也就截然的不同。 然而,那些需要进行大幅度改善的机构反而不大可能认识到自身的弱点。那些需要更加严格的测试过程的机构往往是最不可能进行这一活动的,在许多情况下,机构的管理部门并不能真正地理解开发一个高质量的系统的好处。
- **测试的时机**:测试量会随时间的推移发生改变。在一个竞争很激烈的市场里,争取时间可能是制胜的关键,开始可能不会在测试上花多少时间,但几年后如果市场分配格局已经建立起来了,那么产品的质量就变得更重要了,测试量就要加大。测试量应该针对合适的目标进行调整。
## 软件测试方法
软件测试的策略、方法和技术是多种多样的。对于软件测试方法,可以从不同的角度得到以下基本分类:
- 从是否需要执行被测软件的角度,可分为静态测试和动态测试;
- 从测试是否针对系统的内部结构和具体实现算法的角度来看,可分为白盒测试和黑盒测试;
- 根据执行测试的主体不同,又可以将测试方法分为人工测试和自动化测试。
### 静态测试与动态测试
> 静态测试
在软件开发过程中,每产生一个文档,都必须对它进行测试,以确定它的质量是否满足要求。这样的检查工作与全面质量管理的思想是一致的,也与项目管理过程相一致。每当一个文档通过了静态测试,就标志着一项开发工作的总结,标志着项目取得了一定的进展,进入了一个新的阶段。
静态测试的基本特征是在对软件进行分析、检查和测试时不实际运行被测试的程序。它可以用于对各种软件文档进行测试,是软件开发中十分有效的质量控制方法之一。在软件开发过程中的早期阶段,由于可运行的代码尚未产生,不可能进行动态测试,而这些阶段的中间产品的质量直接关系到软件开发的成败与开销的大小,因此,在这些阶段,静态测试的作用尤为重要。在软件开发多年的生产实践经验和教训的基础上,人们总结出了一些行之有效的静态测试技术和方法,如结构化走通、正规检视等等。这些方法和测试技术可以与软件质量的定量度量技术相结合,对软件开发过程进行监视、控制,从而保障软件质量。
针对程序代码的静态测试是指不运行被测程序本身,仅通过分析或检查源程序的文法、结构、过程、接口等来检查程序的正确性。静态方法通过程序静态特性的分析,找出欠缺和可疑之处,例如不匹配的参数、不适当的循环嵌套和分支嵌套、不允许的递归、未使用过的变量、空指针的引用和可疑的计算等。静态测试结果可用于进一步的查错,并为测试用例选取提供指导。
针对代码的静态测试包括代码检查、静态结构分析、代码质量度量等。它可以由人工进行,充分发挥人的逻辑思维优势,也可以借助软件工具自动进行。
- 代码检查
- 代码检查主要检查代码和设计的一致性,代码对标准的遵循、可读性,代码的逻辑表达的正确性,代码结构的合理性等方面;可以发现违背程序编写标准的问题,程序中不安全、不明确和模糊的部分,找出程序中不可移植部分、违背程序编程风格的问题,包括变量检查、命名和类型审查、程序逻辑审查、程序语法检查和程序结构检查等内容。
- 在实际使用中,代码检查比动态测试更有效率,能快速找到缺陷,发现 30%70%的逻辑设计和编码缺陷;代码检查看到的是问题本身而非征兆。但是代码检查非常耗费时间,而且代码检查需要知识和经验的积累。代码检查应在编译和动态测试之前进行,在检查前,应准备好需求描述文档、程序设计文档、程序的源代码清单、代码编码标准和代码缺陷检查表等。
- 静态结构分析
- 静态结构分析主要是以图形的方式表现程序的内部结构,例如函数调用关系图、函数内部控制流图。其中,函数调用关系图以直观的图形方式描述一个应用程序中各个函数的调用和被调用关系;控制流图显示一个函数的逻辑结构,它由许多节点组成,一个节点代表一条语句或数条语句,连接结点的叫边,边表示节点间的控制流向。
- 代码质量度量
- ISO/IEC 9126 国际标准所定义的软件质量包括六个方面:功能性、可靠性、易用性、效率、可维护性和可移植性。软件的质量是软件属性的各种
标准度量的组合。
- 针对软件的可维护性目前业界主要存在三种度量参数Line 复杂度、Halstead复杂度和 McCabe 复杂度。其中 Line 复杂度以代码的行数作为计算的基准。Halstead以程序中使用到的运算符与运算元数量作为计数目标直接测量指标然后可以据此计算出程序容量、工作量等。McCabe 复杂度一般称为圈复杂度(Cyclomatic complexity)它将软件的流程图转化为有向图然后以图论来衡量软件的量。McCabe 复杂度包括圈复杂度、基本复杂度、模块设计复杂度、设计复杂度和集成复杂度。
> 动态测试
所谓动态测试是指通过运行被测程序,检查运行结果与预期结果的差异,并分析运行效率和健壮性等性能,动态测试包括功能确认与接口测试、覆盖率分析、性能分析、内存分析等。
- **功能确认与接口测试** :这部分的测试包括各个单元功能的正确执行、单元间的接口,包括:单元接口、局部数据结构、重要的执行路径、错误处理的路径和影响上述几点的边界条件等内容。
- **覆盖率分析** :覆盖率分析主要对代码的执行路径覆盖范围进行评估,语句覆盖、判定覆盖、条件覆盖、条件/判定覆盖、修正条件/判定覆盖、基本路径覆盖都是从不同要求出发,为设计测试用例提出依据的。
- **性能分析** 代码运行缓慢是开发过程中一个重要问题。一个应用程序运行速度较慢,程序员不容易找到是在哪里出现了问题。如果不能解决应用程序的性能问题,将降低并极大地影响应用程序的质量,于是查找和修改性能瓶颈成为调整整个代码性能的关键。目前性能分析工具大致分为纯软件的测试工具、纯硬件的测试工具(如逻辑分析仪和仿真器等)和软硬件结合的测试工具三类。
- **内存分析** 内存泄漏会导致系统运行的崩溃,尤其对于嵌入式系统这种资源比较匮乏、应用非常广泛,而且往往又处于重要部位的,将可能导致无法预料的重大损失。通过测量内存使用情况,我们可以了解程序内存分配的真实情况,发现对内存的不正常使用,在问题出现前发现征兆,在系统崩溃前发现内存泄露错误;发现内存分配错误,并精确显示发生错误时的上下文情况,指出发生错误的原由。
### 黑盒测试与白盒测试
> 黑盒测试
黑盒测试是指在对程序进行的功能抽象的基础上,将程序划分成功能单元,然后对每个功能单元生成测试数据进行测试。黑盒测试也称功能测试或数据驱动测试,它是已知产品所应具有的功能,通过测试来检测每个功能是否都能正常使用。在测试时,把程序看作一个不能打开的黑盒子,在完全不考虑程序内部结构和内部特性的情况下,测试者在程序接口进行测试,只检查程序功能是否按照需求规格说明书的规定正常使用,程序是否能适当接收输入数据而产生正确的输出信息,并且保持外部信息的完整性。
在黑盒测试中,被测软件的输入域和输出域往往是无限域,因此穷举测试通常是不可行的。必须以某种策略分析软件规格说明,从而得出测试用例集,尽可能全面而又高效地对软件进行测试。
- **等价类划分**
- 所谓等价类,就是某个输入域的集合,集合中的每个输入对揭露程序错误来说是等效的,把程序的输入域划分成若干部分,然后从每个部分中选取少数代表性数据作为测试用例,这就是等价类划分方法。它是功能测试的基本方法。
- **因果图法**
- 因果图是一种形式语言,由自然语言写成的规范转换而成,这种形式语言实际上是一种使用简化记号表示数字逻辑图。因果图法是帮助人们系统地选择一组高效测试用例的方法,此外,它还能指出程序规范中的不完全性和二义性。
- **边界值分析**
- 实践证明,软件在输入、输出域的边界附近容易出现差错,边界值分析是考虑边界条件而选取测试用例的一种功能测试方法。所谓边界条件,是相对于输入和输出等价类直接在其边界上,或稍高于和稍低于其边界的这些状态条件。边界值分析是对等价类划分的有效补充。
- 另外常见的黑盒测试方法还有基于决策表的测试、错误推测法等。
> 白盒测试
白盒测试是根据被测程序的内部结构设计测试用例的一类测试,又称为结构测试或逻辑驱动测试,它是知道产品内部工作过程,通过测试来检测产品内部动作是否按照规格说明书的规定正常进行,按照程序内部的结构测试程序,检验程序中的每条通路是否都能按预定要求正确工作。其主要方法有逻辑覆盖、基本路径测试等,主要用于软件验证。白盒法全面了解程序内部逻辑结构、对所有逻辑路径进行测试,是穷举路径测试。在使用这一方案时,测试者必须检查程序的内部结构,从检查程序的逻辑着手,得出测试数据,测试程序的内部变量状态、逻辑结构、运行路径等,检验程序中的每条通路是否都能按预定要求正确工作,所有内部成分是否按规定正常进行。
贯穿程序的所有路径数是天文数字,所以白盒测试法在实际操作时不可能实现路径的穷举。它常以达到对程序内部结构的某种覆盖标准为目标。白盒测试主要用于软件验证,其主要方法有逻辑覆盖、数据流覆盖等。
不同的测试方法各有所长,都能比较容易地发现某种类型的错误,却不易发现其他类型的错误,各有侧重、各有优缺点,构成互补关系。白盒测试可以有效地发现程序内部的编码和逻辑错误,但无法检验出程序是否完成了规定的功能;黑盒测试可以根据程序的规格说明检测出程序是否完成了规定的功能,但未必能够提供对代码的完全覆盖,而且规格说明往往会出现具有歧义或不完整的情况,这在一定程度上降低了黑盒测试的效果。因此在实际测试中,应结合各种测试方法形成综合策略。一般在单元测试阶段主要用白盒测试,在系统测试时主要用黑盒测试。
实际上,黑盒测试法和白盒测试法的界限现在已经变得越来越模糊了,因为单纯地根据规约或代码生成测试用例都不是很现实的。目前己有越来越多的人在尝试将这两种方法结合起来,例如根据规格说明来生成测试用例,然后根据代码(静态分析或动态执行代码)来进行测试用例的取舍和精化等,以至形成了所谓的“灰盒测试”法。这也是目前软件测试的一个发展方向。
### 人工测试与自动化测试
> 人工测试
广义上,人工测试是人为测试和手工测试的统称。人为测试的主要方法有桌前检查 (desk checking) ,代码审查( code review )和走查( walkthrough )。事实上,用于软件开发各个阶段的审查( inspection )或评审 (review) 也是人为测试的一种。经验表明,使用这种方法能够有效地发现 30% 到 70% 的逻辑设计和编码错误。由于人为测试技术在检查某些编码错误时,有着特殊的功效,它常常能够找出利用计算机不容易发现的错误。人为测试至今仍是一种行之有效的测试方法。手工测试指的是在测试过程中,按测试计划一步一步执行程序,得出测试结果并进行分析的测试行为。目前,在功能测试中经常使用这种测试方法。
> 自动化测试
自动化测试指的是利用测试工具来执行测试,并进行测试结果分析的测试行为。自动化测试不可能完全自动,它离不开人的智力劳动。但是它能替代人做一些繁琐或不可能通过手工达到的事情。由于测试工作的繁重性、重复性等特征,自动化测试是提高测试效率的一个有效方法,也是目前测试研究领域的一个热点。
## 软件测试阶段
软件测试贯串软件产品开发的整个生命周期,软件项目一开始软件测试也就开始了。从过程来看,软件测试是由一系列的不同测试阶段所组成的。这些阶段分为:
- 规格说明书审查
- 系统和程序设计审查
- 单元测试
- 集成测试
- 确认测试
- 系统测试以及验收(用户)测试。
软件开发的过程是自顶向下的,测试则正好相反,上述过程就是自底向上、逐步集成的。
**规格说明书审查**:为保证需求定义的质量,应对需求分析规格说明书进行严格的审查。由测试人员参与系统或产品需求分析,认真阅读有关用户需求分析文档,真正理解客户的需求,检查规格说明书对产品描述的准确性、一致性等,为今后熟悉应用系统、编写测试计划、设计测试用例等做好准备工作。
**系统和程序设计审查**代码会审是一种静态的白盒测试方法是由一组人通过阅读、讨论来审查程序结构、代码风格、算法等的过程。会审小组由组长、3-5 名程序设计人员、编程人员和测试人员组成。会审小组在充分阅读待审程序文本、控制流程图及有关要求、规范等文件基础上,召开代码会审会。实践表明,代码会审做得好的话可以发现大部分程序缺陷,甚至程序员在自己讲解过程中就能发现不少代码错误,而讨论可能进一步促使问题暴露。
- **单元测试**:单元测试集中对用源代码实现的每一个程序单元进行测试,检查各个程序模是否正确地实现了规定的功能。
- **集成测试**:该阶段把已测试过的模块组装起来,主要对与设计相关的软件体系结构的构造进行测试。
- **确认测试**:检查已实现的软件是否满足了需求规格说明中确定了的各种需求以及软件配置是否完全、正确。
- **系统测试**:把已经经过确认的软件纳入实际运行环境中,与其它系统成份组合在一起进行测试。
- **验收测试**:检验软件产品的最后一道工序,主要突出用户的作用,同时软件开发人员也应有一定的程度参与。
## 单元测试
单元测试又称模块测试,是针对软件设计的最小单位——程序模块,进行正确性检验的测试工作。其目的在于发现各模块内部可能存在的各种差错。这个阶段更多关注程序实现的细节,需要从程序的内部结构出发设计测试用例。多个模块可以平行地独立进行单元测试。
### 单元测试主要任务
在单元测试时,测试者需要依据详细设计说明书和源程序清单,了解该模块的 I/O条件和模块的逻辑结构主要采用白盒测试的测试用例辅之以黑盒测试的测试用例使之对任何合理的输入和不合理的输入都能鉴别和响应。它主要测试以下几方面的问题
- **模块接口测试**
- 单元测试的开始,应对通过被测模块的数据流进行测试,测试项目包括:
- 调用本模块的输入参数是否正确
- 本模块调用子模块时输入给子模块的参数是否正确
- 全局量的定义在各模块中是否一致
- 是否修改了只做输入用的形式参数
- 在做内外存交换时需要考虑:
- 文件属性是否正确
- OPEN 与 CLOSE 语句是否正确
- 缓冲区容量与记录长度是否匹配
- 在进行读写操作之前是否打开了文件
- 在结束文件处理时是否关闭了文件
- 正文书写/输入错误
- IO 错误是否检查并做了处理
- **局部数据结构测试**
- 不正确或不一致的数据类型说明
- 使用尚未赋值或尚未初始化的变量
- 错误的初始值或错误的缺省值
- 变量名拼写错或书写错
- 不一致的数据类型
- 上溢、下溢或地址异常
- **路径测试**
- 选择适当的测试用例,对模块中重要的执行路径进行测试。
- 应当设计测试用例查找由于错误的计算、不正确的比较或不正常的控制流而导致的错误。
- 对基本执行路径和循环进行测试可以发现大量的路径错误。
- **错误处理测试**
- 出错的描述是否难以理解。
- 出错的描述是否能够对错误定位。
- 显示的错误与实际的错误是否相符。
- 对错误条件的处理正确与否。
- 在对错误进行处理之前,错误条件是否已经引起系统的干预等。
- **边界测试**
- 注意数据流、控制流中刚好等于、大于或小于确定的边界值时出错的可能性,对这些地方要仔细地选择测试用例,认真加以测试。
- 如果对模块运行时间有要求的话,还要专门进行关键路径测试,以确定最坏情况下和平均意义下影响模块运行时间的因素。
### 单元测试执行过程
通常单元测试在编码阶段进行,在源程序代码编制完成,经过评审和验证,确认 没有语法错误之后,就开始进行单元测试的测试用例设计。利用设计文档,设计可以 验证程序功能、找出程序错误的多个测试用例。对于每一组输入,应有预期的正确结果。
模块并不是一个独立的程序,在考虑测试模块时,同时要考虑它和外界的联系, 用一些辅助模块去模拟与被测模块相联系的其它模块。这些辅助模块分为两种:
![image-20211201171933670](软件测试方法与过程.assets/image-20211201171933670.png)
- **驱动模块driver**:用以模拟被测模块的上级模块,它接收测试数据,把这些数据传送给被测模块,启动被测模块,最后输出实测结果。
- **桩模块stub**:也称为存根程序,用以模拟被测模块工作过程中所调用的子模块。桩模块由被测模块调用,它们一般只进行很少的数据处理,例如打印入口和返回,以便于检验被测模块与其下级模块的接口。桩模块可以做少量的数据操作,不需要把子模块所有功能都带进来,但不允许什么事情也不做。
被测模块与它相关的驱动模块及桩模块共同构成了一个“测试环境”。 如果一个模块要完成多种功能,且以程序包或对象类的形式出现,例如 Ada 中的包MODULA 中的模块C++中的类。这时可以将这个模块看成由几个小程序组成。对其中的每个小程序先进行单元测试要做的工作,对关键模块还要做性能测试。对支持某些标准规程的程序,更要着手进行互联测试。有人把这种情况特别称为模块测试,以区别单元测试。
## 集成测试
集成测试,也称为组装测试或联合测试。在单元测试的基础上,将所有模块按照设计要求组装成为子系统或系统,进行集成测试。实践表明,一些模块虽然能够单独地工作,但并不能保证连接起来也能正常的工作。程序在某些局部反映不出来的问题,在全局上很可能暴露出来,影响功能的实现。
### 集成模式
选择什么样的方式把模块组装起来形成一个可运行的系统,直接影响到测试成本、测试计划、测试用例的设计、测试工具的选择等。通常有两种集成方式:一次性集成方式和增量式集成方式。
- **一次性集成测试模式**:它是一种非增量式组装方式,也叫做整体拼装。使用这种方式,首先对每个模块分别进行模块测试,然后再把所有模块组装在一起进行测试,最终得到要求的软件系统。
- **增量式集成测试模式**:增量式的测试方法与非增量式的测试不同,它的集成是逐步实现的,集成测试也是逐步完成的,又称渐增式集成。也可以说它将单元测试与集成测试结合起来进行。首先对一个个模块进行模块测试,然后将这些模块逐步组装成较大的系统,在集成的过程中边连接边测试,以发现连接过程中产生的问题,通过增殖逐步组装成为要求的软件系统。
一次性集成测试的方法是先分散测试,然后集中一起来再一次完成集成测试。假如在模块的接口处存在错误,只会在最后的集成测试时一下子暴露出来。这时为每个错误定位和纠正非常困难,并且在改正一个错误的同时又可能引入新的错误,新旧错误混杂,更难断定出错的原因和位置。与此相反,增量式集成测试的逐步集成和逐步测试的方法,将可能出现的差错分散暴露出来,错误易于定位和纠正。而且一些模块在逐步集成的测试中,得到了较多次的考验,因此,接口测试更加彻底,能取得较好的测试结果。
总之,增量式测试要比非增量式测试具有一定的优越性。两种模式中,增量式测试模式虽然需要编写的 Driver 或 Stub 程序较多、发现模块间接口错误相对稍晚些,但增量式测试模式还是具有比较明显的优势。一次性集成测试模式一般不推
荐使用,不过在规模较小的应用系统中还是比较适合使用的。
### 集成方法
当对两个以上模块进行集成时,不可能忽视它们和周围模块的相互联系。为模拟这种联系,需设置若干辅助测试模块,也就是连接被测试模块的程序段。和单元测试阶段一样,辅助模块通常有驱动模块和桩模块两种。增量式集成测试可以按照不同的次序实施,因此通常有两种不同的方法,也就是自顶向下结合和自底向上结合。
**自顶向下集成** :自顶向下集成是从主控模块开始,按照软件的控制层次结构向下逐步把各个模块集成在一起。集成过程中可以采用深度优先或广度优先的策略。其中按深度方向组装的方式,可以首先实现和验证一个完整的软件功能。自顶向下集成的具体步骤可以描述为:
- 对主控模块进行测试,测试时用桩程序代替所有直接附属于主控模块的模块
- 根据选定的结合策略(深度优先或广度优先),每次用一个实际模块代替一个桩模块(新结合进来的模块往往又需要新的桩模块)
- 在结合下一个模块的同时进行测试
- 为了保证加入模块没有引进新的错误,可能需要进行回归测试(即全部或部分地重复以前做过的测试)
- 从第2步开始不断地重复进行上述过程直至完成
自顶向下集成能尽早地对程序的主要控制和决策机制进行检验,因此较早地发现错误。但是在测试较高层模块时,低层处理采用桩模块替代,不能反映真实情况,重要数据不能及时回送到上层模块,因此测试并不充分。自顶向下集成不需要驱动模块,但需要建立桩模块,要使桩模块能够模拟实际子模块的功能十分困难,因为桩模块在接收了所测模块发送的信息后需要按照它所代替的实际子模块功能返回应该回送的信息,这必将增加建立桩模块的复杂度,而且导致增加一些附加的测试。
另外,涉及复杂算法和真正输入/输出的模块一般在底层,它们是最容易出问题的模块,到组装和测试的后期才遇到这些模块,一旦发现问题,导致过多的回归测试。
**自底向上集成**:自底向上集成是从“原子”模块(即软件结构最低层的模块)开始组装测试。因为模块是自底向上进行组装,对于一个给定层次的模块,它的子模块(包括子模块的所有下属模块)已经组装并测试完成,所以不再需要桩模块,在模块的测试过程中需要从子模块得到的信息可以直接运行子模块得到。其具体步骤是:
- 把低层模块组合成实现某个特定软件子功能的族
- 写一个驱动程序(用于测试的控制程序),协调测试数据的输入和输出
- 对由模块组成的子功能族进行测试;
- 去掉驱动程序,沿软件结构自下上移动,把子功能族组合起来形成更大的子功能族
- 从第2步开始不断重复进行上述过程直至完成
自底向上集成的缺点是“程序一直未能作为一个实体存在,直到最后一个模块加上去后才形成一个实体”。就是说,在自底向上组装和测试的过程中,对主要的控制直到最后才接触到。但这种方式的优点是不需要桩模块,而建立驱动模块一般比建立桩模块容易,同时由于涉及到复杂算法和真正输入/输出的模块最先得到组装和测试,可以把最容易出问题的部分在早期解决。此外自底向上集成可以实施多个模块的并行测试,提高测试效率。
**混合集成**:自顶向下增殖的方式和自底向上增殖的方式各有优缺点。一般来讲,一种方式的优点是另一种方式的缺点。因此,具体测试时通常是把以上两种方式结合起来进行集成和测试。混合集成是自顶向下和自底向上集成的组合。一般对软件结构的上层使用自顶向下结合的方法,对下层使用自底向上结合的方法。
另外在组装测试时,应当确定关键模块,并尽量对这些关键模块及早进行测试。关键模块的特征是:满足某些软件需求;在程序的模块结构中位于较高的层次(高层控制模块);较复杂、较易发生错误;有明确定义的性能要求。
**持续集成**:在实际测试中,应该将不同集成模式有机结合起来,采用并行的自顶向下、自底向上混合集成方式,而更重要的是采取持续集成的策略。软件开发中各个模块不是同时完成,根据进度将完成的模块尽可能早的进行集成,有助于尽早发现缺陷,避免集成阶段大量缺陷涌现。同时自底向上集成时,先期完成的模块将是后期模块的桩程序,而自顶向下集成时,先期完成的模块将是后期模块的驱动程序,从而使后期模块的单元测试和集成测试出现了部分的交叉,不仅节省了测试代码的编写,也有力于提高工作效率。
**回归测试**:在软件生命周期中的任何一个阶段,只要软件发生了改变,就可能给该软件带来问题。软件的改变可能是源于发现了错误并做了修改,也有可能是因为在集成或维护阶段加入了新的模块。在增量型软件开发过程中,通常将软件分成阶段进行开发,在一个阶段的软件开发结束后将被测软件交给测试组进行测试,而下一个阶段增加的软件又有可能对原来的系统造成破坏。因此,每当软件发生变化时,我们就必须进行回归测试,重新测试原有的功能,以便确定修改是否达到了预期的目的,检查修改是否损害了原有的正常功能。
具体的方法可以是:对修改过的代码重新运行现有的测试,确定更改是否破坏了在更改之前有效的任何事物,并且在必要的地方编写新测试。执行回归测试时,首要考虑的应该是覆盖范围足够大但不浪费时间。尽可能少花时间执行回归测试,但不减少在旧的、已经测试过的代码中检测新失败的可能性。
此过程中需考虑的一些策略和因素包括下列内容:
- 即测试已修复的错误。程序员可能已经处理了症状,但并未触及根本原因
- 监视修复的副作用。错误本身可能得到了修复,但修复也可能造成其他错误
- 为每个修复的错误编写一个回归测试
- 如果两个或更多的测试类似,确定哪一个效率较低并将其删除
- 识别程序始终通过的测试并将它们存档
- 集中考虑功能性问题,而不是与设计相关的问题
- 更改数据(更改量可多可少)并找出任何产生的损坏
- 跟踪程序内存更改的效果
回归测试并不减少对系统新功能和特征的测试需求,回归测试也包括新功能和特征的测试。如果回归测试包不能达到所需的覆盖要求,必须补充新的测试用例使覆盖率达到规定的要求。
在实际工作中,可以将回归测试与兼容性测试结合起来进行。在新的配置条件下运行旧的测试可以发现兼容性问题,同时也可以揭示编码在回归方面的错误。
## 确认测试
确认测试又称有效性测试。它的任务是验证软件的有效性,即验证软件的功能和性能及其它特性是否与用户的要求一致。在软件需求规格说明书描述了全部用户可见的软件属性,其中有一节叫做有效性准则,它包含的信息就是软件确认测试的基础。在确认测试阶段主要进行有效性测试以及软件配置复审。
### 进行有效性测试(功能测试)
有效性测试是在模拟的环境(可能就是开发的环境)下,运用黑盒测试的方法,验证被测软件是否满足需求规格说明书列出的需求。为此,需要首先制定测试计划,规定要做测试的种类。还需要制定一组测试步骤,描述具体的测试用例。通过实施预定的测试计划和测试步骤,确定软件的特性是否与需求相符,确保所有的软件功能需求都能得到满足,所有的软件性能需求都能达到,所有的文档都是正确且便于使用。同时,对其它软件需求,例如可移植性、兼容性、出错自动恢复、可维护性等,也都要进行测试,确认是否满足。
### 软件配置复查
软件配置复查的目的是保证软件配置的所有成分都齐全,各方面的质量都符合要求,具有维护阶段所必需的细节,而且已经编排好分类的目录。
除了按合同规定的内容和要求,由人工审查软件配置之外,在确认测试的过程中,应当严格遵守用户手册和操作手册中规定的使用步骤,以便检查这些文档资料的完整性和正确性。必须仔细记录发现的遗漏和错误,并且适当地补充和改正。
## 系统测试
所谓系统测试,是将通过确认测试的软件,作为基于整个计算机系统的一个元素,与计算机硬件、外设、某些支持软件、数据和人员等其它系统元素结合在一起,在实际运行(使用)环境下,对计算机系统进行一系列的严格有效的测试以发现软件的潜在问题,保证系统的运行。
系统测试明显区别于功能测试。功能测试主要是验证软件功能的实现情况,不考虑各种环境以及非功能问题,如安全性、可靠性、性能等,而系统测试是在更大的范围内进行的测试,着重对系统的性能、特性进行测试。它的目的在于通过与系统的需求定义作比较,发现软件与系统定义不符合或与之矛盾的地方。所以系统测试的测试用例应该根据需求分析规格说明来设计,并在实际使用环境下来运行。
下面对系统测试的内容进行简要介绍:
### 强度测试
强度测试是要检查在系统运行环境不正常乃至发生故障的情况下,系统可以运行到何种程度的测试。强度测试需要在反常规数据量、频率或资源的方式下运行系统,以检查系统能力的最高实际限度。例如:输入数据速率提高一个数量级,确定输入功能将如何响应;或设计需要占用最大存储量或其它资源的测试用例进行测试。
强度测试的一个变种就是敏感性测试。在程序有效数据界限内一个小范围内的一组数据可能引起极端的或不平稳的错误处理出现,或者导致极度的性能下降的情况发生。此测试用以发现可能引起这种不稳定性或不正常处理的某些数据组合。
### 性能测试
性能测试用来测试软件在系统集成中的运行性能,检查其是否满足需求说明书中规定的性能,特别是对于实时系统或嵌入式系统,仅提供符合功能需求但不符合性能需求的软件是不能接受的。性能测试可以在测试过程的任意阶段进行,即使是在单元层,但只有当整个系统的所有成分都集成在一起后,才能检查一个系统的真正性能。性能测试常常需要与强度测试结合起来进行,并常常要求同时进行硬件和软件检测,这就是说,常常有必要在一种苛刻的资源环境中衡量资源的使用。
通常,对软件性能的检测表现在以下几个方面:响应时间、吞吐量、辅助存储区,例如缓冲区,工作区的大小等、处理精度,等等。
外部的测试设备可以检测测试执行,当出现某种情况时可以记录下来。通过对系统的检测,测试者可以发现导致效率降低和系统故障的原因。为了记录性能,需要在系统中安装必要的量测仪表或者为度量性能而设置的软件。
### 恢复测试
恢复测试是要证实在克服硬件故障(包括掉电、硬件或网络出错等)后,系统能否正常地继续进行工作,并不对系统造成任何损害。为此,可采用各种人工干预的手段,模拟硬件故障,故意造成软件出错,并由此检查系统的错误探测功能──系统能否发现硬件失效与故障;能否切换或启动备用的硬件;在故障发生时能否保护正在运行的作业和系统状态;在系统恢复后能否从最后记录下来的无错误状态开始继续执行作业,等等。
### 安全测试
安全性测试是要检验在系统中已经存在的系统安全性、保密性措施是否发挥作用,有无漏洞,以检查系统对非法侵入的防范能力。安全测试期间,测试人员假扮非法入侵者,采用各种方法试图突破防线。系统安全设计的准则是,使非法侵入的代价超过被保护信息的价值。
### 可靠性测试
软件可靠性是软件系统在规定的时间内和规定的环境条件下,完成规定功能的能力。它是软件系统的固有特性之一,表明了一个软件系统按照用户的要求和设计目标,执行其功能的可靠程度。软件可靠性与软件缺陷有关,也与系统输入与系统使用有关。理论上说,可靠的软件系统应该是正确、完整、一致和健壮的。
根据在测试过程中收集获得的失效数据,如失效间隔时间、失效修复时间、失效数量、失效级别等,应用可靠性模型,可以得到系统的失效率及可靠性增长趋势。其中可靠性增长趋势是测试开始时的失效率与测试结束时的失效率之比。
从黑盒(占主要地位)和白盒测试两个角度出发有以下几种常用的可靠性模型。
- **黑盒方面的可靠性模型**包括了基本执行时间模型Musa、故障分离模型Jelinski-Moranda、NHPP 模型及增强的 NHPP 模型Goel-Okumoto以及贝叶斯判定模型Littlewood-Verrall
- **在白盒方面的可靠性模型**包括了基于路径的模型Krishna-Murthy 和 Mathur和基于状态的模型Gokhale,et al.)。
对于相同的数据,不同的模型可以得到不同的结果,有些结果可能大相径庭。这往往是因为不同的模型基于的假设条件不同而造成的。
业界流行的可靠性模型还有很多种,不同的可靠性模型其依赖的假设条件也是不同的,使用范围也不同。因此对于一个产品,其所适合使用的可靠性模型需要根据实际出发,尽可能选择与可靠性模型假设条件相近的模型。
### 安装测试
理想情况下,一个软件的安装程序应当平滑地集成用户的新软件到已有的系统中去,就象一个客人被介绍到一个聚会中去一样,彼此交换适当的问候。一些对话窗口提供简单的、容易理解的安装选项和支持信息,并且完成安装过程。然而,在某些糟糕的情况下,安装程序可能会做错误的事情,使新的程序无法工作,已有的功能受到影响,甚至安装过程严重损坏用户系统。
在安装软件系统时,会有多种选择:要分配和装入文件与程序库;布置适用的硬件配置;进行程序的联结。而安装测试就是要找出在这些安装过程中出现的错误,其目的是要验证成功安装系统的能力。它通常是开发人员的最后一个活动,并且通常在开发期间不太受关注。但是,它是客户使用新系统时执行的第一个操作。因此,清晰并且简单的安装过程是系统文档中最重要的部分。
### 容量测试
容量测试是根据预先分析出反映软件系统应用特征的某项指标极限值(如最大并发用户数,最大数据库记录数等),测试系统在其极限值状态下是否能保持主要功能正常运行。例如:对于编译程序,让它处理特别长的源程序;对于操作系统,让它的作业队列“满员”;对于信息检索系统,让它使用频率达到最大。在使用系统的全部资源达到“满负荷”的情形下,测试系统的承受能力。
### 文档测试
文档测试是检查用户文档(如用户手册)的清晰性和精确性。在用户文档中所使用的例子必须在测试中测试过,确保叙述正确无误。
## 验收测试
验收测试是软件产品完成系统测试后,在发布之前所进行的软件测试活动,它是技术测试的最后一个阶段。通过验收测试,产品就会进入发布阶段。验收测试的目的是确保软件准备就绪,并且可以让最终用户将其用于执行软件的既定功能和任务。它是向未来的用户表明系统能够像预定要求那样工作,应检查软件能否按合同要求进行工作,即是否满足软件需求说明书中的确认标准。
实施验收测试既可以是非正式的测试,也可以是有计划、有系统的测试。在软件交付使用之后,用户将如何实际使用程序,对于开发者来说是无法预测的。因为用户在使用过程中常常会发生对使用方法的误解、异常的数据组合、以及产生对某些用户来说似乎是清晰的但对另一些用户来说却难以理解的输出等等。由于一个软件产品可能拥有众多用户,不可能由每个用户都进行验收,而且初期验收测试中大量的错误可能导致开发延期,甚至吓跑用户,因此多采用一种称为α、β测试的过程,以发现可能只有最终用户才能发现的错误。
### α测试
α测试是软件开发公司组织内部人员模拟各类用户对即将面世的软件产品(称为α版本)进行测试。这是在受控制的环境下进行的测试。它的关键在于要尽可能逼真地模拟实际运行环境和用户对软件产品的操作,并尽最大努力涵盖所有可能的用户操作方式。α测试人员是除产品开发人员之外首先见到产品的人,他们提出的功能和修改意见是特别有价值的。α测试可以从软件产品编码结束之时开始,或在模块(子系统)测试完成之后开始,也可以在确认测试过程中产品达到一定的稳定和可靠程度之后再开始。有关的手册(草稿)等应事先准备好。经过α测试调整的软件产品称为β版本。
### β测试
β测试是由软件的多个用户在一个或多个用户的实际使用环境下进行的测试。与α测试不同的是,开发者通常不在测试现场。因而,β测试是在开发者无法控制的环境下进行的软件现场应用。在β测试中,由用户记下遇到的所有问题,包括真实的以及主观认定的,定期向开发者报告,开发者在综合用户的报告之后,做出修改,最后将软件产品交付给全体用户使用。只有当α测试达到一定的可靠程度时,才能开始β测试。由于它处在整个测试的最后阶段,不能指望这时发现主要问题。同时,产品的所有手册文本也应该在此阶段完全定稿。由于β测试的主要目标是测试可支持性,所以β测试应尽可能由主持产品发行的人员来管理。
## 面向对象软件测试
### 面向对象软件的单元测试
传统的单元测试的对象是软件设计的最小单位——模块。单元测试应对模块内所有重要的控制路径设计测试用例,以便发现模块内部的错误。单元测试多采用白盒测试技术,系统内多个模块可以并行地进行测试。
当考虑面向对象软件时,单元的概念发生了变化。封装驱动了类和对象的定义,
这意味着每个类和类的实例(对象)包装了属性(数据)和操纵这些数据的操作。一个类可以包含一组不同的操作,而一个特定的操作也可能存在于一组不同的类中。因此,单元测试的意义发生了较大变化。我们不再孤立地测试单个操作,而是将操作作为类的一部分。此时最小的可测试单位是封装的类或对象,而不再是个体的模块。
![image-20211201173618104](软件测试方法与过程.assets/image-20211201173618104.png)
### 面向对象软件的集成测试
面向对象软件没有层次的控制结构,因此传统的自下而上或自上而下的集成测试策略并不适用于面向对象方法构造的软件,需要研究适合面向对象特征的新的集成测试策略。其测试有两种不同策略:
- **基于类间协作关系的横向测试**:由系统的一个输入事件作为激励,对其触发的一组类进行测试,执行相应的操作/消息处理路径,最后终止于某一输出事件。应用回归测试对已测试过的类集再重新执行一次,以保证加入新类时不会产生意外的结果。
- **基于类间继承关系的纵向测试**:首先通过测试不使用或很少使用其他类服务的类,即独立类(是系统中已经测试正确的某类)来开始构造系统。在独立类测试完成后,下一层继承独立类的类(称为依赖类)被测试,这个依赖类层次的测试序列一直循环执行到构造完整个系统。
### 面向对象软件的系统测试
系统测试应该尽量搭建与用户实际使用环境相同的测试平台,应该保证被测系统的完整性,对临时没有的系统设备部件,也应有相应的模拟手段。系统测试时,应该参考 OOA 分析的结果,对应描述的对象、属性和各种服务,检测软件是否能够完全“再现”问题空间。系统测试不仅是检测软件的整体行为表现,从另一个侧面看,也是对软件开发设计的再确认。

@ -0,0 +1,572 @@
## 黑盒测试法概述
> 概述
黑盒测试又称为功能测试或数据驱动测试,着眼于程序外部结构,将被测试程序视为一个不能打开的黑盒子,完全不考虑程序内部逻辑结构和内部特性,主要针对软件界面、软件功能、外部数据库访问以及软件初始化等方面进行测试。
![image-20211201173901972](黑盒测试.assets/image-20211201173901972.png)
> 主要目的
黑盒测试的目的主要是在已知软件产品应具有的功能的基础上,发现以下类型的错误:
- 检查程序功能是否按照需求规格说明书的规定正常使用,测试每个功能是否有遗漏,检测性能等特性要求是否满足要求。
- 检测人机交互是否错误,检测数据结构或外部数据库访问是否错误,程序是否能够适当地接收数据而产生正确的输出结果,并保持外部信息(如数据库或文件)的完整性。
- 检测程序初始化和终止方面的错误。
黑盒测试属于穷举输入测试方法,只有将所有可能的输入都作为测试情况来使用,才能检查出程序中所有的错误。但穷举测试是不现实的,因此我们需要选择合适的方法使设计出来的测试用例具有完整性、代表性,并能有效地发现软件缺陷。
> 黑盒测试常用的方法和技术
![image-20211201174008210](黑盒测试.assets/image-20211201174008210.png)
## 边界值测试
### 边界值分析法
边界值分析法BVABoundary Value Analysis )是一种很实用的黑盒测试用例设计方法,它具有很强的发现程序错误的能力。无数的测试实践表明,大量的故障往往发生在输入定义区域或输出值域的边界上,而不是在其内部,如做一个除法运算的例子,如果测试者忽略被除数为 0 的情况就会导致问题的遗漏。所以在设计测试用例 时,一定要对边界附近的处理十分重视。为检验边界附近的处理专门设计测试用例, 通常都会取得很好的效果。
> 基本思想
应用边界值分析的基本思想是:选取正好等于、刚刚大于和刚刚小于边界值的数据作为测试数据。
边界值分析法是最有效的黑盒分析法,但在边界情况复杂的情况下, 要找出适当的边界测试用例还需要针对问题的输入域、输出域边界,耐心细致地逐个进行考察。
> 边界值分析
边界值分析关注的是输入、输出空间的边界条件,以标识测试用例。实践证明,程序在处理大量中间数值时都正确,但在边界处却往往可能出现错误。例如,循环条件漏写了等于,计数器少计了一次或多计了一次,数组下标忽略了 0 的处理等等,这些都是我们平时编程容易疏忽而导致出错的地方。
刚开始时,可能意识不到一组给定数据包含了多少边界,但是仔细分析总可以找到一些不明显的、有趣的或可能产生软件故障的边界。
实际上,边界条件就是软件操作界限所在的边缘条件。 一些可能与边界有关的数据类型有:数值、速度、字符、位置、尺寸、数量等。
同时,针对这些数据类型可以考虑它们的下述特征:第一个/最后一个,最小/最大,开始/完成,超过/在内,空/满等。以上是一些可能出现的边界条件。
实际应用中,每一个软件测试问题都不完全相同,可能包含各式各样的边界条件,应视具体情况而定。
> 内部边界值分析
上面边界值分析中所讨论的边界条件比较容易发现,它们在软件规格说明中或者有定义,或者可以在使用软件的过程中确定。而有些边界却是在软件内部,用户几乎看不到,但我们在进行软件测试时仍有必要对它们进行检查,这样的边界条件称为内部边界条件或次边界条件。
寻找内部边界条件比较困难,虽然不要求软件测试人员成为程序员或者具有阅读源代码的能力,但要求软件测试人员能大体了解软件的工作方式。
例如对文本输入或 文本转换软件进行测试,在考虑数据区间包含哪些值时,最好参考一下 ASCII 表。如果测试的文本输入框只接受用户输入字符 A——Z 和 a——z就应该在非法区间中 检查 ASCII 表中刚好位于 A 和 a 前面Z 和 z 后面的值——@ [ 和 { 。
### 边界值分析法测试用例
> 边界分析测试的基本思想
为便于理解,假设有两个变量 x1 和 x2 的函数 F其中函数 F 实现为一个程序, x1、x2 在下列范围内取值: a≤x1≤bc≤x2≤d 区间[ab]和[cd]是 x1、x2 的值域,程序 F 的输入定义域如下图所示,即带阴影矩 形中的任何点都是程序 F 的有效输入。
![image-20211201183816380](黑盒测试.assets/image-20211201183816380.png)
采用边界值分析测试的基本原理是:故障往往出现在输入变量的边界值附近。
例如,当一个循环条件为“≤”时,却错写成“<”,计时器发生少计数一次。
![image-20211201183838327](黑盒测试.assets/image-20211201183838327.png)
边界值分析测试的基本思想是使用在最小值min、略高于最小值min+、正常值nom、略低于最大值max-和最大值max处取输入变量值。同时对于有多个输入变量的情况我们通常是基于可靠性理论中称为“单故障”的假设这种假设认为有两个或两个以上故障同时出现而导致软件失效的情况很少也就是说软件失效基本上是由单故障引起的。因此边界值分析测试用例的获得是通过使一个变量取极值剩下所有变量取正常值。
因此,边界值分析测试用例的获得,是通过使一个变量 取极值,剩下所有变量取正常值。前面有两个输入变量的程序 F 的边界值分析测试用例(如图 3.2 所示)是:
![image-20211201184120162](黑盒测试.assets/image-20211201184120162.png)
{ < x1nom ,x2min >, < x1nom ,x2min+ >, < x1nom , x2nom >, < x1nom , x2max- >, < x1nom , x2max >,< x1min , x2nom >,< x1min+ , x2nom >, < x1max- , x2nom >, < x1max , x2nom > }
对于一个含有 n 个变量的程序,保留其中一个变量,让其余的变量取正常值,被 保留的变量依次取 min、 min+、nom、max-、max 值,对每个变量都重复进行。这样, 对于一个有 n 个变量的程序,边界值分析测试程序会产生 4n+1 个测试用例。如果没有显式的给出边界,如三角形问题,则必须创建一种人工边界,可以先设定下限值(边长应大于等于 1并规定上限值如 100或取默认的最大可表示的整数值。
![image-20211201184148234](黑盒测试.assets/image-20211201184148234.png)
> 健壮性测试
健壮性测试是边界分析测试的一种简单扩展,除了取 5 个边界值分析取值外还需要考虑采用一个略超过最大值max+以及略小于最小值min-)的取值,检查超过极限值时系统的表现会是什么。健壮性测试最有意义的部分不是输入,而是预期的 输出。它要观察例外情况如何处理,比如某个部分的负载能力超过其最大值可能出现的情形。健壮性测试如图 3.3 所示。
![image-20211201184223797](黑盒测试.assets/image-20211201184223797.png)
### 边界值分析法测试实例
> 三角形问题
![image-20211201184332302](黑盒测试.assets/image-20211201184332302.png)
问题描述:
三角形问题接受三个整数 a、b 和 c 作为输入,用做三角形的边。
程序的输出是由这三条边确定的三角形类型:等边三角形、等腰三角形、不等边三角形或非三角形。
通过提供更多细节可以改进这个定义。于是这个问题变成以下的形式。
三角形问题接受三个整数 a、b 和 c 作为输入,用做三角形的边。整数 a、b 和 c 必须满足以下条件:
![image-20211201184514708](黑盒测试.assets/image-20211201184514708.png)
程序的输出是由这三条边确定的三角形类型:等边三角形、等腰三角形、不等边三角形或非三角形。如果输入值没有满足 c1、c2 和 c3 这些条件中的任何一个则程序会通过输出消息来进行通知例如“b 的取值不在允许取值的范围内。”如果 a、b 和 c 取值满足 c1、c2 和 c3则给出以下四种相互排斥输出中的一个
- 如果三条边相等,则程序的输出是等边三角形。
- 如果恰好有两条边相等,则程序的输出是等腰三角形。
- 如果没有两条边相等,则程序输出的是不等边三角形。
- 如果 c4、c5 和 c6 中有一个条件不满足,则程序输出的是非三角形。
在三角形问题描述中,除了要求边长是整数外,没有给出其它的限制条件。边界下限为 1上限为 100。表 3.1 给出了边界值分析测试用例。
![image-20211201184707417](黑盒测试.assets/image-20211201184707417.png)
>NextDate 函数
问题描述:
NextDate 是一个有三个变量(月份、日期和年)的函数。函数返回输入日期后面的那个日期。变量月份、日期和年都具有整数值,且满足以下条件:
- c11≤月份≤12
- c21≤日期≤31
- c31912≤年≤2050
在NextDate 函数中,规定了变量 month、day、year 相应的取值范围,即 1 ≤ month ≤ 121 ≤ day ≤311912 ≤ year ≤ 2050表 3.2 给出了其健壮性测试用例。
![image-20211201184826162](黑盒测试.assets/image-20211201184826162.png)
边界值分析局限性:
如果被测程序是多个独立变量的函数,这些变量受物理量的限制,则很适合采用边界值分析。这里的关键是“独立”和“物理量”。
![image-20211201184826162](黑盒测试.assets/image-20211201184826162.png)
简单地看一下表 3.2 中 NextDate 函数的边界分析测试用例,就会发现其实这些测试用例是不充分的。
例如,没强调 2 月和闰年。这里的真正问题是,月份、日期和年 变量之间存在依赖关系,而边界值分析假设变量是完全独立的。不过即便如此,边界值分析也能够捕获月末和年末缺陷。
边界值分析测试用例通过引用物理量的边界独立导出变量极值,不考虑函数的性质,也不考虑变量的语义含义。因此我们把边界值分析测试用例看作是初步的,这些测试用例的获得基本没有利用理解和想象。
物理量准则也很重要。如果变量引用某个物理量,例如温度、压力、空气速度、 负载等,则物理边界极为重要。举一个这方面的例子:
菲尼克斯的航空港国际机场 1992 年 6 月 26 日被迫关闭,原因是当天的空气温度达到 122 ℉导致飞行员在起飞之前不能设置某一特定设备,因为该设备能够接受的最大空气温度是 120 ℉。
边界值分析对布尔变量和逻辑变量没有多大意义。例如布尔变量的极值是 TRUE 和 FALSE但是其余三个值不明确。
我们在后面章节可以看到,布尔变量可以采用基于决策表的测试。
## 等价类测试
使用等价类作为功能性能测试的基础有两个动机:我们希望进行完备的测试,同时又希望避免冗余。
边界值测试不能实现这两种希望中的任意一个:研究那些测试用例表,很容易看出存在大量冗余,再进一步仔细研究,还会发现严重漏洞。
等价类测试重复边界值测试的两个决定因素:健壮性和单/多缺陷假设。本节我们给出了4种形式的等价类测试在弱/强等价类测试之分的基础之上针对是否进行无效数据的处理产生健壮与一般等价类测试之分。
### 等价类
等价类的重要特征是对它们构成集合的一个划分,其中,划分是指互不相交的一组子集,并且这些子集的并是整个集合。
这对于测试有两点非常重要的意义:表示整个集合这个事实提供了一种形式的完备性,而互不相交可保证一种形式的无冗余性。由于子集是由等价关系决定的,因此子集的元素都有一些共同点。
等价类测试的思想是通过每个等价类中的一个元素标识测试用例。
如果广泛选择等价类,则可以大大降低测试用例之间的冗余。
例如在三角形问题中我们当然要有一个等边三角形的测试用例我们可能选择三元组101010作为测试用例的输入。
如果这样做了, 则可以预期不会从诸如333100100100这样的测试用例中得到多少新东西。直觉告诉我们这些测试用例会以与第一个测试用例一样的方式进行“相同处理”因此这些测试用例是冗余。
当我们在考虑结构性测试时,将会看到“相同处理”映射到“遍历相同的执行路径。”
![image-20211201185246770](黑盒测试.assets/image-20211201185246770.png)
等价类测试的关键就是选择确定类的等价关系。通常我们通过预测可能的实现, 考虑在现实中必须提供的功能操作来做出这种选择。我们将用一系列例子说明这一 点,但是首先必须区分弱和强等价类测试。
为了便于理解,我们还是讨论与有两个变量 x1 和 x2 的函数 F 联系起来。如果 F 实现为一个程序,则输入变量 x1 和 x2 将拥有以下边界,以及边界内的区间:
![image-20211201185314275](黑盒测试.assets/image-20211201185314275.png)
其中方括号和圆括号分别表示闭区间和开区间的端点。x1 和 x2 的无效值是x1<ax1>d 以及 x2<ex2>g。
- **弱一般等价类测试**
![image-20211201185353718](黑盒测试.assets/image-20211201185353718.png)
弱一般等价类测试通过使用测试用例的每个等价类(区间)的一个变量实现(单缺陷 假设的作用)。对于上面给出的例子,可得到如图 3.4 所示的弱等价类测试用例。
![image-20211201185411375](黑盒测试.assets/image-20211201185411375.png)
这三个测试用例使用每个等价类中的一个值。事实上,永远都有等量的弱等价类测试用例,因为划分中的类对应最大子集数。
- **强一般等价类测试**
强一般等价类测试基于多缺陷假设,它需要等价类笛卡儿积的每个元素对应的测试用 例(如图 3.5 所示)。笛卡儿积可保证两种意义上的“完备性”:一是覆盖所有的等价类, 二是覆盖所有可能的输入组合中的每一个。
![image-20211201185510350](黑盒测试.assets/image-20211201185510350.png)
“好的”等价类测试的关键是等价关系的选择,要注意被“相同处理”的输入。在大多数情况下,等价类测试定义输入定义域的等价类。不过,其实也没有理由不能根据被测程序函数的输出值域定义等价关系。事实上,这对于三角形问题是最简单的方法。
- **弱健壮等价类测试**
这种测试的名称显然与直觉矛盾,怎么能够既弱又健壮呢?其实这是因为它是基于两个不同的角度而命名的。说它弱是因为它基于单缺陷假设,说它健壮是因为这种测试考虑了无效值
1对于有效输入弱健壮等价类测试使用每个有效类的一个值就像我们在弱一般等价类测试中所做的一样。请注意这些测试用例中的所有输入都是有效的。
2对于无效输入弱健壮等价类测试的测试用例将拥有一个无效值并保持其余的值都是有效的。此时“单缺陷”会造成测试用例失败。
![image-20211201185622304](黑盒测试.assets/image-20211201185622304.png)
对于健壮等价类测试通常有两个问题。
第一是:规格说明常常并没有定义无效测试用例所预期的输出是什么。因此,测试人员需要花大量时间定义这些测试用例的输出。
第二是:强类型语言没有必要考虑无效输入。
![image-20211201185646617](黑盒测试.assets/image-20211201185646617.png)
- **强健壮等价类测试**
强健壮等价类测试,“强”是指该类测试用例的获得是基于多缺陷假设,“健壮” 则和前面的定义一样,是指考虑了无效值。如图 3.7 所示,强健壮等价类测试从所有等价类笛卡尔积的每个元素中获得测试用例
![image-20211201185720758](黑盒测试.assets/image-20211201185720758.png)
### 等价类测试实例
> 三角形问题
在描述问题时,我们曾经提到有四种可能出现的输出:非三角形、不等边三角形、 等腰三角形和等边三角形。可以使用这些输出,标识如下所示的输出(值域)等价类:
- R1={:有三条边 a、b 和 c 的等边三角形}
- R2={:有三条边 a、b 和 c 的等腰三角形}
- R3={:有三条边 a、b 和 c 的不等边三角形}
- R4={:有三条边 a、b 和 c 的非三角形}
四个弱一般等价类测试用例是:
![image-20211201185855380](黑盒测试.assets/image-20211201185855380.png)
由于变量 a、b 和 c 没有有效区间划分,则强一般等价类测试用例与弱一般等价类测试用例相同。
考虑 a、b 和 c 的无效值产生的以下额外弱健壮等价类测试用例:
![image-20211201185914625](黑盒测试.assets/image-20211201185914625.png)
以下是额外强健壮等价类测试用例三维立方的一个“角”:
![image-20211201185928154](黑盒测试.assets/image-20211201185928154.png)
请注意,预期输出如何完备地描述无效输入值。
等价类测试显然对于用来定义的等价关系很敏感。如果在输入定义域上定义等价类,则可以得到更丰富的测试用例集合。三个整数 a、b 和 c 有些什么可能的取值呢? 这些整数相等(有三种相等方式),或都不相等。
![image-20211201185951454](黑盒测试.assets/image-20211201185951454.png)
作为一个单独的问题,我们可以通过三角形的性质来判断三条边是否构成一个三角形(例如,三元组有一对相等的边,但是这些边不构成一个三角形)
![image-20211201190007123](黑盒测试.assets/image-20211201190007123.png)
如果我们要彻底一些可以将“小于或等于”分解为两种不同的情况这样D6就变成
![image-20211201190021538](黑盒测试.assets/image-20211201190021538.png)
同样对于 D7 和 D8 也有类似的情况。
> NextDate 函数
NextDate 函数可以很好地说明选择内部等价关系的工艺。前面已经介绍过NextDate 是一个三变量函数,即月份、日期和年,这些变量的有效值区间定义如下:
- M1={月份1≤月份≤12}
- D1={日期1≤月份≤31}
- Y1={年1812≤月份≤2012}
无效等价类是:
- M2={月份月份12}
- D2={日期日期31}
- Y2={年年2012}
由于每个独立变量的有效区间均为 1 个,因此只有弱一般等价类测试用例出现, 并且与强一般等价类测试用例相同:
![image-20211201190152773](黑盒测试.assets/image-20211201190152773.png)
以下是弱健壮测试用例的完整集合:
![image-20211201190219195](黑盒测试.assets/image-20211201190219195.png)
与三角形问题一样,以下是额外强健壮性等价类测试用例三维立方的一个“角”:
![image-20211201190232104](黑盒测试.assets/image-20211201190232104.png)
划分等价关系的重点是等价类中的元素要被“同样处理”。上述方法所得测试用例集其实是不足的,因为它只注意到在单个变量处理的有效/无效层次上进行,而没有进一步分析具体处理的过程与特征。对该函数如果更仔细地选择等价关系,所得到的等价类和测试用例集将会更有用。
例如在 NextDate 函数中,注意到必须对输入日期做怎样的处理?
如果它不是某个月的最后一天,则 NextDate 函数会直接对日期加 1。到了月末下一个日期是 1月份加 1。到了年末日期和月份会复位到 1年加 1。最后闰年问题要确定有关的月份的最后一天。
经过这些分析之后,可以假设有以下等价类:
- M1={月份:每月有 30 天}
- M2={月份:每月有 31 天}
- M1={月份:此月是 2 月}
- D1={日期1≤日期≤28}
- D2={日期:日期=29}
- D3={日期:日期=30}
- D4={日期:日期=31}
- Y1={年:年=2000}
- Y2={年:年是闰年}
- Y3={年:年是平年}
通过选择有 30 天的月份和有 31 天的月份的独立类,可以简化月份最后一天问题。 通过把 2 月分成独立的类可以对闰年问题给予更多关注。我们还要特别关注日期的值D1中的日差不多总是加 1D4 中的日只对 M2 中的月才有意义。最后,年有三个类,包括 2000 年这个特例、闰年和非闰年类。这并不是完美的等价类集合, 但是通过这种等价类集合可以发现很多潜在错误。
这些类产生以下弱等价类测试用例。与前面一样,机械地从对应类的取值范围中选择输入:
![image-20211201190356493](黑盒测试.assets/image-20211201190356493.png)
机械选择输入值不考虑领域知识,因此没有考虑两种不可能出现的日期。“自动” 测试用例生成永远都会有这种问题,因为领域知识不是通过等价类选择获得的。
经过改进的强一般等价类测试用例是:
![image-20211201190417520](黑盒测试.assets/image-20211201190417520.png)
![image-20211201190447624](黑盒测试.assets/image-20211201190447624.png)
从弱一般测试转向强一般测试会产生一些边界值测试中也出现的冗余问题。从弱到强的转换不管是一般类还是健壮类都是以等价类的叉积表示。3 个月份类乘以 4 个日期类乘以 3 个年类,产生 36 个强一般等价类测试用例。对每个变量加上 2 个无效类,得到 150 个强健壮等价类测试用例。
通过更仔细地研究年类,还可以精简测试用例集合。通过合并 Y1 和 Y3把结果 称做平年,则 36 个测试用例就会降低到 24 个。这种变化不再特别关注 2000 年,并 58 会增加判断闰年的难度。需要在难度和能够从当前用例中了解到的内容之间做平衡综合考虑。
> 佣金问题
问题描述前亚利桑那州境内的一位步枪销售商销售密苏里州制造商制造的步枪机lock、枪托stock和枪管barrel。枪机卖 45 美元,枪托卖 30 美元,枪管卖 25 美元。销售商每月至少要售出一支完整的步枪,且生产限额是大多数销售商在一个 月内可销售 70 个枪机、80 个枪托和 90 个枪管。每访问一个镇子之后,销售商都给密 苏里州步枪制造商发出电报,说明在那个镇子中售出的枪机、枪托和枪管数量。到了 月末销售商要发出一封很短的电报通知1 个枪机被售出。这样步枪制造商就知道当月的销售情况并计算销售商的佣金如下销售额不到1000 美元的部分为 101000不含1800美元的部分为 15超过 1800 美元的部分为 20。 佣金程序生成月份销售报告,汇总售出的枪机、枪托和枪管总数,销售商的总销售额 以及佣金。
佣金问题的输入定义域,由于枪机、枪托和枪管的限制而被“自然地”划分。这些等价类也正是通过传统等价类测试所标识的等价类。第一个类是有效输入,其他两个类是无效输入。在佣金问题中,仅考虑输入定义域等价类产生的测试用例集合非常不能令人满意。通过进一步分析,我们能够发现对佣金函数的输出值域定义等价类可以有效改进测试用例集合。
输入变量对应的有效类是:
- L1={枪机1≤枪机≤70}
- L2={枪机=-1}
- S1={枪托1≤枪托≤80}
- B1={枪管1≤枪托≤90}
输入变量对应的无效类是:
- L2={枪机:枪机=0 或枪机<-1}
- L3={枪机:枪机>70}
- S2={枪托:枪托<1}
- S3={枪管:枪管>80}
- B2={枪管:枪管<1}
- B3={枪管:枪管>90}
其中变量枪机还用做指示不再有电报的标记。当枪机等于-1 时While 循环就会终止,总枪机、总枪托和枪管的值就会被用来计算销售额,进而计算佣金,因此对于变量枪机增加了第 2 个有效类 L2。
根据上述等价类的划分,可得如下所示佣金问题的弱一般等价类测试用例,这个 测试用例同样也等于强一般等价类测试用例。
![image-20211201230357490](黑盒测试.assets/image-20211201230357490.png)
七个弱健壮测试用例如下。
![image-20211201230412831](黑盒测试.assets/image-20211201230412831.png)
最后,额外强健壮等价类测试用例三维立方的一个“角”是:
![image-20211201230430165](黑盒测试.assets/image-20211201230430165.png)
请注意,对于强测试用例,不管是强一般测试用例还是强健壮测试用例,都只有一个是合理输入。如果确实担心错误案例,那么这就是很好的测试用例集合。但是这样很 难确信佣金问题的计算部分没有问题。在本例中,我们可以通过对输出值域定义等价类来进一步完善测试。前面提到过,销售额是所售出的枪机、枪托和枪管数量的函数:
![image-20211201230451253](黑盒测试.assets/image-20211201230451253.png)
由此得到如下的输出值域等价类测试用例:
![image-20211201230507137](黑盒测试.assets/image-20211201230507137.png)
这些测试用例让人感觉到正在接触问题的重要部分。与弱健壮测试用例结合在一 起,就可得到佣金问题的相当不错的测试。另外,可能还希望增加一些边界检查,只是为了保证从 1000 美元到 1800 美元的转移是正确的。
### 指导方针
我们已经介绍了三个例子,最后讨论关于等价类测试的一些观察和等价类测试指导方针。
- 显然,等价类测试的弱形式(一般或健壮)不如对应的强形式的测试全面。
- 如果实现语言的强类型(无效值会引起运行时错误),则没有必要使用健壮 形式的测试。
- 如果错误条件非常重要,则进行健壮形式的测试是合适的。
- 如果输入数据以离散值区间和集合定义,则等价类测试是合适的。当然也适用于如果变量值越界就会出现故障的系统。
- 通过结合边界值测试,等价类测试可得到加强。(我们可以“重用”定义等价类的工作成果。)
- 如果程序函数很复杂,则等价类测试是被指示的。在这种情况下,函数的复 杂性可以帮助标识有用的等价类,就像 NextDate 函数一样。
- 强等价类测试假设变量是独立的,相应的测试用例相乘会引起冗余问题。而 如果存在依赖关系,则常常会生成“错误”测试用例,就像 NextDate 函数一 样(此时最好采用决策表技术解决)。
- 在发现“合适”的等价关系之前,可能需要进行多次尝试,就像 NextDate 函 数例子一样。如果不能肯定存在“明显”或“自然”等价关系,最好对任何合理 的实现进行再次预测。
## 基于决策表的测试
在所有的功能性测试方法中,基于决策表的测试方法是最严格的,因为决策表具有逻辑严格性。
### 决策表介绍
自从 20 世纪 60 年代初以来,决策表一直被用来表示和分析复杂逻辑关系。决策 61 表很适合描述不同条件集合下采取行动的若干组合的情况。
![image-20211201230705200](黑盒测试.assets/image-20211201230705200.png)
决策表有四个部分:粗竖线左侧是桩部分;右侧是条目部分。横粗线的上面是条件部分,下面是行动部分。因此,我们可以引用条件桩、条件条目、行动桩和行动条目。条目部分中的一列是一条规则。规则只是在规则的条件部分中指示的条件环境下要采取什么行动。在表 3.3 给出的决策表中,如果 c1、c2 和 c3 都为真,则采取行动 a1 和 a2。如果 c1 和 c2 都为真而 c3 为假,则采取行动 a1 和 a3。在 c1 为真 c2 为假的 条件下采取行动 a4此时规则中的 c3 条目叫做“不关心”条目。不关心条目有两种主 要解释:条件无关或条件不适用。
如果有二叉条件(真/假,是/否0/1 ),则决策表的条件部分是旋转了 90 度的(命 题逻辑)真值表。这种结构能够保证我们考虑了所有可能的条件的组合。如果使用决 策表标识测试用例,那么决策表的这种完备性质能够保证一种完备的测试。所有条件都是二叉条件的决策表叫做有限条目决策表。如果条件可以有多个值,则对应的决策 表叫做扩展条目决策表。 决策表被设计为说明性的,给出的条件没有特别的顺序,而且所选择的行动发生时也没有任何特定顺序。
### 决策表表示方法
为了使用决策表标识测试用例,我们把条件解释为输入,把行动解释为输出。有时条件最终引用输入的等价类,行动引用被测软件的主要功能处理部分。这时规则就解释为测试用例。由于决策表可以机械地强制为完备的,因此可以有测试用例的完整集合。
产生决策表的方法可以有多种。
![image-20211201230807856](黑盒测试.assets/image-20211201230807856.png)
在表 3.4 所示的决策表中,给出了不关心条目和不可能规则使用的例子。正如第 一条规则所指示,如果整数 a、b 和 c 不构成三角形,则我们根本不关心可能的相关系。在规则 3、4 和 6 中,如果两对整数相等,则根据传递性,第三对整数也一定相等,因此这些规则不可能满足。
![image-20211201230820493](黑盒测试.assets/image-20211201230820493.png)
表 3.5 所示的决策表给出了有关表示方法的另一种考虑:条件的选择可以大大地 扩展决策表的规模。这里将老条件c1:a、b、c 构成三角形?)扩展为三角形特性的三个不等式的详细表示。如果有一个不等式不成立,则三个整数就不能构成三角形。 我们还可以进一步扩展,因为不等式不成立有两种方式:一条边等于另外两条边的和, 或严格大于另外两条边的和。
![image-20211201230833672](黑盒测试.assets/image-20211201230833672.png)
如果条件引用了等价类,则决策表会有一种典型的外观。如表 3.6 所示的决策表来自 NextDate 问题,引用了可能的月份变量相互排斥的可能性。由于一个月份就是一 个等价类,因此不可能有两个条目同时为真的规则。不关心条目(-)的实际含义是“必须失败”。有些决策表使用者用 F 表示这一点。
![image-20211201230852544](黑盒测试.assets/image-20211201230852544.png)
不关心条目的使用,对完整决策表的识别方式有微妙的影响。对于有限的条目决策表,如果有 n 个条件,则必须有 2 n 条规则。如果不关心条目实际地表明条件是不相关的,则可以按以下方法统计规则数:没有不关心条目的规则统计为 1 条规则;规则中每出现一个不关心条目,该规则数乘一次 2。表 3.5 所示决策表的规则条目数统 计如表 3.7 所示。请注意,规则总数是 64正好是应该得到的规则条数
![image-20211201230920870](黑盒测试.assets/image-20211201230920870.png)
如果将这种简化算法应用于表 3.6 所示的决策表,会得到如表 3.8 所示的规则条数统计。
![image-20211201230939629](黑盒测试.assets/image-20211201230939629.png)
应该只有八条规则,所以显然有问题。为了找出问题所在,我们扩展所有三条规则,用可能的 T 或 F 替代“-”,如图 3.9 所示。
![image-20211201230953248](黑盒测试.assets/image-20211201230953248.png)
请注意,所有条目都是 T 的规则有三条:规则 1.1、2.1、和 3.1;条目是 T、T、 F 的规则有两条:规则 1.2 和 2.2。类似地,规则 1.3 和 3.2、2.3 和 3.3 也是一样的。 如果去掉这种重复,最后可得到七条规则,缺少的规则是所有条件都是假的规则。这 种处理的结果如表 3.10 所示,表中还给出了不可能出现的规则。
![image-20211201231007415](黑盒测试.assets/image-20211201231007415.png)
这种识别完备决策表的能力,使我们在解决冗余性和不一致性方面处于很有利的 地位,表 3.11 给出的决策表是冗余的,因为有三个条件则应该是 2³=8 条规则,此处却有九条规则。(规则 9 和规则 1~4 中某一条相同,是冗余规则。)
![image-20211201231023187](黑盒测试.assets/image-20211201231023187.png)
注意规则 9 的行为条目与规则 1~4 的条目相同。只要冗余规则中的行为与决策表相同的部分相同,就不会有什么大问题。如果行为条目不同,例如表 3.12 所示的情况, 则会遇到比较大的问题。
![image-20211201231037464](黑盒测试.assets/image-20211201231037464.png)
如表 3.12 所示的决策表被用来处理事务,其中 c1 是真c2 和 c3 都是假,则规则 4 和规则 9 都适用。我们可以观察到两点:
- 规则 4 和规则 9 是不一致的。因为它们的行为集合是不同的。
- 决策表是非确定的。因为此时不能确定是应该应用规则 4 还是应用规则 9。 因此测试人员在应用决策表技术时要小心使用不关心条目。
### 决策表的应用
决策表最为突出的优点是,能够将复杂的问题按照各种可能的情况全部列举出来,简明并避免遗漏。因此,利用决策表能够设计出完整的测试用例集合。运用决策表设计测试用例,可以将条件理解为输入,将动作理解为输出。
- **三角形问题的测试用例**
![image-20211201231150972](黑盒测试.assets/image-20211201231150972.png)
使用表 3.5 所示的决策表,可得到 11 个功能性测试用例3 个不可能测试用例, 3 个测试用例违反三角形性质1 个测试用例可得到等边三角形1 个测试用例可得到 不等边三角形3 个测试用例可得到等腰三角形(如表 3.13 所示)如果扩展决策表以 显示两种违反三角形性质的方式,可以再选三个测试用例(一条边正好等于另外两条边的和)。做到这一点需要做一定的判断,否则规则会呈指数级增长。在这种情况下,最终会再得到很多不关心条目和不可能的规则。
![image-20211201231212352](黑盒测试.assets/image-20211201231212352.png)
使用表 3.5 所示的决策表,可得到 11 个功能性测试用例3 个不可能测试用例, 3 个测试用例违反三角形性质1 个测试用例可得到等边三角形1 个测试用例可得到 不等边三角形3 个测试用例可得到等腰三角形(如表 3.13 所示)如果扩展决策表以 显示两种违反三角形性质的方式,可以再选三个测试用例(一条边正好等于另外两条边的和)。做到这一点需要做一定的判断,否则规则会呈指数级增长。在这种情况下,最终会再得到很多不关心条目和不可能的规则。
- **NextDate 函数测试用例**
NextDate 函数可以说明定义域中的依赖性问题,决策表可以突出这种依赖关系, 因此使得它成为基于决策表测试的一个完美例子。前面介绍过 NextDate 函数的等价类 划分。等价类划分的不足之处是机械地选取输入值,可能会产生“奇怪”的测试用例, 如找 2003 年 4 月 31 日的下一天。问题产生的根源是等价类划分和边界值分析测试都 假设了变量是独立的。若变量之间在输入定义域中存在某种逻辑依赖关系,则这些依赖关系在机械地选取输入值时就可能会丢失。决策表方法通过使用“不可能动作”的概念表示条件的不可能组合,使我们能够强调这种依赖关系。
为了产生给定日期的下一个日期NextDate 函数能够使用的操作只有 5 种day 变量和 month 变量的加 1 和复位操作year 变量的加 1 操作。
在以下等价类集合上建立决策表:
- M1: { month: month 有 30 天 }
- M2: { month: month 有 31 天12 月除外 }
- M3: { month: month 是 12 月 }
- M4: { month: month 是 2 月 }
- D1: { day: 1≤day≤27 }
- D2: { day: day = 28 }
- D3: { day: day = 29 }
- D4: { day: day = 30 }
- D5: { day: day = 31 }
- Y1: { year: year 是闰年 }
- Y2: { year: year 不是闰年 }
![image-20211201231332009](黑盒测试.assets/image-20211201231332009.png)
如表 3.14 所示是决策表,共有 22 条规则。
规则 1~5 处理有 30 天的月份,其中不可能规则也列出,如规则 5 处理在有 30 天 的月份中考虑 31 日;规则 6~10 和规则 11~15 处理有 31 天的月份,其中规则 6~10 处 理 12 月之外的月份,规则 11~15 处理 12 月;最后的 7 条规则关注 2 月和闰年问题。
![image-20211201231402291](黑盒测试.assets/image-20211201231402291.png)
规则 1~5 处理有 30 天的月份,其中不可能规则也列出,如规则 5 处理在有 30 天 的月份中考虑 31 日;规则 6~10 和规则 11~15 处理有 31 天的月份,其中规则 6~10 处 理 12 月之外的月份,规则 11~15 处理 12 月;最后的 7 条规则关注 2 月和闰年问题。
![image-20211201231426127](黑盒测试.assets/image-20211201231426127.png)
可进一步简化这 22 条规则。若决策表中有两条规则的动作项相同,则一定至少 68 有一个条件能够把这两条规则用不关心条件合并。例如,规则 1、2、3 都涉及有 30 天的月份 day 类 D1、D2 和 D3并且它们的动作项都是 day 加 1因此可以将规则 1、 2、3 合并。类似地,有 31 天的月份的 day 类 D1、D2、D3 和 D4 也可合并2 月的 D4 和 D5 也可合并。简化后的决策表如表 3.15 所示。
![image-20211201231447352](黑盒测试.assets/image-20211201231447352.png)
根据简化后的决策表 3.15,可设计测试用例,如表 3.16 所示。
### 决策表测试适用范围
每种测试方法都有适用的范围。基于决策表的测试可能对于某些应用程序,如 NextDate 函数十分有效,但是对于另一些应用程序(如佣金问题)就不是很有效。基 于决策表测试通常适用于要产生大量决策的情况,如三角形问题,或在输入变量之间存在重要的逻辑关系的情况,如 NextDate 函数。
一般来说,决策表测试法适用于具有以下特征的应用程序:
- if-then-else 逻辑突出;
- 输入变量之间存在逻辑关系;
- 涉及输入变量子集的计算;
- 输入与输出之间存在因果关系。
在建立决策表的过程中不容易一步到位,第一次标识的条件和行动往往可能不那么令人满意。与其他技术一样,这时采用迭代会有所帮助。把第一次得到的结果作为铺路石,逐渐改进,直到得到满意的决策表。
## 错误推测法
错误猜测大多基于经验,需要从边界值分析等其它技术获得帮助。这种技术猜测特定软件类型可能发生的错误类型,并且设计测试用例查出这些错误。对有经验的工程师来说,错误猜测有时是最有效的发现 bug 的测试设计方法。为了更好地利用现成的经验,可以列出一个错误类型的检查表,帮助猜测错误可能发生在程序中的位置, 提高错误猜测的有效性。
## 练习题
> 分析黑盒测试方法的特点。
> 健壮等价类测试与标准等价类测试的主要区别是什么?
> 试用等价分类法测试党政管理系统中党员出生年月的输入设计是否符合要求,假设出生年月格式为 yyyymmdd。
> 找零钱最佳组合:假设商店货品价格(R)皆不大于 100 元(且为整数),若顾客付 款在100 元内(P),求找给顾客之最少货币个(张)数?(货币面值 50 元(N50)10 元(N10)5 元(N5)1 元(N1)四种。试根据边界值法设计测试用例。
> 试为三角形问题中的直角三角形开发一个决策表和相应的测试用例。注意,会有等腰直角三角形。
> 现有一个学生标准化考试批阅试卷,产生成绩报告的程序。其规格说明如下:程序的输入文件由一些有 80 个字符的记录组成,所有记录分为 3 组,如图:
>
> ![image-20211201231722813](黑盒测试.assets/image-20211201231722813.png)
>
> - 标题:该组只有一个记录,其内容是成绩报告的名字。
> - 各题的标准答案:每个记录均在第 80 个字符处标以数字 2。该组的记录
> - 第一个记录:第 1~3 个字符为试题数1~999。第 10~59 个字符是 1~50 题 的标准答案(每个合法字符表示一个答案)。
> - 第二个记录:是第 51~100 题的标准答案。 …….
> - 学生的答案:每个记录均在第 80 个字符处标以数字 3。每个学生的答卷在 若干个记录中给出。
> - 学号1~9 个字符
> - 1~50 题的答案10~59。当大于 50 题时,在第二、三、……个记录中给出。
> - 学生人数不超过 200试题数不超过 999。
>
> 程序的输出有 4 个报告: a)按学号排列的成绩单,列出每个学生的成绩、名次。 b)按学生成绩排序的成绩单。 c)平均分数及标准偏差的报告。 d)试题分析报告。按试题号排序,列出各题学生答对的百分比。 采用边界值分析方法,分析和设计测试用例。分别考虑输入条件和输出条件,以 及边界条件。采用错误推测法补充设计一些测试用例。

@ -0,0 +1,309 @@
## 白盒测试基本概念
### 基本概念
白盒测试也称结构测试或逻辑驱动测试,是针对被测单元内部是如何进行工作的测试,它的突出特点是基于被测程序的源代码,而不是软件的规格说明。在软件测试中,白盒测试一般是由程序员完成的,当然也有专门做白盒测试的测试工程师。白盒测试人员必须对测试中的软件有深入的认识,包括其结构、各组成部分及之间的关联,以及其内部的运行原理、逻辑,等等。白盒测试人员实际上是程序员和测试员的结合体。
白盒测试的主要方法有程序结构分析、逻辑覆盖、基本路径测试等,它根据程序的控制结构设计导出测试用例,主要用于软件程序的验证。白盒测试法全面了解程序内部的逻辑结构,对所有的逻辑路径进行测试,是一种穷举路径的测试方法。在使用这种方法时,测试者必须检查程序的内部结构,从检查程序的逻辑着手,得出测试数据。
为了清晰描述白盒测试方法。需要首先对有关白盒测试的几个基本概念进行说明,即流图、环形复杂度和图矩阵。
### 流图
在程序设计时,为了更加突出控制流的结构,可对程序流程图进行简化,简化后的图称为控制流图。
经简化后产生的控制流图中所涉及的图形符号只有两种,即节点和控制流线。
- 节点用带有标号的圆圈表示,可以代表一个或多个语句、一个处理框程序和一个条件判断框(假设不包含复合条件)。
- 控制流线由带箭头的弧线或线表示,可称为边,它代表程序中的控制流。
常见语句的控制流图如图4.1所示。
![image-20211201232227736](白盒测试.assets/image-20211201232227736.png)
包含条件的节点被称为判定节点(也叫谓词节点),由判定节点发出的边必须终止于某一个相同节点,由边和节点所限定的范围被称为区域。
如果将一个典型的程序流程图转换为控制流图转换结果如图4.2 所示。
![image-20211201232254292](白盒测试.assets/image-20211201232254292.png)
对于复合条件则可将其分解为多个单个条件并映射成控制流图图4.3 所示。
![image-20211201232307614](白盒测试.assets/image-20211201232307614-16383721926481.png)
### 环形复杂度
环形复杂度也称为圈复杂度,概括地讲,它就是一种为程序逻辑复杂度提供定量尺度的软件度量。可以将该度量用于基本路径方法,它可以提供程序基本集的独立路径数量和确保所有语句至少执行一次的测试数量上界。其中,独立路径是指程序中至少引入一个新的处理语句集合或一个新条件的程序通路,它必须至少包含一条在本次定义路径之前不曾用过的边。路径可用流图中表示程序通路的节点序列表示,也可用弧线表示。
显而易见程序中含有的路径数和程序的复杂性有着密切的关系也就是说程序越复杂它的路径数就越多。但程序复杂性如何度量呢McCabe给出了程序结构复杂性的计算公式。
程序控制流图是一个有向图如果图中任何两个结点之间都至少存在一条路径这样的图称为强连通图。McCabe提出如果程序控制流图是一个强连通图其复杂度VG可按以下公式计算
`VGe-n+l`
其中e为图G中的边数n为图G中的结点数并且McCabe认为强连通图的复杂度VG就是图中线性独立环路的数量。
通过从汇结点到源结点添加一条边便可创建控制流图的强连接有向图。图4.4是一个经过了这种处理后的强连接有向图。其复杂度是:
`VGe-n+l11-7+15`
独立路径是指从程序入口到出口的多次执行中,每次至少有一个语句(包括运算、赋值、输入、输出或判断)是新的,未被重复的。如果用前面提到的控制流图来描述,独立路径就是在从入口进入控制流图后,至少要经历一条从未走过的弧。
因此路径p6A、B、C、B、E、F、Gp7A、B、C、B、C、B、C、G不是独立路径。因为p6可以由路径pl、p2和p3组合而成p7可由路径pl和p2组合而成。
![image-20211201232450418](白盒测试.assets/image-20211201232450418.png)
![image-20211201232455647](白盒测试.assets/image-20211201232455647.png)
### 图矩阵
图矩阵即流图的邻接矩阵表示形式其阶数等于流图的节点数。矩阵中的每列和每行都对应于标识的某一节点矩阵元素对应于节点之间的边。如图4.5 和图4.6所示,描述了一个简单的流图及其对应的矩阵。
![image-20211201232521619](白盒测试.assets/image-20211201232521619.png)
## 逻辑覆盖
### 逻辑覆盖标准
有选择地执行程序中某些最有代表性的通路是对穷尽测试的唯一可行的替代办法。所谓逻辑覆盖是对一系列测试过程的总称,这组测试过程逐渐进行越来越完整的通路测试。测试数据执行(或叫覆盖)程序逻辑的程度可以划分成哪些不同的等级呢?从覆盖源程序语句的详尽程度分析,大致有以下一些不同的覆盖标准。
### 语句覆盖
为了暴露程序中的错误至少每个语句应该执行一次。语句覆盖的含义是选择足够多的测试数据使被测程序中每个语句至少执行一次。例如图4.7所示的程序流程图描绘了一个被测试模块的处理算法。
![image-20211201232718644](白盒测试.assets/image-20211201232718644.png)
为了使每个语句都执行一次程序的执行路径应该是sacbed 为此只需要输入下面的测试数据实际上X可以是任意实数
A = 2B = 0X = 4
语句覆盖对程序的逻辑覆盖很少在上面例子中两个判定条件都只测试了条件为真的情况如果条件为假时处理有错误显然不能发现。此外语句覆盖只关心整个判定表达式的值而没有分别测试判定表达式中每个条件取值不同时的情况。在上面的例子中为了执行sacbed 路径以测试每个语句只需两个判定表达式A > 1AND ( b = 0) 和( A = 2 ) OR ( X > 1 )都取真值因此使用上述一组测试数据就够了。但是如果程序中不把第一个判定表达式中的逻辑运算符“AND”错写成“OR”或者把第二个判定表达式中的条件“X>1”误写成“X<1使
综上所述,可以看出语句覆盖是很弱的逻辑覆盖标准,为了更充分地测试程序,可以采用以下所述的逻辑覆盖标准。
### 判定覆盖
判定覆盖又叫分支覆盖,它的含义是,不仅每个语句必须至少执行一次,而且每个判定表达式的每种可能的结果都应该至少执行一次,也就是每个判定的每个分支都至少执行一次。
对于上述例子来说能够分别覆盖路径sacbed 和sabd 的两组测试数据或者可以分别覆盖路径sacbd 和sabed 的两组测试数据,都满足判定覆盖标准。例如,用下面两组测试数据就可做到判定覆盖:
.A = 3, B = 0, X = 3 覆盖sacbd
Ⅱ.A = 2, B = 1, X = 1 覆盖sabed
判定条件覆盖比语句覆盖强,但是对程序逻辑的覆盖程度仍然不高,例如,上面的测试数据只覆盖了程序全部路径的一半。
### 条件覆盖
条件覆盖的含义是,不仅每个语句至少执行一次,而且使判定表达式中的每个条件都取到各种可能的结果。
图4.7的例子总共有两个判定表达式每个表达式中有两个条件为了做到条件覆盖应该选取测试数据使得实在a 点有下述各种结果出现:
`A > 1, A ≤ 1, B = 0, B ≠ 0`
在b 点有下述各种结果出现:
`A = 2, A ≠ 2, X > 1, X ≤ 1`
只需要使用下面两组测试数据就可以达到上述覆盖标准:
.`A = 2, B = 0, X = 4`
满足A > 1, B = 0, A= 2 和X > 1 的条件执行路径sacbed
Ⅱ.`A = 1, B = 1, X = 1`
满足A ≤ 1, B ≠ 0, A ≠ 2 和X ≤ 1 的条件执行路径sabd
条件覆盖通常比判定覆盖强,因为它使判定表达式中每个条件都取到了两个不同的结果,判定覆盖却只关心整个判定表达式的值。例如,上面两组测试数据也同时满足判定覆盖标准。但是,也可能有相反的情况:虽然每个条件都取到了两个不同的结果,判定表达式却始终只取一个值。例如,如果使用下面两组测试数据,则只满足条件覆盖标准并不满足判定覆盖标准(第二个判定表达式的值总为真):
.`A = 2, B = 0, X = 1`
满足A > 1, B = 0, A = 2 和X ≤ 1 的条件执行路径sacbed
Ⅱ.`A = 1, B = 1, X = 2`
满足A ≤ 1, B ≠ 0, A ≠ 2 和X > 1 的条件执行路径sabed
### 判定/条件覆盖
既然判定覆盖不一定包含条件覆盖,条件覆盖也不一定包含判定覆盖,自然会提出一种能同时满足这两种覆盖标准的逻辑覆盖,这就是判定/条件覆盖。它的含义是,选取足够多的测试数据,使得判定表达式中的每个条件都取到各种可能的值,而且每个判定表达式也都取到各种可能的结果。
对于图4.7的例子而言,下述两组测试数据满足判定/条件覆盖标准:
.`A = 2B = 0X = 4`
Ⅱ.`A = 1B = 1X = 1`
但是,这两组测试数据也就是为了满足条件覆盖标准最初选取的两组数据,因此,有时判定/条件覆盖也并不比条件覆盖更强。
### 条件组合覆盖
条件组合覆盖是更强的逻辑覆盖标准它要求选取足够的测试数据使得每个判定表达式中条件的各种可能组合都至少出现一次。对于图4.7的例子共有8种可能的条件组合它们是
1A > 1, B = 0
2A > 1, B ≠ 0
3A ≤1, B = 0
4A ≤1, B ≠ 0
5A = 2X > 1
6A = 2X ≤ 1
7A ≠ 2X > 1
8A ≠ 2X ≤ 1
和其它逻辑覆盖标准中的测试数据一样条件组合5~8中的X值是指在程序流程图第二个判定框b 点的X值。
下面的4个测试数据可以使上面列出的8种条件组合每种至少出现一次
.`A = 2B = 0X = 4`
针对15两种组合执行路径sacbed
Ⅱ.`A = 2B = 1X = 1`
针对26 两种组合执行路径sabed
Ⅲ.`A = 1B = 0X = 2`
针对37两种组合执行路径sabed
Ⅳ.`A = 1B = 1X = 1`
针对48两种组合执行路径sabd
显然,满足条件组合覆盖标准的测试数据,也一定满足判定覆盖、条件覆盖和判定/条件覆盖标准。因此条件组合覆盖是前述几种覆盖标准中最强的。但是满足条件组合覆盖标准的测试数据并不一定能使程序中的每一条路径都执行到例如上述4组测试数据都没有测试到路径sacbd 。
以上根据测试数据对源程序语句检测的详尽程度,简单讨论了几种逻辑覆盖标准。在上面的分析过程中常常谈到测试数据执行的程序路径,显然,测试数据可以检测的程序路径的多少,也反映了对程序测试的详尽程度。从对程序路径的覆盖程度分析,我们又能够提出下述一些主要的逻辑覆盖标准。
### 点覆盖
图论中点覆盖的概念定义如下如果连通图G的子图G是连通的而且包含G的所有结点则称G是G的点覆盖。
在正常情况下流图是连通图的有向图。满足点覆盖标准要求选取足够多的测试数据,使得程序执行路径至少经过流图的每个结点一次,由于流图的每个结点与一条或多条语句相对应,显然,点覆盖标准和语句覆盖标准是相同的。
### 边覆盖
图论中边覆盖的定义是如果连通图G的子图G″是连通的而且包含G的所有边则称G″是G的边覆盖。为了满足边覆盖的测试标准要求选取足够多的测试数据使得程序执行路径至少经过流图中每条边一次。通常边覆盖和判定覆盖是一致的。
### 路径覆盖
路径覆盖的定义是,选取足够多测试数据,使程序的每一条可能路径都至少执行一次。
对于图4.7所示例子,请读者考虑满足路径覆盖的测试数据。
这里所用的程序段非常简短只有4条路径。但在实际问题中一个不太复杂的程序其路径数都可能是一个庞大的数字以致要在测试中覆盖所有的路径是不可能实现的。为解决这一难题只得把覆盖的路径数压缩到一定限度内例如对程序中的循环体只执行一次。
即使对于路径数有限的程序做到了路径覆盖,也不能保证被测程序的正确性。因为通过分析测试数据,我们可以发现路径覆盖不能保证满足条件组合覆盖。而且在前面我们也已经介绍过穷举路径测试法无法检查出程序本身是否违反了设计规范,即程序是否是一个错误的程序;不可能查出程序因为遗漏路径而出现的错误;同时也发现不了一些与数据相关的错误。
由此看出,各种结构测试方法都不能保证程序的正确性。但是,测试的目的并不是要证明程序的正确性,而是要尽可能找出程序中隐藏的故障。事实上,并不存在一种十全十美的测试方法能够发现所有的软件故障。
## 最少测试用例数计算
为实现测试的逻辑覆盖,必须设计足够多的测试用例,并使用这些测试用例执行被测程序,实施测试。对某个具体程序来说,至少要设计多少测试用例。这里提供一种估算最少测试用例数的方法。
我们知道,结构化程序是由三种基本控制结构组成。这三种基本控制结构就是:顺序型——构成串行操作;选择型——构成分支操作;重复型——构成循环操作。
为了把问题化简,避免出现测试用例极多的组合爆炸,把构成循环操作的重复型
结构用选择结构代替。也就是说,并不指望测试循环体所有的重复执行,而是只对循环体检验一次。这样,任一循环便改造成进入循环体或不进入循环体的分支操作了。
![image-20211201233525287](白盒测试.assets/image-20211201233525287.png)
图4.8给出了类似于流程图的N-S图表示的基本控制结构图中A、B、C、D、S均表示要执行的操作P是可取真假值的谓词Y表真值N表假值。其中图4.8c和图4.8d两种重复型结构代表了两种循环。在作了如上简化循环的假设以后对于一般的程序控制流我们只考虑选择型结构。事实上它已能体现顺序型和重复型结构了。
![image-20211201233540283](白盒测试.assets/image-20211201233540283.png)
例如图4.9表达了两个顺序执行的分支结构。两个分支谓词P1和P2取不同值时将分别执行a或b及c或d操作。显然要测试这个小程序需要至少提供4个测试用例才能做到逻辑覆盖。使得ac、ad、bc及bd操作均得到检验。其实这里的4是图中第1个分支谓词引出的两个操作及第2个分支谓词引出的两个操作组合起来而得到的。
![image-20211201233552055](白盒测试.assets/image-20211201233552055.png)
对于一般的、更为复杂的问题估算最少测试用例数的原则也是同样的。现以图4.10表示的程序为例。该程序中共有9个分支谓词尽管这些分支结构交错起来似乎十分复杂很难一眼看出应至少需要多少个测试用例但如果仍用上面的方法也是很容易解决的。我们注意到该图可分上下两层分支谓词1的操作域是上层分支谓词8的操作域是下层。这两层正像前面简单例子中的P1和P2的关系一样。只要分别得到两层的测试用例个数再将其相乘即得总的测试用例数。这里需要首先考虑较为复杂的上层结构。
![image-20211201233608834](白盒测试.assets/image-20211201233608834.png)
谓词1不满足时要作的操作又可进一步分解为两层这就是图4.11中的子图ab。它们所需测试用例个数分别为1+1+1+1+1 = 5及1+1+1 = 3。因而两层组合得到5×3 = 15。于是整个程序结构上层所需测试用例数为1+15 = 16。而下层显然为3。故最后得到整个程序所需测试用例数至少为6×3 = 48。
## 独立路径测试
独立路径测试是在程序控制流图的基础上,通过分析控制结构的环路复杂性,导出可执行的独立路径集合,从而设计出相应的测试用例。设计出的测试用例要保证被测程序的每条可执行的独立路径至少被执行一次。路径测试考虑以下几个方面:
- 程序控制流图
- 程序环路复杂性。借助McCabe复杂性度量可以从程序的环路复杂性导出程序路径集合中的独立路径条数。
- 设计测试用例。确保独立路径集合中的每一条路径被执行。
在路径测试中最关键的问题仍然是如何设计测试用例使之能够避免测试的盲目性又能有较高的测试效率。一般有3个途径可得到测试用例
1通过非路径分析得到测试用例
测试人员凭经验设计测试用例或由应用系统本身提供测试用例。在使用这些测试用例执行被测程序后,一些路径就被检测过了。
2对未测试的路径生成相应的测试用例
枚举被测程序所有可能的独立路径,并与前面已测试过的路径相比,便可得知哪些路径还没有被测试过,针对这些路径生成测试用例,进而完成对它们的测试。
3生成指定路径的测试用例
根据指定的路径,生成相应的测试用例。
按以上方法实施测试,原则上是可以做到路径覆盖的,因为:
- 对程序中的循环作了如上限制以后,程序路径的数量是有限的。
- 程序的路径可经枚举全部得到。
- 完成若干个测试用例后,对所测路径、未测路径是知道的。
- 在指出要测试的路径以后,可以自动生成相应的测试用例。
## 循环测试
循环是绝大多数软件算法的基础,但是,在测试软件时却往往未对循环结构进行足够的测试。
![image-20211201233822574](白盒测试.assets/image-20211201233822574.png)
循环测试是一种白盒测试技术它专注于测试循环结构的有效性。在结构化的程序中通常只有3种循环即简单循环、串接循环和嵌套循环如图4.12所示。下面讨论这3种循环的测试方法。
### 简单循坏
应该使用下列测试集来测试简单循环其中n是允许通过循环的最大次数。
- 跳过循环
- 只通过循环一次
- 通过循环两次
- 通过循环m次其中m < n-1
- 通过循环n1,n,n+1次
### 嵌套循环
如果把简单循环的测试方法直接应用到嵌套循环可能的测试数就会随嵌套层数的增加按几何级数增长这会导致不切实际的测试数目。B.Beizer提出了一种能减少测试数的方法
从最内层循环开始测试,把所有其它循环都设置为最小值;对最内层循环使用简单循环测试方法,而使外层循环的迭代参数(例如,循环计数器)取最小值,并为越界值或非法值增加一些额外的测试;由内向外,对下一个循环进行测试,但保持所有其它外层循环为最小值,其它嵌套循环为“典型”值;继续进行下去,直到测试完所有循环。
### 串接循环
如果串接循环的各个循环都彼此独立,则可以使用前述的测试简单循环的方法来测试串接循环。但是,如果两个循环串接,而且第一个循环的循环计数器值是第二个循环的初始值,则这两个循环并不是独立的。当循环不独立时,建议使用测试嵌套循环的方法来测试串接循环。
## 面向对象的白盒测试
对面向对象软件的类测试相当于传统软件的单元测试。但与传统软件的单元测试不同的是,它往往关注模块的算法细节和模块借口间流动的恶数据,面向对象软件的类测试是由封装在类中的操作和类的状态行为所驱动的。
类测试一般有两种主要的方式:功能性测试和结构性测试,即对应于传统结构化软件的黑盒测试和白盒测试。
功能性测试以类的规格说明为基础它主要检查类是否符合其规格说明的要求。例如对于Stack类即检查它的操作是否满足LIFO规则结构性测试则从程序出发它需要考虑其中的代码是否正确同样是Stack类就要检查其中代码是否动作正确且至少执行过一次。
结构性测试是对类中的方法进行测试,它把类作为一个单元来进行测试。测试分为两层:第一层考虑类中各独立方法的代码;第二层考虑方法之间的相互作用。
每个方法的测试要求能针对其所有的输入情况,但这样还不够,只有对这些方法之间的接口也做同样测试,才能认为测试是完整的。对于一个类的测试要保证类在其状态的代表集上能够正确工作,构造函数的参数选择以及消息序列的选择都要满足这一准则。因此,在这两个不同的测试层次上应分别做到:
1方法的单独测试
结构性测试的第一层是考虑各独立的方法,这可以与过程的测试采用同样的方法,两者之间最大的差别在于方法改变了它所在实例的状态,这就要取得隐藏的状态信息来估算测试的结果,传给其它对象的消息被忽略,而以桩来代替,并根据所传的消息返回相应的值,测试数据要求能完全覆盖类中代码,可以用传统的测试技术来获取。
2方法的综合测试
第二层要考虑一个方法调用本对象类中的其它方法和从一个类向其它类发送信息的情况。单独测试一个方法时,只考虑其本身执行的情况,而没有考虑动作的顺序问题,测试用例中加入了激发这些调用的信息,以检查它们是否正确运行了。对于同一类中方法之间的调用,一般只需要极少甚至不用附加数据,因为方法都是对类进行存取,故这一类测试的准则是要求遍历类的所有主要状态。
## 其它白盒测试方法简介
### 域测试
域测试Domain Testing是一种基于程序结构的测试方法。Howden把程序中出现的错误分为域错误、计算型错误和丢失路径错误三种。这是相对于执行程序的路径来说的。我们知道每条执行路径对应于输入域的一类情况是程序的一个子计算。如果程序的控制流有错误对于某一特定的输入可能执行的是一条错误路径这种错误称为路径错误也叫做域错误。如果对于特定输入执行的是正确路径但由于赋值语句的错误致使输出结果不正确则称此为计算型错误。另外一类错误是丢失路径错误。它是由于程序中某处少了一个判定谓词而引起的。域测试是主要针对域错误进行的程序测试。
域测试的“域”是指程序的输入空间。域测试方法基于对输入空间的分析。自然,任何一个被测程序都有一个输入空间。测试的理想结果就是检验输入空间中的每一个输入元素是否都产生正确的结果。而输入空间又可分为不同的子空间,每一子空间对应一种不同的计算。在考察被测试程序的结构以后,会发现子空间的划分是由程序中分支语句中的谓词决定的。输入空间的一个元素,经过程序中某些特定语句的执行而结束(当然也可能出现无限循环而无出口),那都是满足了这些特定语句被执行所要求的条件的。
域测试正是在分析输入域的基础上,选择适当的测试点以后进行测试的。域测试有两个致命的弱点:一是为进行域测试对程序提出的限制过多,二是当程序存在很多路径时,所需的测试点也很多。
### 符号测试
符号测试的基本思想是允许程序的输入不仅仅是具体的数值数据而且包括符号值这一方法也是因此而得名。这里所说的符号值可以是基本符号变量值也可以是86
这些符号变量值的一个表达式。这样,在执行程序过程中以符号的计算代替了普通测试执行中对测试用例的数值计算。所得到的结果自然是符号公式或是符号谓词。更明确地说,普通测试执行的是算术运算,符号测试则是执行代数运算。因此符号测试可以认为是普通测试的扩充。
符号测试可以看作是程序测试和程序验证的一个折衷方法。一方面,它沿用了传统的程序测试方法,通过运行被测程序来验证它的可靠性。另一方面,由于一次符号测试的结果代表了一大类普通测试的运行结果,实际上是证明了程序接受此类输入,所得输出是正确的,还是错误的。最为理想的情况是,程序中仅有有限的几条执行路径。如果对这有限的几条路径都完成了符号测试,就能较有把握地确认程序的正确性。
从符号测试方法使用来看,问题的关键在于开发出比传统的编译器功能更强,能够处理符号运算的编译器和解释器。
目前符号测试存在一些未得到圆满解决的问题,分别是:
1分支问题
当采用符号执行方法进行到某一分支点处,分支谓词是符号表达式,这种情况下通常无法决定谓词的取值,也就不能决定分支的走向,需要测试人员做人工干预,或是执行树的方法进行下去。如果程序中有循环,而循环次数又决定于输入变量,那就无法确定循环的次数。
2二义性问题
数据项的符号值可能是有二义性的。这种情况通常出现带有数组的程序中。
我们来看以下的程序段:
`X( I ) = 2 + A `
`X( J ) = 3 `
`C = X( I ) `
如果I = J则C = 3否则C = 2 + A。但由于使用符号值运算这时无法知道I是否等于J。
3大程序问题
符号测试中总是要处理符号表达式。随着符号执行的继续,一些变量的符号表达式会越来越庞大。特别是当符号执行树如果很大,分支点很多,路径条件本身变成一个非常长的合取式。如果能够有办法将其化简,自然会带来很大好处。但如果找不到化简的办法,那将给符号测试的时间和运行空间带来大幅度的增长,甚至使整个问题的解决遇到难于克服的困难。
### Z路径覆盖
分析程序中的路径是指:检验程序从入口开始,执行过程中经历的各个语句,直到出口。这是白盒测试最为典型的问题。着眼于路径分析的测试可称为路径测试。完成路径测试的理想情况是做到路径覆盖。对于比较简单的小程序实现路径覆盖是可能做到的。但是如果程序中出现多个判断和多个循环,可能的路径数目将会急剧增长,达到天文数字,以至实现路径覆盖不可能做到。
为了解决这一问题我们必须舍掉一些次要因素对循环机制进行简化从而极大地减少路径的数量使得覆盖这些有限的路径成为可能。我们称简化循环意义下的路径覆盖为Z路径覆盖。
![image-20211201234242354](白盒测试.assets/image-20211201234242354.png)
这里所说的对循环化简是指限制循环的次数。无论循环的形式和实际执行循环体的次数多少我们只考虑循环一次和零次两种情况。即只考虑执行时进入循环体一次和跳过循环体这两种情况。图4.13中ab表示了两种最典型的循环控制结构。前者先作判断循环体B可能执行假定只执行一次也可能不执行。这就如同c所表示的条件选择结构一样。后者先执行循环体B假定也执行一次再经判断转出其效果也与c中给出的条件选择结构只执行右支的效果一样。
对于程序中的所有路径可以用路径树来表示具体表示方法本文略。当得到某一程序的路径树后从其根结点开始一次遍历再回到根结点时把所经历的叶结点名排列起来就得到一个路径。如果我们设法遍历了所有的叶结点那就得到了所有的路径。当得到所有的路径后生成每个路径的测试用例就可以做到Z路径覆盖测试。
## 练习题
> 简述白盒测试用例的设计方法,并进行分析总结。
> 分析归纳逻辑覆盖的各种策略,并比较每种覆盖的特点,分析在怎样的情况下采用何种覆盖方式。
> 对图所示程序段进行语句覆盖、判定覆盖、条件覆盖、判定/条件覆盖、条件组合覆盖和路径覆盖方法进行测试用例设计。
>
> ![image-20211201234335330](白盒测试.assets/image-20211201234335330.png)
> 对于下面N-S图计算所需的最少测试用例数
>
> ![image-20211201234503930](白盒测试.assets/image-20211201234503930.png)

@ -6,4 +6,9 @@
* [✍ CS和BS体系结构](src/university/5 "CS和BS体系结构")
* [✍ 其它体系结构](src/university/6 "其它体系结构")
* [✍ 面向服务体系结构](src/university/7 "面向服务体系结构")
* [✍ 中间件和框架](src/university/8 "中间件和框架")
* [✍ 中间件和框架](src/university/8 "中间件和框架")
* 🏁 软件测试
* [✍ 软件测试概述](src/university/101 "软件测试概述")
* [✍ 软件测试方法与过程](src/university/102 "软件测试方法与过程")
* [✍ 黑盒测试](src/university/103 "黑盒测试")
* [✍ 白盒测试](src/university/104 "白盒测试")

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 76 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 54 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 54 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 29 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 33 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 97 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 37 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 35 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 83 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 38 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 58 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 58 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 65 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 65 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 62 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 86 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 111 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 86 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 150 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 140 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 158 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 190 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 35 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 80 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 88 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 82 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 116 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 44 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 100 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 106 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 99 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 142 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 68 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 37 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 60 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 57 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 44 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 65 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 54 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 51 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 58 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 33 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 31 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 55 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 63 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 62 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 66 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 80 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 43 KiB

Loading…
Cancel
Save