Chatper 2 手工测试
测试是为了找bug的一系列过程.那么手工测试毫无疑问是主要活动
任何一个软件公司发布的产品都有缺陷
这个缺陷是一种特殊缺陷,比其他任何缺陷都重要:即逃过所有各种检测手段而最终存在于发布产品中的缺陷. 这些缺陷如何损害公司利益?用户在使用软件过程中找到这些缺陷。
观点: 因为用户是在使用软件过程中找到这些缺陷,所以我们的测试人员也应该通过使用软件来找到他们。
为什么被引入?
无论使用自动化测试和单元测试,还是其他一些手段,都难以接触到这些特殊缺陷。
无论测试人员怎么实现自动化测试,即使全部都自动化,这些缺陷还是会处处作怪,并在产品中屡屡重现从而伤害最终用户
手工测试的问题
缺乏目的性, 随机性强:free testing , random testing, ad-hoc testing
重复性强,无聊透顶
新的手工测试方法:探索式测试
大家第一感觉会说探索式测试会不会和ad-hot testing / free testing / random testing 一样啊。探索性测试有时与随机测试混淆。随机测试通常指即兴的缺陷发现过程。从定义上理解,任何人都可以做随机测试。
而探索性测试是基于feedback的,边设计边测试,有侧重点的,高度依赖测试人员的背景,水平和经验的一种测试技术。
软件缺陷的预防
从开发出发
- 设计规范
- 代码静态分析工具
- code review
- unit test
上面的技术有问题,不解决问题,技术不会太有效
- 问题1. 开发找自己的代码有效性差,不善于找缺陷,从编程角度出发
- 问题2. 代码审核,静态分析都是静止状态的技术。但很多缺陷,那些隐藏很深的缺陷,如果不在真实的运行环境中运行起来,不用真实的数据,是不会被触发的
- 问题3. 缺乏数据。往往会出现这样的情况,软件运行一段时间后,当数据量积累到一定程度,软件出现故障了。
测试是一个动态的过程,它包括在不同的环境中运行软件,使用合理的测试数据,并在较短的测试周期内尽可能多的尝试不同的输入值。这就是软件测试人员可以施展身手的地方。
软件缺陷的检测手段:自动化和手工
自动化的好与坏
- 好: 编写一个简单的程序,就可以执行无数次测试用例,还可以发现很多软件缺陷。可以被多次重复使用。
- 坏: 自动化代码可靠?维护成本?“预言家难题”?自动化已经行之多年,为什么到用户手里还是有问题?
预言家难题:测试中最艰巨的任务之一,就是在运行一个测试用例时,如何才能知道被测试软件确实完成了它应该完成的任务?被测试软件是否输出了正确的结果?在运行过程中,是否带来副作用?如何才能确信这一点?如果给定一个用户环境,特定的数据配置和输入顺序,有没有预言家能断言:软件确实做了,也只做了它所应该做的事情。现实情况下,往往由于软件的设计规格说明书并不完整,或者根本没有,这导致软件的测试人员也没有办法做这个断言。所以说:自动化确实很重要,但光靠它还不够,过度依赖自动化测试会为程序的最终成功带来隐患。 如果测试人员不能依靠开发人员的缺陷预防工具和自动化手段,他们还能寄希望于什么呢?唯一答案是手工测试
手工测试
主观能动性,设计真实的场景,找到显而易见和难以察觉的缺陷。特别是对于业务逻辑相关的,比起自动化,手工测试是最理想选择。但手工测试远远不能彻底解决问题。慢,没有规律,不可反复使用,发现问题后也不能重现,不能移植,没有可借鉴的经验教训,无聊透顶。
怎么办?形势严峻? - 使用手工测试领域最好的技术:探索式测试
探索式测试
手工测试使用脚本:执行 test case。死板。一定的变通?不指定test data,给一个笼统的用户场景,有一定的发挥余地。测试脚本可以规定得很细,也可以含有一些粗线条的描述。当测试脚本比较笼统的时候,测试人员就需要学习随机应变的本领,掌握面对各种选择时如何可以进行合理的判断,这就是探索性测试所要阐述的问题之一。
探索式测试:完全抛开测试脚本。看上去没规律,但对于一个有经验并熟练掌握探索式测试的人来说,就有效。
使用探索式测试并不是说不写文档。测试结果,测试实例,测试文档都会在运行测试时创建。这与先编写好完全不同
探索式测试最适用于敏捷开发过程的web应用程序。这些程序开发周期很短,基本没有时间可以编写正式的测试脚本。产品功能变化很快,原来的测试用例可能很快失效,那为什么写?更多的时间用于实际测试而不是维护测试用例。
探索式测试的缺点:没有重点,漫无目的,对某些功能重复测试,在多人测试团队该情况就更严重了。木有文档,怎么保证测试覆盖率?
这里要强调指导方法的重要性了
- 局部探索式测试法,辅助测试人员在测试过程中及时作出决定。
- 全局探索式测试法,帮助测试人员设计整体测试计划和测试策略
- 把探索式测试和使用脚本的手工测试合并起来,混合探索测试技术。使用正式脚本可以为探索式测试设立一个明确的框架范围,探索式测试则可以提高脚本测试的有效性。
最好的是从正式脚本开始,然后再使用探索式测试法在脚本中加入各种各样的变化。这样做的话,单一的测试脚本会演化出很多探索式测试用例。
Chapter 3 局部探索式测试方法
任何一个错综复杂的缺陷都与功能无异.软件测试的复杂性。测试软件这种高度复杂的产品,如果不知道正确结果是什么,质量标准定义又很含糊不清,这活儿几乎没法干。
那么软件测试人员面对这类 mission impossible时,我们要做什么?
- 欣赏软件测试的高度复杂性和艰巨性。测试无涯,我们永远也做不完,所以必须要把所有要做的事情按照优先级排序,然后从最重要的事情做起。我们的目标是软件发布的时候可以达到如下的目标:所有重要的任务都完成了,而剩下没做的事情都是比较次要的。如果我们做到这一点,就可以较早尽可能降低发布风险。
测试是一个不停进行抉择的过程。局部探索式测试帮助你做出各种细小决定,比如如何决定文本框的输入值,如何解释错误消息,如何理解前一次输入和后一次输入值之间的关系。
根据软件的各种属性,我将决策分为5部分
- 输入input
- 状态state
- 代码路径 code path
- 用户数据 user data
- 执行环境 execution environment
测试工作简化为现在所有输入或是运行环境的全体集合中选择一个子集,然后再输入时使用选中的子集,最后通过推理认定是否这些输入已经足够多了。
用户输入
输入是由应用程序外部引发的,并导致应用程序执行某些代码。
比如在text field输入一些值不算输入,必须点击了button算输入,因为这样才能执行代码
- 原子输入:2,a
- 抽象输入:月份 什么长度
其实更复杂:各种输入间会互相影响。
比如机器上查找cd文件没有问题,找视频文件没有问题,但同时找cd和视频文件,就有可能报错了. 各种输入的顺序:输入a 输入b ab ba aa bb
如何测试用户输入
合法输入和非法输入
非法输入需更加重视,因为大部分dev不喜欢写错误处理,而是喜欢写实现功能的代码。正因为这种态度,会导致应用程序有问题. 对于非法输入,可以采用以下方式处理
- 错误处理 error handler
- 输入筛选器 input filter. 把坏的输入值挡在应用程序外. powerpoint的插入表选行列,dropdown list. 开发人员是否正确地实现了该功能。非法输入和合法输入的区分。比如月份加入了0
是否可以绕过屏蔽器。( 一个dropdown list option的value通过修改html源代码来实现) 如果有某种方法可以让输入值进入系统,或者当输入值进入系统后还可以修改,那么屏蔽器就没有什么用处了。开发人员还要实现更多的错误检查代码。 - 输入检查 input check.
输入检查属于应用程序代码主线的一部分,通常通过if then else 或是switch case 等等实现。一般是报错,显示错误信息。该信息会描述当前状况,并准确地表示当前的输入值出现了不合法的情况. 牢牢抓住显示的错误信息,必须阅读每一条错误信息,是否写错?错误信息给出的原因如为什么错,怎么修改会给测试人员更多的启发 - 使用异常 exception. 异常处理代码就像错误检测,但是它不是对每一个输入值进行检测,异常处理检测任何一种错误。如果在软件运行的过程中出现任何被指定的错误,该段代码也会进行处理。异常处理代码处理各种各样的软件失效状况,不单单局限于非法输入. 所以说,由异常处理代码产生的错误信息相对于由特定的输入检查产生的错误信息来说,一般更笼统,含糊。
反复测试同一段函数,继续使用刚才引发异常的输入数据,或是修改一下,看会不会出错。或是运行其他一些要调用该函数的测试用例,看看会发生情况。
非法输入应该被忽略,或者应该触发出错信息(出错信息可以显示在一个弹出式对话框中,可以被输出到错误日志文件中,显示在界面的某个特定的保留区域)。应该按照按照规格说明书定义的来正常处理合法的输入,产生合理的结果。如果你观察到的和上述情况有任何不同,这是软件缺陷。
常规输入和非常规输入
特殊字符
默认输入
开发人员设置的默认值。测试人员需要检查这个默认值是否正确。比如一个打印表单中的打印页数字段,默认值就可能是“全部”,我们需要需要验证这是不是用户最有可能使用的数值。看见默认值,可以把它删除,留下一个空白的。开发人员往往没有考虑到这个情况,因为他们把时间都花在如何设置默认值,却没有去想如果改数值没有怎么办。然后还可以试试默认值附近的值。比如给数字默认值大1小1,给字符串默认值头部尾部添加修改删除。一个字段如果被预先设置好默认值,便和不含默认值的字段在程序处理时往往有很大不同,花点时间,会有回报。
空白输入
显示出的错误信息还会根据哪些字段没有填而变化
输出来指导输入
先明确自己希望软件会产生什么样的反应,然后确定哪些输入会引发相应输出,然后再测试中使用这些输入值。
- 确定希望程序产生什么样的输出结果,然后考察所有的用户场景,看看如何去生成期望的结果
- 先观察输出结果,然后再选择新的输入,并保证新的输出是重新计算后的结果,或者确保新的输出结果和原先不同
- 寻找被保存起来的输出结果。这种输出值通常被计算出来后会被显示在屏幕上,或者被存储在一个文件中以供程序未来再次读取。如果这些值是可以改变的,一定要试试去改动这些值或者改动它们的功能(大小类型)
状态
可以这么来看待软件状态,在我们选择下一步使用哪个输入时,必须考虑从前使用过的那些输入所造成的累积效应。应用程序和其运行环境进行交互和接受到的所有输入导致软件状态发生变化。
如果输入a,它就会改变被测软件的当前状态,然后再输入a,就不能算是运行完全相同的测试了。应用程序的状态已经改变,所以对应于输入a的输出结果也可能完全不同。和输入一样,状态同样会影响软件是否会失效。在一种状态下输入一个值,可能一切都很好,如果同样的输入值被使用于另一个状态下,程序可能就出错了。
如何测试软件状态
- 软件接受到输入,并在内部保存这些输入值后,软件状态就会发生变化。测试人员要测试这些状态变化的情况。
- 测试是否正确更新了它自身的当前状态?
- 测试程序的当前状态是否会导致接受某些输入时发生故障?
- 测试软件是不是进入了一些它不应该进入的状态?
输入和状态之间的关系
-
使用状态信息来帮助寻找相关的输入
测试输入的各种组合可以说是测试的一个基本常识。比如:某网站,它允许用户输入coupon来打折,但coupon不适用于打折商品。所以我们需要测试:shopping cart上加入打折商品,然后输入coupon来进行测试。不仅仅是只测试coupon用于非打折商品。在测试中,状态(购买的商品和价格)对结果有什么影响。加上输入值coupon,就会得出输入组合。
-
使用状态信息来辨识重要的输入序列
当一个输入导致状态信息被更新时,紧接着再多次使用同样的输入会导致一连串的状态变化。如果状态变化在某种方式上被累加起来,就必须考虑到溢出的问题了。比如shopping cart会不会被装满?通过观察被测应用程序中状态的累积程度,然后重复使用相同或不同的输入来检验这种累积是否会带来副作用。
代码路径
一条代码路径就是一连串代码语句,它起始于软件开始运行的语句,终止于一条特定的语句,往往就是那条代表软件运行结束的语句。
量大。因为分支,循环等。
测试人员必须明确知道程序里可能有哪些分支,并理解哪些分支输入会导致软件走这条分支而不是另一条。这不是一个简单的任务,特别是要求测试人员在不接触源代码或者不适用那些可以把输入映射到代码覆盖率的工具时。如果某些代码路径没有被测试到,他们中就很有可能存在软件缺陷。 白盒测试可以做路径覆盖。 黑盒来说,更多的业务理解的深入,测试用例的设计,就是测试用例的覆盖率上。
用户数据
测试环境最好有和真实用户数据相似的数据. 做到和真实用户一样的数据 很难:
- 真实用户数据经过长年累月的添加修改,非常多。而测试只是几天,几星期的事情,测试必须在短时间内产生一下子这么多的数据
- 真实用户数据通常包括测试人员不了解的很多相互关系和结构,测试人员也难以推测。
- 数据量大的时候,在哪里可以放得下这些数据也是个问题。不可能去买个昂贵的数据中心。
- 用真实的数据库?那么必须非常小心,且做一些额外工作把数据库恢复到原先的状态。或是复制用副本。
- 隐私。
不管有没有用户的真实数据,测试人员都很麻烦。。。。
运行环境
退一万步说,即使我们可以测试随着用户输入,状态变迁,用户数据变化而变化的所有代码路径,当软件被安装到一个崭新的环境中,还可能失效。环境本身就是一个输入源。
运行环境包含什么?很难回答?os,各种配置,网络情况,其他程序等等很难测试。各种各样的变化,变化的绝对数量远远超过我们可以全部尝试的范围。
小结
输入,代码路径,状态,被存储的数据和运行环境等,这里有太多太多的因素,而每种因素又有太多的变化可能,这一切使得软件测试变得极其复杂。无论你是采用写好case或是探索式测试来解决这个无穷变化的问题,都可以说难如登天。
探索式测试试图把制定计划,进行测试,重新制定计划等多个过程有机地结合起来,每次只前进一小步,但这每一步都是由软件过去和当前的运行状况,软件在测试时表现出来的各种行为和软件运行时留下的种种蛛丝马迹来即时确定的。有效地利用探索式测试技术可以帮助我们发布一个高质量的软件产品。
Chapter 4 全局探索式测试方法
商业区(软件重要特性)测试类型
- 指南测试法:根据用户手册或是产品说明书,验证手册描述的各种特征的正确性以及用户手册的准确性
- 卖点测试法:软件最能卖钱的特性,最重要的。
- 地标测试法:软件特性为地标,在选择完地标后,需要确定它们的前后顺序,然后从一个地标执行到另一个地标来探索应用程序,直到访问了列表中的所有地标。可以有地标覆盖图来表示工作的进展
- 极限测试法:向软件提出很多难以回答的问题,找麻烦
- 快递测试法:专注于数据的流向。应该确认那些被存储起来的输入数据并跟随它们走遍软件
- 深夜测试法:测试除执行卖点特性的代码,比如各种维护任务。
- 遍历测试法:通过选定一个目标,然后使用可以发现的最短路劲来访问目标包含的所有对象
历史区(老的功能和缺陷修复代码)测试类型
- 恶邻测试法:反复测试产品缺陷多的地方,对邻近功能采用遍历测试法,以此来验证那些修复已知缺陷的代码没有引入新的缺陷
- 博物馆测试法:测试遗留代码
- 上一版测试法:如果当前产品构造是对先前版本的更新,很重要的一点就是必须运行先前版本上支持的所有场景和测试用例
娱乐区(辅助特性)测试类型
- 配角测试法:专注于某些特定的特性,它们虽然不是那种我们希望用户使用的主要特性,但和那些主要的特性一同出现在显示器上
- 深巷测试法:如果测试部门已经跟踪产品特性的使用情况,深巷测试法就是建议测试人员应该测试使用情况列表中排在最下面的几项特性。测试各种特性之间的互相作用,试着把最流行和最不流行的特性放在一起混着测。
- 通宵测试法:让程序一直运行
旅游区(快速访问软件的各个功能)测试类型:
- 收藏家测试法:收集软件的输出,确保能观察到软件能生成的任何一个输出
- 长路径测试法:测试离应用程序开始点尽可能远的特性
- 超模测试法:关注GUI
- 测一送一测试法:测试同时运行同一应用程序多个拷贝的情况
- 苏格兰酒吧测试法:有些地方只能从别人那里获得信息,然后测试,测试人员需要事先知道如何去找到它们。
旅馆区(忽视的次要功能)测试类型:
- 取消测试法:启动操作停止它。使用cancel按钮。使用取消功能
- 懒汉测试法:测试人员做尽量少的工作,接受所有的默认值,保持输入字段为零,尽量少的操作。
破旧区测试类型:
- 破坏者测试法:破坏文件,限制资源等,让运行环境恶劣。
- 反叛测试法:非法输入,输入不可能的数据,恶意输入
- 强迫症测试法:重复操作。改变顺序
Chapter 5 混合探索式测试技术
探索式测试蕴含着丰富的策略,它将结构化的思想和自由的探索方式很好地结合起来,在发现缺陷以及检验正确性上都卓有成效
基于场景的探索性测试
测试场景描述了基本的功能,探索则增加了尽量多的变化。
场景可以代表探索式测试的一个绝佳的起点,探索可以给场景加入宝贵的变化,否则场景将很有限
场景的来源
- 需求说明书
- 设计开发过程中信息收集
- 敏捷模式的用户故事
场景的分类
- 讲述用户故事
- 描述需求
- 演示产品功能
- 演示集成场景
- 描述设置和安装
- 描述警告和出错情形
上面的分类都是可以作为开展探索性测试的场景
使用基于场景的探索性测试技术
场景操作:对场景的步骤加以操作,来给场景注入变化。改变特定动作的场景,并从场景中派生出衍生场景,用于测试不同的状态和不同代码路径
-
插入步骤:给场景增加额外的步骤可以使他们更加多样化,从而测试更多的功能
- 增加更多的测试数据:这个场景用到哪些数据?怎样有意义地增加测试所使用的数据?
- 使用附加操作:哪些其他输入和现有场景使用的输入有关?
- 访问新的界面:其他的哪些界面和现有场景使用的界面相关?
这些步骤最终都需要测试人员返回到原始场景。我们的目的是加强场景而不是彻底改变场景的基本目的。在这类场景操作中,测试人员通过增加其他输入,加大数据量或变化场景把整个场景拖长,但是并没有改变场景的核心目标。
删除步骤:递进的方式重复应用这个场景操作,每次只删除一个步骤。去掉冗余和可选的步骤,使场景步骤尽可能的少。可以用来测试应用程序是否可以识别出现在缺少信息或者缺乏一次从属功能
替换步骤:如果场景中某些步骤可以有多种方法完成,就可以用替换步骤的场景操作来修改这个场景
重复步骤:场景经常包含非常明确的动作顺序。通过重复单独的步骤或重复一组步骤来改变这个顺序。测试人员的任务是理解这些变化并创建适当的重复顺序
替换数据:测试人员需要知道与应用程序相关的数据源并创建各种各样的变化
替换环境:兼容性,什么浏览器,系统有变化,配置变化,cookie变化
漫游测试:测试人员查看脚本,找到需要测试人员做决定的地方或者找到可能产生逻辑分支的地方,先往完全不同的方向走,然后再返回到脚本描述的主要路径。漫游实际上创建出相当长的和范围更广的衍生场景。
卖点测试法:现有场景加入新主要功能,测试功能间的互动。
地标测试法:从场景开始并从场景中选取特定功能的地标,然后随机打乱这些地标的顺序,这样得的到场景就和原始场景不同了。
极限测试法:挑战软件,向它提出困难的问题
深巷测试法:现有场景加入新的不重要的功能
强迫症测试法:重复场景中的每个步骤两次或三次。场景中任何操作数据的步骤都值得我们重复做,因为它会导致软件处理内部数据,设置内部状态,然后再改变内部状态。在软件中四处移动数据历来是有效的测试方法,它可以帮助我们找到重要缺陷。
通宵测试法:不断重复运行场景而不需要退出被测应用程序
破坏测试法:在运行场景测试时,在资源调用处进行破坏活动。
收藏家测试法:执行场景和衍生场景时用文档记录下所观测到的每个输出,输出越多场景越多
超模测试法:关注界面
配角测试法:测试人员不是执行脚本描述的功能,而是找到最近的邻近功能来执行。
取消测试法:所有功能的取消
chapter 6 实践中的探索式测试
探索式测试用于帮我们在测试设计中开发出测试用例,它也可以帮助我们发现在规范说明书中可能漏掉的用户场景,还可以组织测试人员的测试思路
实践中有用的漫游测试方法
出租车测试法
对于要到达所需的屏幕、对话框或一些其他功能组件,用户通常有大量的路线可以选择。因此,测试人员的责任和出租车司机一样,他们必须熟悉到达指定位置的每天可能的路劲。在某些情况下,目的地状态有可能依赖于路劲而发生一些变化,这种情况亦应进行验证。这个漫游是从强迫症测试法派生出来的,该测试方法的最终目的是要重复执行某项特定的操作。但是,不是重复执行完全相同的测试路劲,重点是要执行不同的测试路劲。这是它和强迫症测试法的关键区别。
出租车禁区测试法
这种漫游的目的是要验证无论选择哪一条路用户始终都无法到达目的地。
取消测试法
- 集中测试应用程序在功能终止后是否能继续正确工作。
- 在取消被测对象之前应该改变它的状态
- 在进行取消操作后再次尝试同一场景也非常重要
破坏测试法
迫使你考虑应用程序所使用的资源,已便你可以调整可用资源,从而发现会导致其失败的场景
快递测试法
专注于数据。修改属性和项目,并确保它们在其他地方被正确刷新。帮助我们确定这些类型的数据依赖关系,并指导我们有条理地思考数据元素之间的关联。所以作为测试人员,我们需要思考一个数据在不同模块之间的传递关系以及数据被更改后再各个模块
测一送一测试法
可以发现应用程序在多用户同时使用时的缺陷
不同的漫游测试法可以用于开发周期的不同阶段
早期开发阶段的目标
- 早期发现设计缺陷
- 发现被误用的设计
- 发现用户界面和可用性上的误用
这些目标让漫游变得更有目的性:这些探索式测试注重于把事情做成而不是以某种特殊的方式做事情。这些漫游法包括地标测试法和出租车测试法。在测试周期的早期阶段,测试工作会侧重于发现大的问题
后期开发阶段的目标
- 确保产品功能的正常工作
- 确保用户数据的安全性
- 确保完工的软件符合要求
- 描述功能特征的适用范围
- 确认以前的缺陷不再重现
这些目标让漫游变得详细而精确:这些探索式测试注重于一种特定方式来探索某一特定的东西。这些漫游法包括深巷测试法、遍历测试法、超模测试法和取消测试法。
Chapter 7
漫游与测试中有五个棘手问题
-
漫无目的 aimlessness
- 有计划,有准备,有策略和有多变的战术,这是成功进行软件测试的前提。拥有测试策略和规范的技术使得测试人员在处理他们的任务时目标更明确
-
重复性 repetitiveness
- 漫游路径又迫使测试用例的产生具有更多的变化
暂时性 transiency
单调性 monotony
健忘性 memorylessness
其他
探索式测试是一种强调个人自由与责任的测试方法,让独立测试人员可以借由不断的学习来改善测试的规划与测试的执行,而在测试过程中也会同时改善测试案例达到相铺相成的效果