6-5软件测试与测试优先编程
目的:
1.了解测试的价值,了解测试优先编程的过程
2.通过划分方法的输入和输出空间,找到其边界,并选择好的测试用例,能够为方法设计一个测试套件
3.能够通过测量代码覆盖率来判断测试套件
4.了解并知道何时使用blackbox与whitebox测试、单元测试与集成测试以及自动回归测试
什么是软件测试?
软件测试是为利益相关者提供有关被测产品或服务质量的信息而进行的调查。
–这是执行程序或应用程序的过程,目的是查找错误(错误或其他缺陷),并验证软件产品是否适合使用。
–它涉及软件组件的执行,以评估一个或多个感兴趣的属性。
即使是最好的测试也无法达到100%的无错误
测试的目的与其他目标目的相反,是为了验证程序中是否存在错误。但再好的测试也无法证明系统里完全不存在错误。
好的测试的特征:
一个好的测试很可能会发现一个错误
一个好的测试并不冗余
一个好的测试应该是“最佳方案”
一个好的测试既不应该太简单也不应该太复杂
测试分级:
1.单元测试:指通常在功能级别验证特定代码段功能的测试。
2.集成测试:由多个程序员或编程团队创建的两个或多个类、包、组件、子系统的组合执行。
3.系统测试:对一个完全集成的系统进行测试,以验证该系统满足其要求,并在其最终配置中执行软件。
最后用户会对软件进行验收测试,来验证软件是否符合客户要求。
回归测试:当程序发生错误时进行修改后,需要对其他没错误的部分也进行重新测试
根据测试时是否运行程序可以把测试分为静态测试和动态测试。
静态测试是在没有实际执行程序的情况下执行的。–静态测试通常是隐式的,作为校对,另外当编程工具/文本编辑器检查源代码结构或编译器(预编译器)检查语法和数据流作为静态程序分析时。
–审查、演练或检查称为静态测试。
▪ dynamic testing描述了对代码动态行为的测试,它实际使用一组给定的测试用例执行编程代码。
–动态测试可以在程序100%完成之前开始,以便测试特定的代码段,并应用于离散函数或模块。–这方面的典型技术是使用存根/驱动程序或从调试器环境执行。
Testing和Debugging
测试:侧重于发现是否存在错误
调试:识别错误根源,消除错误
白盒测试:已知代码内部结构进行测试
黑盒测试:根据代码模块的行为进行测试
为什么测试如此困难?
穷举测试是不可行的:可能的测试用例的空间通常太大,无法穷举覆盖。–想象一下对32位浮点乘法运算a*b进行详尽的测试,一共有2^64个测试用例!
▪ 随意的测试(“试试看它是否有效”)不太可能发现错误,除非程序的错误太大,以至于任意选择的输入更可能失败而不是成功。–这也不会增加我们对程序正确性的信心。
▪ 随机或统计测试对软件不起作用。其他工程学科可以测试小的随机样本(例如,1%的硬盘驱动器生产),并推断整个生产批次的缺陷率。–这只适用于物理工件,但不适用于软件
软件行为在离散输入空间中差异是巨大的,通常在大多数情况下是正确的,但会有小部分情况下程序会出错。另外bug的出现往往不符合特定概率分布,与物理系统不同,无统计分布规律可循。
一个著名的bug就是奔腾处理器的bug,另外一个就是Ariane 5号
重要的心态是:让程序“尽快出错”和故意让其出错。这样才能让软件的质量更高。
什么是测试用例?
测试用例是一组测试输入、执行条件和预期结果。
例如,测试用例{测试输入+执行条件+预期结果}–为特定目标开发测试用例,例如执行特定程序路径或验证是否符合特定需求。
–测试用例可能只是您向程序提出的问题。运行测试的目的是获取信息,例如程序是否通过测试。
–测试用例是质量保证的基石,而开发它们是为了验证产品的质量和行为。
测试用例的重要程度与代码的重要程度是同样的。
好的测试用例的特征与好的测试的特征一致。
测试优先编程:
不用将测试放在代码完成之后。
动机-尽早并经常测试。使它更早的失败。
–不要等到测试结束,当你有一大堆未验证的代码时。将测试留到最后只会使调试变得更长、更痛苦,因为错误可能会出现在代码中的任何地方。
–在开发代码的过程中测试代码要愉快得多。
过程:–为函数编写规范。规范–编写执行规范的测试。
–编写实际代码。一旦你的代码通过了你写的测试,你就完成了。
写测试能理解,修正和改善规格说明。
规格说明:
描述函数的输入和输出行为。–它给出了参数的类型及其任何附加约束(例如sqrt()的参数必须是非负的)。
–它还提供返回值的类型以及返回值与输入之间的关系。
–在代码中,规范由方法签名和上面描述其功能的注释组成。
首先编写测试是理解规范的好方法。–规范可能有错误,太-不正确,不完整,模棱两可,缺角情况。–在浪费时间编写错误规范的实现之前,尝试编写测试可以及早发现这些问题。
单元测试:
针对软件的 最小单元模型开展测试,隔离各个模块,容易定位错误和调试
单独测试模块会使调试更加容易。–当一个模块的单元测试失败时,您可以更加确信这个bug是在该模块中发现的,而不是在程序中的任何地方
**Junit测试:**加上@Test标注来说明这是一个标注
如果assertEquals左边的期望结果和右边的运行结果相同,则测试通过。
一个测试类中可以有多个测试方法。
如何自动生成测试方法:
测试文件夹Test需要设定为源文件夹,这样逻辑上会和src文件夹关联
黑盒测试:
黑盒测试是一种软件测试方法,它检查应用程序的功能,而不窥视其内部结构或工作。
黑盒测试尝试查找以下类型的错误:
–不正确或缺少的函数
–接口错误
–数据结构或外部数据库访问中的错误
–行为或性能错误–初始化和终止错误
黑盒测试的测试用例是围绕规范和需求构建的,即应用程序应该做什么。
测试用例通常来自软件的外部描述,包括规范、需求和设计参数。–选择一组小到足以快速运行,大到足以验证程序的测试用例。让三者交叉的部分越多越好。
等价类的划分:
等价划分是一种将程序的输入域划分为数据类的测试方法,从中可以导出测试用例。
如果一组对象可以通过对称的、传递的和自反的关系进行链接,则会出现一个等价类。等价类表示输入条件的一组有效或无效状态。
通常,输入条件是特定的数值、值的范围、相关值的集合或布尔条件。
等价类背后的思想是将输入空间划分为程序具有相似行为的相似输入集,然后使用每个集的一个代表。这种方法通过选择不同的测试用例,并强制测试探索随机测试可能达不到的部分输入空间,从而最大限度地利用有限的测试资源。
等价类的划分原则:
等价类可以根据以下准则定义:
–如果输入条件指定范围,则定义一个有效等价类和两个无效等价类。
–如果输入条件需要特定值,则定义一个有效等价类和一个无效等价类。
–如果输入条件指定集合的成员,则定义一个有效和一个无效的等价类。
–如果输入条件为布尔值,则定义一个有效类和一个无效类。
例如我们对BigInteger类的惩罚进行划分:可以从正负的角度进行划分,同时需要考虑输入数据的特殊情况,例如0,-1,1,极大值等等
最终所有的测试用例需要覆盖所有等价类。
在分区中包含边界
边界值分析
▪ 更多的错误发生在输入域的边界而不是“中心”
–0是正数和负数之间的边界–数值类型的最大值和最小值,如int和double
–集合类型的空性(空字符串、空列表、空数组)
-集合的第一个和最后一个元素
▪ 边界值分析(BVA)作为一种测试技术已经发展起来,导致了对边界值的测试用例的选择。
–一种补充等价划分的测试用例设计技术。
–BVA不是选择等价类的任何元素,而是在类的“边缘”选择测试用例。
覆盖分区的两个极端
▪ 全笛卡尔积
–分区维度的每个合法组合都包含在一个测试用例中。
–对于包含边界的最大示例(分别具有3个部分、5个部分和5个部分的三维),这意味着高达3×5×5=75个测试用例。–实际上,并非所有这些组合都是可能的。例如,无法覆盖组合a<b,a=0,b=0,因为a不能同时小于零和等于零。
▪ 覆盖每个部分——每个维度的每个部分都被至少一个测试用例覆盖,但不一定是每个组合。
–如果仔细选择,max的测试套件可能只有5个测试用例。
- 笛卡尔积:全覆盖
- 覆盖每个取值:最少1次即可
前者:测试完备,但用例数量多,测试代价高
后者:测试用例少,代价低,但测试覆盖度未必高。
通常,我们会在这两个极端之间达成某种妥协,基于人类的判断和谨慎,并受到白盒测试和代码覆盖工具的影响。
黑盒测试VS白盒测试
Blackbox测试意味着只从规范中选择测试用例,而不是从函数的实现中选择。–我们分区并在multiply和max中查找边界,而不查看这些函数的实际代码。
▪ Whitebox测试(也称为glass-box测试)是指在选择测试用例时了解该功能的实际实现方式。–例如,如果实现根据输入选择不同的算法,则应根据这些域进行分区。–如果实现保留了一个内部缓存,该缓存记住对以前输入的答案,那么应该测试重复的输入。
系统的内部透视图以及编程技巧用于设计测试用例。
▪ 测试人员选择输入来练习代码的路径,并确定适当的输出。
▪ 白盒测试可以应用于软件测试过程的单元级、集成级和系统级。一般来说,它是在测试过程的早期执行的。
白盒测试
▪ 使用白盒测试方法,您可以导出这样的测试用例:确保模块中的所有独立路径都至少执行过一次;在它们的真与假边上执行所有逻辑决策;在它们的边界和操作范围内执行所有循环,–运用内部数据结构以确保其有效性。
▪ 典型的白盒测试方法称为“独立/基本路径测试”
测试覆盖范围
测试应该考虑程序内部逻辑测试用例的代码覆盖率。
▪ 代码覆盖率是一种度量,用于描述在运行特定测试套件时程序源代码的执行程度。
–高代码覆盖率的程序(以百分比衡量)在测试期间执行了更多的源代码,这表明与低代码覆盖率的程序相比,它包含未检测到的软件错误的几率更低。
–可以使用许多不同的度量来计算代码覆盖率;一些最基本的度量是程序子例程的百分比和在测试套件执行期间调用的程序语句的百分比。
代码覆盖率
▪ 判断一个测试套件的一种方法是询问它对程序的执行有多彻底。这个概念叫做覆盖。
▪ 有许多常见的覆盖类型:
–函数覆盖:程序中的每个函数都被调用了吗?
–语句覆盖率:每个语句都是由某个测试用例运行的吗?
–分支覆盖:对于程序中的每个if、while、switch case或for语句,某个测试用例是否同时接受了true和false方向?
–条件覆盖率:对于if/while/for/switch case语句中的每个条件,是否都是某个测试用例所采用的真/假方向?
–路径覆盖:分支的每一个可能的组合-通过程序的每一条路径-是否由某个测试用例执行?
分支覆盖比statementcoverage强(需要更多的测试才能实现),而路径覆盖比分支覆盖强。在业界,100%的语句覆盖率是一个共同的目标,但由于无法访问的防御代码(比如“不应该出现在这里”断言),即使这样也很少实现。
▪ 100%的分支机构是非常可取的,而安全关键行业规范有更严格的标准。
▪ 不幸的是,100%的路径覆盖是不可行的,需要指数大小的测试套件才能实现。
测试效果:路径覆盖>分支覆盖>语句覆盖
最彻底的白盒方法是覆盖程序中的每一条路径,但由于程序通常包含一个循环,因此路径的数量很大。
–几乎不可能执行所有路径,我们只能尽量确保覆盖率尽可能高。
▪ 例如:–一个程序包含一个需要执行20次的循环。它包括520个不同的执行路径。假设测试每条路径需要1毫秒,那么完成所有路径的测试需要3170年。
自动化测试
▪ 没有什么比完全自动化更容易运行测试,也更可能运行测试。
▪ 自动测试意味着运行测试并自动检查其结果。
–测试驱动程序不应该是一个交互式程序,它提示您输入并打印出结果供您手动检查。
–相反,测试驱动程序应该在固定的测试用例上调用模块本身,并自动检查结果是否正确。–测试驱动程序的结果应为“所有测试正常”或“这些测试失败:…”
▪ 一个好的测试框架,比如JUnit,可以帮助您构建自动化的测试套件。
回归测试
▪ 一旦实现了测试自动化,在修改代码时重新运行测试非常重要。对大型或复杂程序的任何更改都是危险的。
–无论是修复另一个bug、添加新功能,还是优化代码以使其更快,一个保留正确行为基线的自动化测试套件(即使只是几个测试)都可以节省开支。
–在更改代码时频繁运行测试可防止程序倒退-在修复新错误或添加新功能时引入其他错误。
▪ 每次更改后运行所有测试称为回归测试。
记录您的测试策略
单元测试策略是ADT设计的补充文件。
–根据测试优先编程的思想,建议编写测试策略(如分区和边界),根据这些策略设计测试用例。
▪ 目标是进行代码审查,检查您的测试是否足够,并使其他开发人员了解您的测试。
本次总结
▪ 先测试编程。在编写代码之前先编写测试。
▪ 系统地选择测试用例的划分和边界。
▪ 用于填写测试套件的白盒测试和语句覆盖率。
▪ 单元测试每个模块,尽可能隔离。
▪ 自动回归测试以防止错误再次出现。
▪ 不会有bug。测试是指在代码中发现错误,而测试优先编程是指在您引入错误之后,尽快找到它们。