Web测试囧事
上QQ阅读APP看书,第一时间看更新

提到测试,大家首先会想到的就是功能性的测试,因为只有保证了产品的基本功能和流程,产品才具备给用户提供使用价值的能力,从而才有可能确定产品的核心竞争力。基于这一点,不仅测试人员和开发人员,还有产品经理、项目经理、业务方对功能完备性和正确性的重视程度也往往都是最高的。这也使得功能测试成为任何测试类型的基础。

在进行功能测试时,我们会使用诸如边界值分析、等价类划分、因果分析、组合测试(Pairwise Testing)等测试方法来设计和规划测试用例,但是这些方法大多都是从书本上学来或者从别的渠道了解到的理论方法。而实际上在一个真实的项目中如何运用,或者如何在特定的场景和情境下使用这些方法,去充分发挥这些测试方法的作用,并帮助我们高效地设计和执行测试用例却是更重要的问题。

正因如此,我们在功能测试部分并没有介绍这些方法是什么,而重点在于展示在一个具体的场景下,读者应该如何思考,怎么把理论知识和实际工作结合起来,以发现和避免可能遇到的陷阱。

功能测试部分的故事篇幅都会比较长,希望读者能够每读完一个故事,都留出一段时间进行思索和回味,从而理解故事的核心,触类旁通。

为了让每一章的故事更凝练,我们把功能测试部分分成了4章,分别是:技术篇、测试覆盖篇、测试实践篇和业务需求篇。这4章从Web开发流行的各种技术(例如响应式设计、特性开关、虚拟化等)、测试技术(例如实例化需求、集成测试、契约测试等)、测试实践(例如探索性测试、回顾会议、缺陷大扫除等)和业务需求(例如用户的并发操作、关联操作以及产品功能的一致性等)方面向读者全方位展示功能测试范畴中的常见问题和解决办法,更重要的是指引大家从一步一步的分析中得到解决问题的思路。

本章(技术篇)介绍了在开发和测试的技术方面引起的11个功能测试问题,现在大家和我们一起来看看这些丰富多彩的故事。

1.1 输入框中输入超过最大允许值造成页面跳转溢出

作为计算机系毕业生的小蔡,一直觉得学校里学的理论知识和现在的测试工作差距太大,在实际的工作中也没有什么作用,直到有一天她碰到这么一个有趣的问题。

小蔡负责测试的是一个Web产品,基于不同的搜索条件会显示大量的结果。由于搜索结果太多,所以页面有分页功能,为了方便用户快捷跳转到特定的页面,开发人员特意设计了一个页面快速跳转的功能(见图1-1)。

图1-1 搜索结果页面快速跳转

在设计测试用例的时候小蔡就留心了,因为很多测试理论的书籍都描述过,对于输入框可以进行的验证点很多,比如:特殊字符、超长字符、负值、0值null值,以及很大的数值等。小蔡发现,对于大部分的测试用例,开发人员都处理得很好,没有发现缺陷,她也就松了口气。

但是,当她测试到输入大数值的测试用例时,由于不确定多大的数值会出错,也不能拿到代码,小蔡就探索性地选了一个数值:9999999999(10个“9”)。当输入10个“9”点击“确定”按钮时,突然页面崩溃了!她仔细回想刚才的测试过程:100以内的小数值,以及输入小于等于搜索结果页面数的数值等情况,测试结果都很正常。那到底数值大到什么程度的时候页面会崩溃呢?一时想不到更好的办法,小蔡只好硬着头皮尝试着定位出错的数值范围。

她发现,当输入10个“9”就会出错,而输入9个“9”,页面正常跳转到最后一页。突然,小蔡想起来数据结构课程不是学过很多查找法嘛!这样,二分法就这么出现在了眼前,对,二分查找法(见图1-2)! 5000000000(9个“0”),出错;2500000000(8个“0”),正常。她还是没有线索,可是她坚持这么一点一点查找。最后花了将近3个小时,小蔡终于定位到了出错的数值:4294967295。这个时候,小蔡很纳闷,怎么是这么一个奇怪的数值呢?有零有整的。计算机专业的背景给了小蔡一个提示,不会是2的多少次幂吧?果真,4294967295是232-1。当小蔡收集到这些线索之后和开发人员一沟通,才发现由于开发人员设想页面最大不会超过232,所以对这个字段可以输入的最大数值的类型,选择的是int32。但是,开发人员并没有对超过这个范围的数值进行任何的处理,这就造成了程序判断中请求更大数值页面时的溢出,从而导致了产品页面的崩溃。

图1-2 二分查找的原理

提示

二分查找算法(binary search)或者折半查找算法,是一种在有序数组中查找某一特定元素的搜索算法。

搜索过程从数组的中间元素开始,如果中间元素正好是要查找的元素,则搜索过程结束;如果某一特定元素大于或者小于中间元素,则在数组大于或小于中间元素的那一半中查找,而且跟开始一样从中间元素开始比较。如果在某一步骤数组为空,则代表找不到。这种搜索算法每一次比较都使搜索范围缩小一半。


发现了这个隐藏的缺陷,小蔡很有成就感,但又觉得如果能早点知道代码的逻辑,可能自己就不会花这么长的时间了。同时,小蔡一直觉得计算机的理论知识不会应用到测试实践中,可没想到这次多亏了学校中学到的这些基础知识,不只是int32,232,甚至还有二分查找算法也给了自己解决问题的灵感。没有这些知识,定位问题就更困难了。

核心知识:边界值分析法

边界值分析法是一种常见的黑盒测试方法。

测试界的前辈们通过分析常见Bug,发现绝大多数的Bug都出现在输入/输出范围的边界处。因此,如果能够针对这些边界来设计用例,通常能够更加有效。

边界值测试法通常是作为等价类测试法的补充,和等价类测试法结合起来用。即每个等价类的边界部分就可以作为边界值的测试用例之一。

举个例子。假设有一个日期选择器(date picker)的需求,要求我们能够输入时间后,点击搜索查询输入日期前后一年范围内的数据。假设我们把输入的时间分为如下几个等价类:

1)正常时间范围内:now-1年< test data < now+1年;

2)正常时间范围外:test data < now-1年或者test data > now+1年

3)特殊时间:例如now=2月29,1970/01/01等

4)输入非时间的input value:比如输入值为@等

……

结合分类1、2,这个时候边界值的取值则可以取:

▼test data=now-1年;

▼test data=now+1年;

▼test data=now-1年-1天;

▼test data=now-1年+1天

……

考虑到输出范围是一整年的数据,结合特殊时间和时间的边界(跨年、跨月等),还可以增加

▼test data=2月29

▼test data=12月31号

▼test data=1月1号

……

总之,边界值的取值,应该着重考虑边界情况。如果测试对象是时间,则应该重点考虑跨年、跨月等;如果测试对象是纯数字,则应该重点考虑数字的类型与取值范围;如果测试对象是数据表,则应该重点考虑数据表设计的字段长度,等等。

拓展知识:测试用例设计

测试用例记录了测试的过程,我们可以通过测试用例了解哪些场景已经被验证了,哪些场景还没有被验证。除此之外,在传统的测试行业中,测试用例数量通常也被作为度量测试工作量和指定测试工作计划的重要输入。

测试用例通常会包含几个基本要素:用例编号、输入数据、期望结果以及前置条件等。测试覆盖率则是评价测试用例好坏的关键标准。覆盖率高的测试用例能够提高开发、产品以及其他验收人员对产品的信心。在设计过程中,我们通常会通过边界值、等价类等测试方法,结合配对组合测试(pairwise testing),基本路径分析法等测试策略优化测试用例,以达到提升测试覆盖率和减少冗余测试用例的方法。

以下是测试用例设计原则。

1)测试用例的代表性:能够代表并覆盖各种合理的和不合理的、合法的和非法的、边界的和越界的以及极限的输入数据、操作和环境设置等。

2)测试结果的可判定性:即测试执行结果的正确性是可判定的,每一个测试用例都应有相应的期望结果

3)测试结果的可再现性:即对同样的测试用例,系统的执行结果应当是相同的

1.2 索引值计算错误使资源缩略图显示和大图展现不一致

业务方希望在商品展示的页面,不仅能添加展示图片,还可以展示关于商品的视频(见图1-3)。

图1-3 商品展示页面支持展示图片和视频

业务方希望通过图1-3下方的统一翻页操作,实现无缝浏览图片和视频。如果商品既有图片又有视频,当用户在浏览图片时,在图1-3右下角显示“Video”(视频),这时用户可以通过点击“Video”链接打开第一个视频;同样当用户在浏览视频时,在图1-3右下角显示“Image”(图片),这时用户可以通过点击“Image”链接打开第一张图片。

小蔡按照通常的步骤编写完测试用例,开始使用标准测试数据执行测试。小蔡首先发现点击右下角链接时,本应该显示第1张图片或者视频,但是打开的却是第2张图片或者视频。

小蔡觉得这可能是开发人员在处理图片和视频展示的数组时,使用的是自然数计数,从1开始作为第1个数据项,而非计算机程序数组中通常使用的把从0开始计数作为第一个数据项。当小蔡把这一问题上报之后,开发人员发现确实是这个原因,并进行了快速修正。

小蔡在开发人员新发布的包上又一次进行了测试,这次测试用例基本没有什么问题,她就开始在类真实环境中执行探索性测试,结果发现在某些商品页面进行图片和视频跳转时,出现图片或视频显示错误或者显示成空白的问题。

在老牛的协助下,小蔡发现了问题出现的两个规律:①当从第n张图片切换到视频的时候,系统显示的并不是用户期待的第1个视频,而是第n个视频;②当商品的图片和视频数量不一致,从数量多的资源切换到另一个数量少的资源时,内容就会出现空白。

小蔡和老牛都觉得出现问题的原因比较明确了,是因为在图片和视频跳转时,打开资源的索引值并没有清零,而是保存着前一个元素的索引值。

为什么这个问题在测试环境中没有发现,而在类真实环境中才被发现?原来最初小蔡在测试环境中执行测试用例的时候,使用的是基本测试数据,图片和视频都只有两个,而且两张图片和两个视频的内容分别都是一样的,因此不会出现这个问题。

小蔡和老牛商量了下,决定为了避免遗漏这样的问题,需要丰富测试环境的测试数据,使得测试数据更像真实数据,这样测试结果才更为准确。

同时他们认为这个问题在开发人员的开发过程中也是不应该引入的,因为这样的错误比较初级。于是小蔡和开发人员进行了沟通,发现引入该问题的根源在于开发人员的疏忽。由于图片的文件格式和视频的文件格式不同,所以开发人员使用了两个库来支持图片和视频的打开,但是两个库之间的跳转需要通过在两个库之间传递参数来实现,然而开发人员并没有仔细检查两个库之间所传递的参数,导致并不需要被传递的索引值也传递到另一个库中。

老牛带着小蔡和开发人员立下一个约定:在使用第三方库实现功能时,一定要把使用的函数方法中的所有参数都核对清楚

核心知识①:探索性测试

探索性测试(Exploratory Testing)是软件测试方法的一种。这种方法强调测试者的主观能动性,以及测试设计和测试执行的同时性。目的是探索开发更多不同形态的测试方法,以便改善测试流程。

“先设计,再测试”的传统做法,通常是先分析出测试点,然后针对测点设计好测试方法,最后执行测试。这种模式也带来了一些问题,比如在测试目标不确定的情况下(改需求、输出范围过大等)经常可能出现测试遗漏等,而且在一定程度上也限制了测试思维的发散。而探索性测试的出现,正好弥补了传统测试中出现的这种问题。

探索式软件测试一共分为自由式探索式测试、基于场景的探索式测试、基于策略的探索式测试和基于反馈的探索式测试。

(1)自由式探索

纯粹从使用的角度出发,抛开规则、模式,测试人员可以以任意顺序和方式对软件进行使用测试。这种测试通常会被选作快速冒烟测试用例。

(2)基于场景的探索式测试

这种测试跟传统基于场景的测试比较像,不同的是,在这种测试中测试人员会扩大测试范围。例如,对某搜索框的测试,传统的场景测试用例可能是:

1)输入“电视”,期望结果是搜索到电视;

2)输入“123”,搜索到123相关的内容。

而基于场景的探索性测试下,测试场景则可能是:

1)输入“电视”,探索搜索结果;

2)粘贴“ 1@3”,搜索结果;

3)输入一个乱码,搜索结果;

4)输入电视,搜索结果后回退到搜索首页再搜索;

……

(3)基于策略的探索性测试

这是一种比较依靠经验的测试方式。简单讲就是测试老手,融合自己的经验、技能、感知等条件,结合自由式探索性测试,用自己积累下来的知识来指导测试。是一种经验结合随机性的测试。

(4)基于反馈的探索性测试

反馈指的是当测试人员对被测程序做出指令后得到的响应结果。基于这个结果,测试人员可以调整自己的输入,以期望得到不同的结果。例如,在基于场景的探索性测试的描述中,输入电视和输入电冰箱会得到不同的结果,而其中电视的搜索结果就是对电视这个输入的反馈,电冰箱的结果就是对电冰箱这个输入的反馈。

核心知识②:软件开发中的各种环境

1)开发环境:就是每个开发人员进行编程的电脑,包括软硬件及其配置,为了开发调试方便,一般打开全部错误报告。

2)测试环境:测试人员进行产品部署,并进行功能等测试的环境。

3)预生产环境(非必须):与生产环境不定期同步,保持和生产环境的设置、数据的一致性,也是用于测试的环境。与测试环境的最大区别就是它和生产系统同步性最高,有些比如数据迁移测试,用这个环境测试比测试环境(一般情况下数据较少)更准确。

4)生产环境:正式使用的系统环境,一般会关掉错误报告,打开错误日志。

通常情况下,一个环境对应一个服务器,不同环境代表着系统开发的不同阶段:开发→测试→部署→上线。

1.3 测试Web Service能否正常提供JSON数据

某一天,小蔡所在的项目组刚开发完成一个Web Service,服务的功能是,通过在客户端调用时指定的一个ID,可以从后台数据库中读取对应的房产信息,还有与这个房产关联的一到多个房东信息、一到多个图片信息,以及地址信息等。Web Service最终把这些信息组合成JSON格式的数据返回给调用方,调用方可以通过界面来展示相关信息,也可以通过其他方式去使用这些信息。但是,调用方具体如何使用这些信息与Web Service服务本身的测试关系不大,所以暂且不表了。小蔡需要做的是验证这个Web Service服务能否正常地提供JSON数据。

1.寻找测试关键点

和开发人员讨论后,小蔡了解到,给Web Service服务发送ID后,后台系统会根据这个ID去查询数据库,数据库对应的对象模型如图1-4所示。

图1-4 生成JSON的对象模型

在这个对象关系图里可以看到,Building对象是图的核心,其他对象分别和Building对象是一对一或者一对多的关系。那么小蔡觉得,测试的关键点就是这些数据之间的关系能否正确反映在生成的JSON数据里。(特别是要确保生成的JSON数据的格式没有被破坏,否则解析器就无法解析JSON数据了。)

2.爬楼梯开始了

在确定好测试点之后,小蔡测试思路如下。

1)依据边界值进行测试。由于数据库的数据存在多种情况:0条记录、单条记录、多条记录,需要验证根据这些数据条数生成的JSON文件是否正确。

2)需要关注对象之间的关联关系限制。例如一个Building对应0到4个Callout,那么需要测试当多于4个Callout时,生成的JSON的情况。

3)Web Service允许用户输入参数,所以需要验证输入特殊数据后,系统是否出现异常或者崩溃的现象,以及是否会出现SQL注入的问题。

4)Web Service有可能会被多个客户端并发调用,因此需要验证其性能是否符合设计要求。

根据测试思路,小蔡设计了如下测试用例。

1)Building对象可以有多个Space,所以可以选取0个、1个、6个Space去看生成的JSON是否正确,这个可以通过修改数据库数据的方式生成假数据去完成相关测试。另外,如图1-4所示,小蔡还需要测试Building和其他对象的关系,包括Customer、MediaLink等。小蔡还特别注意了边界值测试,比如,一个Building对应0~4个Callout,那么可以用0、1、4、5个Callout去测试。当有5个Callout的时候,要去查看返回的JSON数据是返回了前4个Callout,还是返回了前5个,又或者抛出了异常,当然,具体哪种结果是程序设计所期望的,需要和开发人员去沟通。

2)调用Web Service时需要指定ID,所以可以测试在ID存在不存在两种情况下JSON结果是否返回正常。

3)异常处理,调用Web Service时可以把ID换成错误的参数,例如null、非常长的字符串、空字符串、英文字符、其他文字(例如中文字符)等。接着小蔡就去验证服务对异常是否做出了正确的处理。

4)安全测试,这个Web Service会被外网使用,那就需要在安全性上投入更多的测试。例如,如果URL可以输入任何参数,这些参数是否包含危险数据导致SQL注入这类安全问题。还有,因为Web Service被外网使用,那在调用这个服务时是否需要提供用户名和密码。

JSON使用了一种特殊描述数据的方式,用“, ”、“[”、“]”、“{”、“}”等分割数据,小蔡需要测试,如果数据库存储的数据恰好有这些符号,这些数据被读取并生成为JSON后是否会破坏JSON的格式。

5)性能测试,如何对Web Service进行性能测试呢,快速测试方式就是通过一些工具的多线程调用,或者手工编写简单多线程代码去调用,然后查看持续调用两小时之后服务是否正常,服务后台内存曲线是否出现异常,有没有出现内存泄露等问题。另外,查看Web Service是否能在符合性能期望的时间内返回数据。

还有一个边缘的性能测试路径是:一个对象有成千上万个关联子对象,能否正常生成对应的JSON文件,以及性能是否能达到期望目标。庆幸小蔡这次测试任务不需要这么做,因为后台数据明确了不可能存在这种情况,但是在别的测试任务中可能需要测试。


提示

假设一种大数据量的场景,如果关联的对象有10万个,我们可以通过工具在数据库中生成10万条数据。这时候,生成的JSON文件可能会有几十兆,那么我们要看需求文档,对这种场景期待的性能指标是多少,例如单个用户调用的话5分钟之内需要生成完毕,20个用户并发情况下10分钟生成完毕。我们需要验证是否5分钟或者10分钟内能生成完毕,同时还要验证,在这个过程中,后台内存增长曲线是否出现过于陡峭的现象。


6)查看生成的JSON数据中,一些特殊类型的数据是否和数据库完全一致。例如,对于float类型,有个Bug是该类型数据精度在数据库中是保存到小数点后8位,但生成的JSON中只保留小数点后2位;类似的特殊数据还包括非常长的字符串,需要查看是否被截断;如果在数据库中保存的是0,在JSON中是否会生成0.00,此外还可以查看空字符串是否正确生成在JSON中。

7)确定服务是否需要支持HTTPS加密,如果需要,就要去测试返回结果是否正常。

哈哈!终于爬完楼梯了,小蔡可以松口气了,走!喝杯啤酒,再来块炸鸡么?

1.4 利用JavaScript加载的漏洞提前购买抢购商品

自从小米手机推出以来,抢购风潮在各类网站上盛行起来,小蔡测试的网站自然也不能免俗,项目组开发的网站也包含了抢购功能。

对于抢购来说,只有到了特定的时间后,商品才会开放并允许抢购,并且抢购网页的代码里使用的时间会定期和服务器进行同步。

小蔡设计了丰富和全面的测试用例,在执行基础测试用例过程中没有发现抢购功能的Bug,不过在进行多地区和多语言的性能测试时,她发现了一个功能上的漏洞,发现漏洞的过程是这样的。

在执行性能测试时,需要选取不同国家和地区的服务器去模拟用户的真实访问,以验证产品性能是否能满足用户体验的要求。显然,通过这些不同国家和地区的服务器访问网站,会比小蔡在公司内部使用内网访问速度慢,更别提有些国家和地区网络发展慢,这些区域的访问速度就更慢了。

然而正是通过使用这些网速很慢的服务器,小蔡发现了这个功能上的漏洞。

1)在高速或者说正常网速的情况下,当用户打开抢购商品的页面时,页面JavaScript会很快加载并执行完成,这时“加入购物车”的按钮会变灰,无法进行操作(见图1-5)。

图1-5 正常网速,等JavaScript加载完成之后“加入购物车”不能操作

2)而当网速很慢时,由于网页中JavaScript是顺序加载执行的,所以“加入购物车”按钮先会显示为可以操作的状态,等JavaScript加载完成后,才会变灰,不可操作(见图1-6)。

图1-6 慢速网络,在JavaScript加载完成之前“加入购物车”按钮可以操作

3)所以在慢速网络中,当页面JavaScript没有加载完成时,用户会看到“加入购物车”按钮是可用状态,因此用户可以点击该按钮,并把抢购商品加入购物车,于是用户就可以在正式抢购开始之前顺利地抢到该商品了。实际上甚至在商品无货的情况下,用户也可以在JavaScript没有加载完时点击“加入购物车”按钮,并且成功把无货商品加入购物车(见图1-7和图1-8)。

图1-7 慢速网络,无货的商品也可以添加到购物车

图1-8 慢速网络,无货的商品也可以支付购买

这个问题是由JavaScript没有加载完成引起的,但是稍懂技术的人甚至可以禁用浏览器的JavaScript,或者通过查看网络访问的URL后,在按钮是灰色的状态下也有可能提交购买请求。所以,修改这个Bug时,还需要在提交订单时做二次验证,去验证该商品是否有货,或者是否已经处于抢购状态,当状态为“是”的时候,才允许后续操作。

当小蔡把这个问题提交给开发人员和产品经理后,经过大家的讨论,发现如果调整商品页面上的JavaScript加载顺序,会涉及很多JavaScript文件的修改,影响范围会很广,包括移动页面上的代码也需要大幅修正,因此整体工作量比较大。鉴于刚才提到的在提交订单时做二次验证也能避免这样的问题,所以他们一致决定采取做二次验证这种方案来进行修复。

拓展知识:JavaScript的加载与执行

浏览器的渲染线程和JS执行线程是互斥的,并且JavaScript默认是阻塞加载的。页面的下载和渲染都必须停下来等待脚本执行完成。JavaScript执行过程耗时越久,浏览器等待响应用户输入的时间就越长。

(1)加载

不管是script标签直接引入的情况,还是src加载的外部资源,都会阻塞页面的渲染。所以一般为了从体验上考虑,我们会将JS文件放置在body标签闭合之前。不过新版的IE、Firefox、Safari和Chrome都允许并行下载JavaScript文件。但是只是JavaScript文件可以并行下载,渲染还是被阻塞的,页面仍然必须等待所有JavaScript代码下载并执行完成才能继续。

(2)执行

每当JavaScript文件加载完成后,都会立刻执行该文件。所以你会看到下一次的请求并不是在上一次请求结束之后立即开始,中间的耗时就是上一个脚本文件的执行时间。

一般对于JavaScript的优化建议如下。

1)将script脚本文件放置在body标签闭合之前。

2)减少script请求数量。

3)无阻塞脚本,在页面加载完成后才加载JavaScript代码。这就意味着在window对象的onload事件触发后再下载脚本。

▼Defer, async。

▼动态添加script元素。

不过在上面这个故事中,正是由于对JavaScript的优化引发了抢购页面先显示元素可操作,然后在JavaScript加载完成后,JavaScript执行使元素不可操作。

1.5 过长的控件名称造成其他元素显示错位

小蔡接到一个公司内部在线表单项目的测试任务,这个项目中有3个独立的角色:管理员A负责编辑和布局表单控件,用户B负责填写表单中控件的内容,而审核员C只能查看B操作后的结果。

这个需求看上去不难,小蔡快速分析并且记录了以下几个测试点,开始了测试工作。

1)A可以正常添加不同类型控件(文本框、富文本框、下拉列表、多选框、单选框等)到页面中。

2)B可以正常在这些控件中输入数据。

3)C可以正常查看B所有的操作内容。

4)针对各个角色所具有的不同的操作权限进行验证。

5)A更新表单后,B要可以看到对应的变化。

6)A未完成操作或者未保存的表单,B是无法看到和使用的。

7)B不保存后者提交操作结果,C无法看到B的操作内容。

8)跨浏览器和跨平台测试。

在测试过程中,小蔡发现这个表单做的类似于我们熟知的Office应用的所见即所得的形式,即A在屏幕左上方放置一个多选框并保存后,B打开页面后也可以在屏幕左上方看到一个多选框,最后C登录后也可以在屏幕左上方看到这个多选框。这使得整个功能变得更加容易测试,几乎是瞥一眼就能看到有没有问题。

三下五除二就跑完用例,小蔡正准备结束测试,偶然间一眼瞥到表单上的多选框选项上,她突然意识到既然多选框和单选框中的选项内容都是自己定义的,那在选项内容中输入超长字符串时,会不会导致该控件的显示位置发生变化呢?

带着这个疑问,小蔡重新使用A角色设置了一个超长的多选框选项名称,然后用B角色做了勾选操作,最后再用C去查看结果。结果她发现了问题:C查看到的结果中,勾选后的内容和多选框显示错位了。

再仔细重现了一遍问题后,小蔡发现A在编辑模式给多选框设置超长的选项名称的时候,在界面上多选框的位置被挤到了文本中央,如图1-9所示。

图1-9 表单编辑界面

B在操作视图中看到的这个多选框又被强制对齐到了文字最左侧,如图1-10所示。

图1-10 表单操作界面

而C在审核视图中看到的情况却是A和C的结合体,即多选框被重置到了文字中央,而被勾选的位置却出现在文字的最左端(见图1-11)。

图1-11 操作结果查看页面

有了小蔡的过程重现,开发人员很快就定位到出现问题的原因:首先是在A用户使用的过程中(见图1-9),没有把操作结果查看视图中的多选框强制左对齐。其次是在用户C使用过程中(见图1-11),由于使用jQuery定制化多选框中的代码逻辑错误,导致勾选状态下的可勾选位置和多选框本身出现了分离,最终导致问题出现。

核心知识:常见的HTML元素及常见检查点

1)<select>标签,可创建单选或多选菜单

常见检查点:下拉列表数据的正确性;数据被选中是否正确,是否变形,是否只读,多选/单选是否正确

2)<label>标签,相当于一个展示文本框

常见检查点:文本是否正确;文本字体、大小、颜色、间距是否正确;for属性是否绑定了正确的元素等

3)<input>标签,用于收集用户信息。

根据不同的type属性值,输入字段拥有很多种形式。可以是文本字段、复选框、掩码后的文本控件、单选按钮、按钮等。

4)button可点击的按钮,点击后通常会触发相应事件。

常见检查点:点击按钮后触发的行为和期望不符合;页面卡死未响应;点击后变形等

5)text文本输入。

常见检查点:是否只读;SQL注入攻击;输入内容超过文本框长度是否引起形变等。

6)checkbox多选框

常见检查点:选中/取消勾选是否有效;文本长度过长是否引起形变;页面刷新后是否被自动取消/勾选等

7)radio单选框。

常见检查点:单选是否有效;文本长度过长是否引起形变;页面刷新后是否被自动取消/勾选等

8)image图片区域。

常见检查点:图片加载是否正确;图片加载失败或者关闭时行为是否符合预期鼠标指针移动到图片上后显示的文本是否正确;图片是否可以正确点击/拖曳等

9)submit提交按钮,提交当前<form>表单信息到指定页面。

常见检查点:提交信息完整性等

1.6 多次操作本该禁用的页面组件造成服务器出错

对页面上的组件进行多次点击是测试人员经常使用的小技巧之一,通常小蔡在执行完基本测试用例之后,开始进行探索性测试时会使用这个技巧,并且利用这个测试技巧发现了不少问题。

这些问题主要集中在用户提交服务器请求后服务器进行处理的相关功能上,例如读取、保存、提交、删除等功能(见图1-12)。

图1-12 用户通过“删除”“保存”等功能和服务器交互

小蔡发现如果网络速度比较慢或者产品本身性能不够好,在用户点击了这些功能按钮后,而页面刷新完成之前这段时间内,该功能按钮仍然可能被用户继续点击。即使有些页面上的按钮处于灰色不可用状态,但当你尝试点击这些灰色的按钮,会发现点击后仍然会给服务器端发送请求(见图1-13)。

图1-13 功能按钮在被置灰后仍然可以使用

这样带来了一系列问题,举例来说,对于保存功能,用户多次点击后会向后台发送多次请求,数据库中也会产生多条重复的数据,这样不仅会造成数据统计错误,更会给再次使用这些数据的人或程序造成很大的麻烦。一个更极端的例子是免密码支付的场景,当用户不小心多次点击支付按钮后,会给用户造成不小的损失;对于删除功能来说,多次点击“删除”按钮后,实际上第一个请求已经让数据库将对应的数据删除了,接下来的删除请求可能会造成后台程序的大量异常。

小蔡发现解决此类问题也很简单,只需要开发人员在编写代码时注意,只允许对该类功能按钮操作一次,在用户操作之后,不仅需要把对应的功能按钮置灰,同时需要取消这些功能按钮上面绑定的事件响应处理机制。

通常小蔡除了会在小组的回顾会议上向开发人员分析这些问题产生的原因和避免方式,还在每张开发故事卡上明确地标注需要测试多次快速点击按钮的场景,这样可以让开发人员从意识上提高对这类缺陷的警惕,从而在编写代码时注意预防此类问题的发生。

拓展知识:回顾会议

无论一个Scrum团队有多出色,总有需要改进的地方。即使一个好的Scrum团队会不断寻找需要改进的方面,这个团队也应该做一个简单回顾,目的是在每个迭代的最后来回顾团队目前做得如何以及找到改进的方法。

回顾会议(Sprint Retrospective)通常发生在每个迭代最后一天,用来帮助团队进行自我改进。会议长度通常为1个小时,团队成员在一起列出团队应该做的事情、需要停止做的事情、应该保持下去的事情。接着团队成员对所有提议进行投票,这样可以在有限的时间里优先讨论大家最关心的几个问题。通过集思广益,提出改进方法,在紧接着的迭代中进行改进。

在下个迭代的回顾会议一开始,会首先关注上一次回顾会议的结果是否被落实。

1.7 页面跳转后出现HTTP 400错误

公司网站又升级了!这次升级后网站增加了一个保险报价功能:客户先在网站上回答公司设计的各种问题(单选题),系统会把答案汇总起来,传给后台计算价格,然后后台系统把计算出的保险报价返回给网站并显示在页面上。

从功能上看,小蔡觉得这个功能需求的关键是网站上设计的每一个问题都会作为一个价格因子并对最终报价产生影响。测试的重点应该是检查每个因子能够引起的价格变化是否符合预期。另外由于每类问题都分布在不同的页面上,所以需要确保在页面切换后,系统能保存之前选择的答案而不丢失。最后性能要求是在后台系统返回报价的时间上,根据业务方的需求需要遵守2/5/8原则,最长不能超过8秒,所以还需要增加对应的性能测试。

设计完用例,小蔡和开发人员一起进行了冒烟测试,结果每个功能都符合预期。于是小蔡在正式测试阶段把大部分精力都放到了验证各个价格因子对价格变化的影响上面。

经过大量的反复选择问题答案和页面切换操作后,小蔡像之前一样准备进入到报价汇总页面查看最终价格,突然发现页面出现HTTP 400错误(见图1-14)!

图1-14 HTTP 400异常

小蔡眼睛一亮,赶紧回退到上一个页面,重新点击报价汇总按钮再次进入报价汇总页面,反复试了好几次,发现仍然会碰到HTTP 400这个问题。于是她关闭并重新启动浏览器,尝试再次重现这个问题,准备整理下出现问题的场景,然后报给研发人员修复,结果这次却正常进入到报价汇总页面。

小蔡觉得挺奇怪的,难道刚才是服务器“抽风”才导致HTTP 400异常?于是她又一连操作了好几次到汇总页面的场景,结果都没有重现问题。虽然感觉很神奇,但是因为没有能够重现问题,小蔡也只能暂时作罢,一边想着肯定是服务器抽风了吧,一边继续测试价格因子。

5分钟以后,当她再次准备进入到价格汇总页面时,问题又出现了,再次显示HTTP 400错误页面!看着页面上提示的错误信息,小蔡突然间有点儿明白了,提示信息说HTTP Request Header长度过长,因此问题应该是在HTTP Request Header上。小蔡利用浏览器自带的开发工具查看了下HTTP的请求头,结果瞬间就发现Cookie看上去似乎比平时看到的要长好多(见图1-15)。

图1-15 从浏览器工具中查看Cookie

小蔡怀疑是Cookie有问题,于是就找开发人员确认。在给开发人员演示了一遍这个Bug后,开发人员终于分析出造成这一结果的直接原因是Cookie长度太长,导致Request Header长度超标,最后发生HTTP Error 400。而再深入一层的根本原因是开发人员为了在切换页面时保存前几个页面的答案状态,把这些状态存到了Cookie里面,而每次翻页的时候又把答案信息错误地添加到Cookie已有信息之后,导致随着小蔡翻页操作越来越多,Cookie也越来越长,最后当Cookie长度超过浏览器限制后就产生了错误。

知道了问题原因,小蔡仔细回想了下整个用例设计过程,觉得这样的问题即使通过覆盖各种用户使用场景仍然会不好发现。但如果测试人员在测试之前能够对整个功能的设计或者实现有一些了解的话,就可以注意到这个点,并且加以测试了。例如这次的Cookie超长问题,如果测试人员和开发人员先进行一些沟通,了解到代码是通过把信息存储到Cookie中去实现该功能,那么测试人员会很自然地想到Cookie长度通常是有限制的,因此会对这种存储信息的方式做特定的测试。

核心知识:HTTP Request Header长度限制

Request Header就是往服务器发送的请求头,HTTP协议中并没有限制Header的大小。理论上无论我们的Header有多大都是可以的。

但实际上各个主流浏览器都会对Header长度进行限制,从几十KB到几百MB不等,基本上能满足平时的需求。此外,在服务端也可以对Heder长度做限制。比如Nginx就可以限制Header的长度。

HTTP Header如果不限制大小会有什么影响?

如果某个网站的服务器是不限制Header大小的,那么它就有可能被黑客利用实施攻击,比如DDoS。黑客可以利用这一点,发送一个非常大(比如几MB)的请求,会占用服务器一个进程来专门处理这个请求。此类请求数量过多时,服务器就无法提供其他对外服务。

1.8 使用没有添加时间戳的缓存使用户看到过期数据

当代主流的网站都使用了缓存技术,目的在于减少用户请求对服务器的压力。当用户首次通过浏览器请求服务器的资源时,服务器会返回所有的资源;当用户再次请求服务器资源时,浏览器会判断资源是否已更新,如果更新了,再向服务器发起请求,如果没有更新,就使用浏览器中缓存的资源。

这里有一个问题,浏览器是如何判断资源是否更新了?一般来说,资源文件在文件名中要么添加时间戳(见图1-16),要么添加标识符(标识符可以是任何一组区别资源不同版本的数值,如v1、v2,或者GUID等)来唯一区分资源的不同版本(见图1-17)。只要本地缓存的资源文件和服务器端最新的资源文件名称不一样,浏览器就要从服务器端获取新的资源文件,如果一样,就使用本地缓存的资源。

图1-16 带有时间戳的资源文件

图1-17 带有标识符的资源文件

如果资源文件没有被添加时间戳或者标识符呢?那用户只能通过手动清除浏览器的缓存来强制获取最新的资源文件了,不过这样等于所有用户请求的文件都没有从浏览器缓存中读取,也就没有为服务器缓解访问压力。并且绝大多数用户并不会去手动清除浏览器的缓存,这就导致用户看到网页的资源文件不是最新的。

最近小蔡测试的商品详细信息页面出现的Bug就属于这种情况。根据用户线上反馈,当用户修改了商品图片并且发布之后,不能及时看到更新后的图片,而是需要等一段不确定的时间才能看到更新后的图片。

经过小蔡和开发人员的调查,发现导致这个问题的原因是缓存资源文件没有被设置时间戳或者标识符,导致用户修改后不能看到更新后的图片。而之所以会造成不确定时间后才能看到更新后的图片,是由于不同浏览器对于没有设置时间戳或者标识符的缓存资源文件处理不一致造成的。

例如IE缓存时间就是一个Session的时间,如果用户打开一个新的IE窗口时,他们就会获取最新的静态资源;而其他浏览器(例如Firefox),则会通过HTTP头文件中Last-Modified参数的具体定义来判断是否需要去重新获取资源。

而小蔡在之前的测试中,为了避免缓存数据对测试结果的影响,她在浏览器中设置了每次退出浏览器时清空缓存(见图1-18),这反而导致小蔡漏测了缓存没有及时刷新的问题。

图1-18 退出IE时自动删除缓存

由于缓存在Web产品中是普遍存在的,所以小蔡立刻取消了浏览器中清除缓存的设置,改为使用手工清除缓存的方式。当她之后在测试中遇到测试结果和预期结果不同,并且有可能是缓存未清除造成的情况时,她会手动清除缓存,这样可以更准确地进行测试。

核心知识:Web缓存

浏览器本身有缓存机制,比较常见的是浏览器会缓存访问过的网页,当再次访问这个URL地址的时候,如果网页没有更新,就不会再次下载网页,而是直接使用本地缓存中的网页。只有当网站明确标识资源已经更新,浏览器才会再次下载网页。

使用Web缓存可以减少网络带宽消耗、降低服务器压力、减少网络延迟,加快页面打开速度。同时我们也可以使用代理服务器,在代理过程中做缓存处理,也可以使用CDN网络提供的缓存能力。

除了上述提到的软件之外的缓存方式,服务器端软件内部也经常使用各种缓存,例如使用数据库进行缓存,或者使用内存进行缓存,也可以提高用户访问网页的速度。

1.9 代理服务器过度缓存文件导致读取错误的账号信息

缓存不仅仅是Web产品为了缓解用户访问带给服务器的压力而设置的,而且用户(例如企业)为了减少多用户访问同一个网站占用过多带宽,也可以设置自己内部的缓存服务器。

小蔡的公司就为大家设置了这样一组缓存服务器。这些缓存服务器的原理和浏览器的缓存原理类似,当大家访问网页时,首先请求的是这些服务器上的资源,如果没有命中,也就是说这些资源不在公司内部的缓存服务器上,这些服务器才向公司外部真实的服务器发送请求,等请求返回后,缓存服务器还需要把这些资源保存在本地,以便于其他用户对同一网页的再次访问,然后才把这些资源发送回最开始发送请求的用户(见图1-19)。

图1-19 企业内部缓存服务器

当然我们这里只是简单地介绍一下内部缓存服务器进行缓存的过程,很多例如缓存如何过期这些细节在这里就不详述了。

小蔡在测试网站的过程中,发现自己登录测试账号后,首先会看到其他测试账号信息(而且这个账号不固定),如果这时候进行任何操作,很有可能出现错误页面,尤其是与账户信息相关的操作,所以小蔡只有强制刷新或者退出后,再次登录才能看到刚才登录的账号信息,并且正常地进行测试。这个问题在早上一般不会发生,而其他时间出现的频率就要高很多。

发生几次之后,小蔡觉得很好奇,就询问了其他的测试人员,发现大家都有同样的问题,在和老牛一起分析后,他们觉得有可能是公司内部缓存服务器机制导致了这个问题,但是至于为什么早上这个问题不容易发生,还是不太了解。

带着这个问题还有他们的怀疑,小蔡和老牛找到了公司信息维护的相关人员。经确认,确实是他们缓存了测试服务器的资源,而且还缓存了登录用户的Cookie文件。

所以当测试人员访问测试服务器时,缓存服务器将输入的用户名和密码以及Cookie文件一起发送给了测试服务器。虽然服务器接收了新的登录用户名和密码,但是显示却使用的是Cookie里面的信息。所以,在进行账户相关操作时,服务器并不知道应该对哪个账号进行操作,于是出现了错误页面。

那为什么早上这个问题出现的几率会小很多呢?根据小蔡他们的猜测,很可能是因为早上正式开始执行测试的人员比较少,时间间隔长,导致Cookie文件虽然被发送,但是过期了,所以服务器很少会出现错误页面。

最后,经过公司信息维护的相关人员的重新配置,取消了对登录用户Cookie文件的缓存,小蔡他们再也没有遇到这个账号显示错乱的问题。

拓展知识:Session和Cookie

由于HTTP是一个无状态协议,客户端每次发出请求时,本次请求无法得知上一次请求的状态信息。在常见的网站中,服务端需要在多次HTTP请求间共享数据,例如用户在购物网站登录后,跳转到商品页面,这时候服务器端需要知道该用户是否已经登录过。在技术上可以使用Session和Cookie去做这件事情。

简单来说,Session是在服务端保存的数据,Cookie是在客户端浏览器中保存的数据,它们一起合作,来实现跨HTTP请求的数据共享。例如,当服务器第一次创建Session时,在内存中记录了用户的信息,同时会在HTTP协议中告诉客户端,需要在Cookie里面记录一个Session ID,以后每次请求都会把这个Session ID发送到服务器,服务器就可以知道这个用户是谁了,从而能从服务器端查询出此用户的各种相关信息。

1.10 多余的空格造成服务器被删除

在敏捷测试中,测试工作不仅包括设计和执行测试用例、编写测试报告,以及测试计划和策略的制定,还有测试部署脚本等工作。这次小蔡在执行部署脚本的测试过程中发现了一个严重的问题,这个Bug会导致整个测试服务器被删除,事情的缘由是这样的。

小蔡根据开发人员提交的部署脚本和执行步骤,一步步在测试环境中进行部署,同时去验证部署脚本中是否有遗漏和错误。当执行完部署脚本中删除临时文件的步骤后,小蔡发现后续步骤都不能执行了!经过调查发现原因是测试服务器已经被格式化了。

虽然测试服务器都是虚拟化的,可以很快在测试环境重新建立一台测试服务器,但是,如果这个Bug出现在生产环境,那将会是非常大的灾难!

小蔡找到了刚才执行过程中出现问题的语句,是“rm -rf / tmp”。她仔细检查了整个语句,发现测试服务器被格式化的原因是在tmp前面多了一个空格,这个空格不仔细看还看不出来(业内也有同样的知名例子:Bumblebee误删用户文件夹,如图1-20所示,在最后一行的/usr /lib…语句中在usr后就被多写了一个空格)。

图1-20 Bumblebee误删用户文件夹

不过在Linux系统中,即使输入了灾难性的操作语句,但如果没有管理员的权限也是无法执行的,因为在执行这条语句的时候,只会出现权限不足,无法操作的提示,不会把服务器整个删除。如图1-21所示,执行删除语句的用户由于没有管理员权限而被拒绝执行了。

图1-21 删除文件需要相应的权限

所以小蔡下一步需要在脚本中找出是哪里赋予了管理员权限,导致后来删除语句被允许执行的。再仔细查找之前的部署脚本,果然不出所料,之前有一个操作步骤需要用管理员权限来复制几个不同的文件到管理员文件夹。通常做此类操作时,会只针对这条语句赋予权限,例如使用sudo命令的这条语句:“sudo cp xxx.zip /var/xxx/xxx/”,但开发人员在这里为了简化脚本的书写,把每一句最开始的sudo转换成单独的一条“su-root”,这种改变造成之后执行的所有语句都被赋予管理员权限进行执行,最终导致格式化测试服务器的命令也被执行了。

小蔡把这一发现告诉了开发人员后,开发人员修改了两处代码:一是删除“su-root”命令,改为针对每次执行需要特定权限的语句时单独添加sudo的方式,确保脚本执行的权限是正确的;第二是删除了导致测试服务器被格式化的那个空格。

核心知识①:sudo和su

sudo用于类UNIX操作系统(如BSD)、Mac OS X,以及GNU/Linux,以允许用户通过安全的方式使用特殊的权限去运行程序,例如使用系统的超级用户权限去运行程序。

su命令可以让操作者在虚拟控制台切换当前用户账户,使用su的缺点之一是必须要先获取超级用户的密码。

核心知识②:Linux文件权限

Linux系统中的文件和目录通过使用访问许可权限,来确定谁能通过何种方式进行访问与操作。文件或目录的访问权限分为只读、只写和可执行3种。

确定了文件的访问权限后,用户能使用Linux系统自带的chmod命令来重新设定访问权限,也能利用chown命令来更改文件或目录的所有者,或者使用chgrp命令来更改文件或目录的用户组。

拓展知识:虚拟化

在计算机技术中,虚拟化(virtualization)是一种资源管理技术,将计算机的各种实体资源,如服务器、网络、内存及存储等,予以抽象、转换后呈现出来,供多个用户共享使用。

1.11 IE 9不支持占位符导致搜索行为异常

对于浏览器兼容性测试,一直都是Web测试中重要的一环,小蔡在测试产品中自然也不能漏掉。

由于小蔡测试的产品是面向普通用户的,所以小蔡选择进行测试的浏览器,也是开发团队选择优先支持的浏览器,是基于市场占有率最高的几款浏览器:Chrome、Firefox、Safari和IE。这些浏览器的版本也很多,如果全部支持也是不可能的,所以开发团队选择支持最新版本的Chrome、Firefox和Safari,以及IE 9~IE 11,还有IE EDGE。

Chrome和Safari都是基于WebKit核心的,所以差别不大。Firefox虽然基于Gecko,但是对于绝大多数Web标准协议都是支持的,所以和Chrome及Safari的差别也很小。IE因为使用的是微软自己的内核,所以和其他浏览器的差别会大不少,尤其是版本较早的IE 9~IE 11,不过微软在IE EDGE上已经开始兼容WebKit,并且兼容最新的Web标准协议,所以和其他浏览器的差别也不大了。

小蔡根据搜集到的这些浏览器差异的信息,决定兼容性测试的重点放在测试Chrome和IE 9两个浏览器上面。

由于产品是大型购物网站,所以用户需要使用搜索特定商品来查看商品详细信息。而在搜索框中,业务方希望推广一些畅销产品,所以使用了占位符(Placeholder)的方式,使用户在点击搜索框之前,在搜索框的搜索关键字部分,看到的是通过占位符设置的推广产品的信息。

小蔡在执行浏览器兼容性测试时发现,由于添加了占位符,导致Chrome和IE 9浏览器上搜索功能的行为不一致(见图1-22)。

图1-22 IE 9的占位符会被当作搜索关键字

▼在Chrome上当用户点击搜索框时,占位符会消失,用户输入的字符会被当作搜索关键字进行搜索。

▼在IE 9上当用户点击搜索框时,占位符并不会消失,用户输入的字符以及占位符的内容会一起被当作搜索关键字进行搜索。

这就导致用户在两个浏览器上使用相同的操作步骤进行搜索时的搜索结果不一致。想要解决这个问题,用户只有一个一个字符地删除IE 9浏览器中搜索框里的占位符,这对用户来说并不友好。

经过开发人员调查发现,这个问题的原因是IE 9浏览器本身就不支持占位符,所以对于占位符的操作也是有问题的。开发人员只好对IE 9上的搜索框单独处理,给搜索框先添加一组灰色的默认文字,来展示畅销商品,等用户点击搜索框时再清除这些字符。

小蔡庆幸IE 9是支持的最低版本的IE,如果需要再兼容IE 6~IE 8,那浏览器之间的差异更多更复杂,也会让开发和测试工作的难度加大不少。同时她决定定期查看用户的浏览器使用率和使用量,等大量用户不再使用IE 9时,就可以不用再做现在这种针对特定浏览器编写代码和测试某项功能了。

拓展知识:浏览器内核

排版引擎负责解析标记式内容(如HTML、XML、CSS、XSL及图像文件等),并将排版后的内容输出至显示器或打印机。Chromium/Chrome(iOS版除外)与Opera使用的浏览器引擎是基于Blink,是WebKit的一个分支,Firefox网页浏览器使用了Gecko网页浏览器引擎,Internet Explorer使用的是Trident网页浏览器引擎。

1.12 小结

本章我们介绍了由特定技术引起的功能测试问题,下一章我们会接触到由于测试覆盖导致的问题。