敏捷自动化测试

我们的测试为什么不够敏捷?

  测试是为了保证软件的质量,敏捷测试关键是保证可以持续、及时的对软件质量情况进行全面的反馈。由于在敏捷开发过程中每个迭代都会增加功能、修复缺陷或重构代码,所以在完成当前迭代新增特性测试工作的同时,还要通过回归测试来保证历史功能不受影响。为此我们期望:

  测试范围足够广:

  1、测试用例要覆盖所有功能;

  2、要在各种可能的环境下作兼容性测试;

  3、系统的稳定性、性能都要测试;

  测试频率足够高:

  1、每日构建产生的版本要保证可用;

  2、每个迭代都需要对历史功能做回归测试;

  3、释放前或上线后如果打了补丁,就需要回归;

  但实际情况往往不遂人愿:

  实际测试周期变短:

  1、上线时间提前确定,研发进度延期,测试计划被迫延后;

  2、最后阶段经常会临时增加测试任务;

  3、所有人都知道还需要再经过一轮测试,但时间没有了;

  有效测试资源稀缺:

  1、临时从其他项目抽调的测试人员不能立刻有效的开展测试工作;

  2、“搞不清楚”本项目的研发人员到底是不会做测试还是不愿做测试;

  因此由于客观上的资源和时间限制,完整的、及时回归测试在人工测试情况下,往往是不可能完成的任务。团队内部也会产生各种争执:

  1、提交给测试的版本为什么研发人员不先做通过性测试?

  2、这次代码改动量不大,有必要再花那么多时间回归么?

  3、当初不是承诺这次修改肯定不会影响以前的功能么?

  4、怎么不早说要支持Chrome浏览器,现在哪还有时间测试啊?

  5、怎么能让现场出现这种低级的Bug,打补丁后为什么不仔细回归一遍?

  争执越演越烈,最终有团队成员爆发了:“这简直不是人干的活!”。

  您怎么看待这句话呢?

  其实话糙理不糙,用更理性的语言翻译一下就是“有些工作不应该以人工的方式来完成”,比如:

  1、大量机械的、重复性的回归测试;

  2、结果的正确性不依赖主观判断的测试;

  3、需要模拟大量数据、大量并发量的测试;

  4、需要不间断执行的测试(比如7*24小时持续执行);

  5、需要短时间内完成的大量测试用例执行(比如完整的功能回归测试);

  通过自动化测试可以极大的提升回归测试、稳定性测试以及兼容性测试的工作效率,在保障产品质量和持续构建等方面起到举足轻重的作用。特别是在敏捷开发模式下,自动化测试是必不可少的。

  目前业界的商业/开源自动化测试工具有很多,比如,功能测试工具有QTP、Selenium等,性能测试有LoadRunner、JMeter等。其工作原理无非都是通过“测试脚本”和“测试数据”来完成“测试过程”,并比较“测试结果”,进而形成“测试报告”。

  本文不对这些测试工具的差异或优劣进行对比,只以作者目前采用的Selenium为例进行分析:敏捷开发模式需要自动化测试,但自动化测试本身的敏捷性又如何呢?

  Selenium是针对Web应用的开源自动化测试工具,通过编写模拟用户操作的脚本,它会打开浏览器对Web应用进行黑盒测试。可以方便的用于功能测试、兼容性测试、 稳定性测试及并发测试。目前已被主流浏览器厂商广泛支持,同时也是很多其它自动化测试工具(比如,RobotFramework)的底层核心技术。Selenium由IDE、Remote Control(简称RC)、WebDriver、Grid四个工程组成:

  1、Selenium IDE

  是一个用于录制/回放测试脚本的Firefox附加组件,录制的脚本可以生成基于Selenium RC的测试代码(Java、Ruby、C#等)。适用于快速入门,不太适用于实际较大的测试项目;

  2、Selenium RC

  RC由Server和Client组成两部分组成,Server负责加载/关闭浏览器以及作为HTTP代理来访问Web应用,Clinet支持多种编程语言和测试框架(TestNG、JUnit、NUnit等)。

  3、Selenium WebDriver

  WebDriver作为Selenium2的核心特性提供比RC更简洁易用的API,是官方推荐的RC替代方案。可以更好的支持动态网页,不需要再额外启动一个独立的Server。

  4、Selenium Grid

  是Selenium的一个扩展工具,可以很方便地同时在多台机器上和异构环境中并行运行多个RC或WebDriver用例。

敏捷自动化测试(2)

发表于:2015-02-28来源:uml.org.cn作者:不详点击数:13616 标签:

Selenium RC是通过在浏览器加载时注入JS函数来操纵后续的浏览器行为,Selenium WebDriver则通过直接调用各个浏览器内置的本地事件来达到这一目的。WebDriver目

  Selenium RC是通过在浏览器加载时“注入”JS函数来操纵后续的浏览器行为,Selenium WebDriver则通过直接调用各个浏览器内置的本地事件来达到这一目的。WebDriver目前已经作为W3C规范草案,提交由Google、Mozilla等浏览器厂商讨论。

  WebDriver规范定义一组与平台、语言无关的接口,包括发现和操作页面上的元素以及控制浏览器行为,主要用于支持Web应用的自动化测试。WebDriver的核心是通过findElement方法返回DOM对象(WebElement),通过WebElement可以对DOM对象进行操作(获取属性、触发事件等)。其中findElement方法需要的元素定位器(Locator)支持ID、XPath、CSS、超链接文本等多种方式。

  “WebDriver”顾名思义就是“Web浏览器驱动”,它专注于解决如何通过外部命令(通常为测试用例)操作浏览器的问题。至于测试用例按照什么顺序执行、执行过程中如何传递数据、测试结果如何断言、如何报告,则可以通过集成其它优秀的专业测试框架(比如,TestNG)来实现(WebDriver没有必要重复造轮子)。

  下面用以“用户管理”为例,来看看用WebDriver实现的“增加”和“删除”测试脚本(只示意部分关键代码)。

  1、在用户列表页面点击“新增”按钮,跳转到新增用户页面:

webDriver.findElement(By.xpath("//a[contains(@id,'addUserBtn')]//button")).click();.

  脚本解读:

  1、By.xpath()表示通过XPath来定位页面元素;

  2、click()表示在找到的当前控件上执行点击操作;

  2、在新增用户页面,输入“帐号”、“密码”、“姓名”,选择“性别”、“生日”和“国籍”,然后点击“保存”按钮,回到用户列表页面,并判断是否增加成功:

String account="autotest2";
2)  webDriver.findElement(By.xpath("//div[contains(@id,'account_userForm')]//input")).sendKeys(account);
3)  webDriver.findElement(By.xpath("//div[contains(@id,'password_userForm')]//input")).sendKeys("1");
4)  webDriver.findElement(By.xpath("//div[contains(@id,'name_userForm')]//input")).sendKeys(account);
5)  webDriver.findElement(By.xpath("//div[contains(@id,'sex_userForm')]//input")).click();
6)  webDriver.findElement(By.xpath("//span[text()='女']")).click();  
7)  webDriver.findElement(By.xpath("//div[contains(@id,'birthdate_userForm')]//input")).click();
8)  webDriver.findElement(By.xpath("//div[contains(@id,'nationality_userForm')]//input")).click();
9)  webDriver.findElement(By.xpath("//span[text()='中国']")).click();     
10) webDriver.findElement(By.xpath("//a[contains(@id,'userSaveBtn')]//button")).click();        
11) WebElement ele = webDriver.findElement(By.xpath("//div[text()='"+account+"']"));    
12) Assert.assertNotNull(ele);

  脚本解读:

  1)sendKeys ()表示在找到的当前控件上输入字符;

  2)2~9行表示通过输入或点击选择的方式为用户相关属性赋值;

  3)第10行表示点击“保存”按钮(点击后会自动转向用户列表页面);

  4)11~12行表示查找页面上文本内容为新增帐号的div,并断言该div是存在的(不为空);

  3、删除刚刚增加的人员,然后判断是否删除成功:

webDriver.findElement(By.xpath("//a[contains(@id,'deleteUserBtn')]//button")).click();
2)  WebElement ele = webDriver.findElement(By.xpath("//div[text()='"+account+"']"));
3)  Assert.assertNull(ele);

原文转自:http://www.uml.org.cn/Test/201307154.asp

 脚本解读:

  1)第1行表示点击“删除”按钮;

  2)2~3行表示查找页面上文本内容为新增帐号的div,并断言该div已经不存在了(为空);

  通过上面的脚本就可以实现“用户增加、删除”的自动化测试,并且可以跨浏览器。看到这里您会不会觉得整体还不错,如果测试脚本再能通过录制的方式自动生成就更好了!

  “看”起来确实还不错,但在实际项目中用起来就没那么爽了。这其实是在技术/工具选型时普遍存在的现象:在验证/试用阶段的评价很高,但在投入生产使用时会遇到各种各样的问题,因此大家在选型阶段除了考虑功能,还要考虑技术/工具本身的开放性和可扩展性。

  上面的方案单纯从技术的角度来讲是很不错的:开源、社区活跃、标准化程度高、支持跨浏览器、脚本回放稳定、可集成性高,等等。

  但是如果就这样应用在实际项目中,会从过程的角度暴露一些棘手的问题:

  测试脚本是“代码级”的,那么应该由谁来编写呢?

  测试人员和开发人员好像都有编写这些测试脚本义务,但似乎又都有不写的理由。

  测试脚本必须不断的修改才能在最新版本上跑通,谁该为此买单?

  在敏捷开发过程中需要快速响应需求的变化(新增、变更),这一点整个团队都好理解。因此如果需求发生变化,开发人员修改代码、测试人员修改测试脚本,一切顺理成章,大家相安无事。但是在用户需求没有变化时,开发人员频繁修改代码的情况也很常见:比如,修改Bug、针对“坏味道”做重构、调整页面布局或样式。于是在“毫无征兆”的情况下,测试脚本又无法执行了!

  这时候测试人员可能会质问开发人员:“做之前怎么不想清楚?都已经测试完成的功能,为什么还总是反复修改?什么时候代码才能稳定?”。

  而开发人员此时也会非常理直气壮:“有Bug能不改么?页面布局不合理导致用户体验差,能不改么?而且敏捷中的一个重要的实践就是重构啊”。

  大家又是似乎都有道理、也都有苦衷。我虽然作为测试人员,但是在这个问题上还是“偏向于”开发人员的: 在软件生命周期的各个阶段(需求、设计、开发、测试)中,后面的阶段对前置阶段是有一定依赖的,所以越往后期响应变化的难度越大。比如,在“开发”环节不仅需要响应“需求”的变化(新增、变更),而且需要响应“设计”的变化。从这个角度来看,“测试”本就应该响应“开发”的变化。

  对于在实际项目自动化测试过程中遇到的上述问题,归根到底是因为“自动化测试方案本身的敏捷程度不够”,主要体现在如下三个方面:

  1、 学习成本高

  测试人员除了要掌握WebDriver接口之外,还要掌握XPath、TestNG的用法,甚至还需要对功能的前台实现有一定了解。

  2、 脚本维护困难

  敏锐的开发/测试人员从上面的示例脚本中,可以马上嗅出一些“坏味道(Bad Smell)”: 代码相似度非常高、可能变化的数据被硬编码在测试代码里、代码可读性差、测试代码与页面源码耦合度大,等等。这些坏味道的出现,通常意味着需要进行重构,否则会愈演愈烈,最终变得尾大不掉。

  【注】业界常见的测试工具本质上还是针对页面源码来编写(或生成)测试脚本的,即使提供了录制工具,此类脚本的可读性和后期可维护性还是非常差的。

  3、 断言条件繁琐

  业界常见的测试工具即使提供录制脚本的功能,但是对于“断言”还是需要人工插入的(工具做不到智能的判断我们想要在哪里设置断言),于是断言就成了自动化测试人员的“噩梦”。

  断言对象可能很“多”,页面的信息量往往很大,需要在测试脚本中为每个断言对象(比如,页面某个文本框的值)补充断言语句。

  预期结果是可能“变”化的,甚至是动态的,因此预期结果的值如果与脚本逻辑耦合在一起,将来极难维护。 断言机制比较“呆”板,对于 未设为断言对象的字段,如果发生错误也是无法感知的,并且难以对于UI样式或UI逻辑(比如,翻页图标应该灰显)进行断言。

  换个角度可以理解为,如果这样的断言条件“多”的话,整个测试用例集会“变”的非常“呆”板!

想要有效的改善这些问题,就必须让自动化测试变得“敏捷”起来!

  像用户使用软件一样享受自动化测试

  近几年,Web技术发展势头迅猛,浏览器市场群雄争霸、各种UI 组件库也如雨后春笋。现在互联网上已经很少有仅支持一种浏览器,并且不基于任何可复用的UI组件库进行开发的应用了。开发人员基于各种优秀的UI组件库(如,JQuery、Dojo、ExtJS)可以很容易的开发出功能强大、展现绚丽、兼容各种浏览器的页面(如下图):

  UI组件库的广泛采用在提高开发效率的同时,也极大的提升了用户体验。基于UI组件库之所以能快速开发出功能强大的页面,是因为UI组件库可以自动生成海量、结构类似的HTML源码(如下图):

  开发人员是幸福的,因为这一切对于他来说完全透明。于是只剩下自动化测试人员独自面对这样“恐怖”的页面源码。

  如前文“我们的测试为什么不够敏捷”中所言,业界常见测试工具的脚本本质上还是针对页面源码的,因此原本就举步维艰的自动化测试在开发使用UI组件库之后变得雪上加霜:

  1、页面DOM结构非常复杂

  导致所录制/编写脚本的复杂度变的更大、可读性变得更差。

  2、UI框架的升级很可能会导致DOM结构的变化

  因此即使开发人员没对代码做任何改动,测试脚本也会因为UI框架的升级变得无法回放。

  3、控件ID是自动生成的,甚至在每次刷新页面后都会变化

  大部分自动化测试工具在“录制”脚本时,都会优先使用ID定位策略,自动生成的ID会导致这种关键的控件定位策略变得无效。

  4、UI框架在各种浏览器下自动生成页面源码可能不完全相同

  为了在不同浏览器下“看起来一样”,实际的DOM结构有时也可能是不同的,因此所录制脚本的浏览器兼容性会比较差。

  技术的发展是为了让生活变得越来越轻松。从用户的角度来看确实如此:Web应用的功能覆盖范围越来越广、操作越来越方便、界面越来越美观。

  为什么自动化测试人员没有感觉工作变得轻松呢?

  要回答这个问题,首先要分析“用户使用软件”与“自动化测试软件”之间的一些重要差异:

  1)用户使用软件时只关注界面上能“看”到的,而不用总是“查看页面源码”;

  2)用户会更关注整体业务的正确性、稳定性,而不仅仅是每个孤立页面功能的正确性;

  3)用户对页面样式、响应时间、浏览器兼容性要求越来越高; 如果我们能像用户使用软件一样进行自动化测试,测试就会变得更敏捷:

  4)根据界面快速编写测试脚本:敏捷应对需求的变化;

  5)降低对技术实现(UI框架、页面样式/布局)的依赖:敏捷应对设计/开发的变化;

  6)测试脚本可以稳定支持各种浏览器:敏捷应对环境的变化;

  (一)根据界面快速编写测试脚本的实现思路

  还是以前文“我们的测试为什么不够敏捷”中用到的“用户增加”界面为例:

  如果你是作为用户在使用上述功能时,心里一定也会默念下面内容吧:

  “账号”输入***、“密码”输入***、“姓名”输入***、“性别”选择***、“生日”输入***、“国籍”选择***,点击“保存”按钮。

  这就可以理解为“根据界面编写的测试脚本”。

  我们在使用Web应用时,心里常常默念的还包括:“展开/收拢/选中”树(Tree)的某个节点、数据表格(Grid)的下一页/上一页、选中数据表格(Grid)的某一行、点击/关闭某个Tab页,等等。

  很多人在与笔者交流的过程中都会问“为什么还要手工编写脚本,怎么不提供脚本录制工具呢?”

  因此在这里想澄清一下大家对“编写”脚本的误解,与传统自动化测试工具相比,“此脚本”非“彼脚本”。

  传统的测试脚本是给特定测试工具执行时使用的,对人类而言可读性极差,更别谈手工编写了,因此“录制/回放”工具往往都是必备组件。即便如此,此类“录制/回放”工具的实际使用效果,也是不尽如人意的。

 从这种角度来看,“录制”脚本是“下策”,“上策”应该是让测试脚本大幅简化,并且具备良好的可读性和可维护性,以达到人工编写很容易的程度。

  ——以上只是笔者的一点差异化见解,对业界优秀的测试工具没有任何冒犯之意!

  (二)降低测试脚本对技术实现依赖度的实现思路

  Web页面开发中的技术实现细节主要包括UI框架的选择及版本升级、页面样式设计、页面布局设计,因此我们的主要目标就是降低测试脚本对这些因素的依赖。

  为了便于测试脚本的解析和组织管理,目前还不能直接写形如“点击(保存)按钮”这样的纯自然语言,但可以设计成接近自然语言的领域专用语言(DSL:Domain Specific Language),本文采用XML来实现这种DSL。比如:

<event id="[button]保存"  name="click"/>

  这种超级清晰、简短的测试脚本与实际海量的页面源码之间有一条鸿沟,我们可以通过“封装”来屏蔽。下面以ExtJS的Button控件为例来示意如何完成这种封装:

  Button的界面展现如下:

  实际生成的页面源码DOM结构如下(省略部分非关键属性):

<div id="button-1032" class="x-btn x-box-item x-btn-default-small 
        x-noicon x-btn-noicon >
    <em id="button-1032-btnWrap">
        <button id="button-1032-btnEl" class="x-btn-center"
                 autocomplete="off" role="button">
            <span id="button-1032-btnInnerEl">Cancel
            <span id="button-1032-btnIconEl" class="x-btn-icon "/>
        </button>
    </em>
</div>

  我们封装所要做的主要工作就是解析出测试脚本中定义的关键信息(button、保存、click),结合对应页面源码的DOM结构,翻译成W3C标准的定位方式(如,XPath)。通过XPath就可以定位到页面上的任何控件。

  对于测试脚本(),翻译后的XPath可以是(//button/span[text()='Cancel'])。

  同理,其它测试脚本还可以包括:

  树节点展开/关闭

<event id="[treeNode]部门" name="open"/>
<event id="[treeNode]部门" name="close"/>

  Grid翻页

<event id="[grid]人员列表" name="nextPage"/>

  它们的封装实现比Button稍微复杂一些,但原理是一样的。

  (三)增强测试脚本浏览器兼容性的实现思路

  这个就比较简单了,只要选用标准化程度高、厂商支持度高、扩展性强的第三方底层实现即可,笔者推荐采用Selenium WebDriver。

  通过上面的介绍,有些读者或许会有个疑问:“如果一个Web应用没有采用任何UI框架、而且编码又极为不规范,那么你这种方案岂不就不适用了?”

  答案是肯定的。但笔者认为此类Web应用如果想要发展下去,其瓶颈还停留在开发环节,对其进行自动化回归测试的投入产出比不是很大。

  此外,为了进一步提高Web应用的可测试性,笔者在实际工作中总结了一些前台页面开发的最佳实践,供大家参考:

  1、页面采用单帧实现

  如果页面上的frame/iframe嵌套很多的话,控件的定位会比较麻烦,并且影响展现速度。

  2、兼容Firefox

  Firefox有很多强大插件,如Firebug、FirePath,非常有助于在开发、测试过程中的调试问题。

3、采用统一的UI框架开发复杂控件

  对于复杂控件,比如Tree、Grid,如果不基于统一的UI框架实现,开发、测试工作量都会很大。

  4、对于需要设置ID的控件,一定要设置唯一、并且有业务意义的ID

  当然对于一般不需要设置ID的控件(如,div)或者控件ID由UI框架自动生成的情况除外。

  5、对于Form中最常见的label+input组合,尽量让label和input有逻辑对应关系

  推荐为label设置for属性,属性值等于input的id,这样对最终用户也比较友好:双击label的时候,鼠标焦点能定位到对应的input中。

  6、页面设计时考虑用户体验,往往用户使用方便的时候,自动化测试也会方便

  比如,一个页面上不要有多个同名的按钮、通过点击上下微调按钮(箭头)改变输入域值的控件也支持直接输入值、日期选择控件支持用户直接输入、下拉列表控件支持输入过滤,等等

  对以上几点最佳实践有如下补充说明:

  1、这些最佳实践不仅能提高Web UI的可测试性,也非常有助于提升用户体验;

  2、这些最佳实践如果能满足更好,即使不能全部满足,也可以开展自动化测试;

  按照本文给出的方案,前文“我们的测试为什么不够敏捷”中用到的“用户管理(增加、删除)”功能的自动化测试脚本就可以改造为如下样式(示意代码):

<event id="[button]新增" name="click"/>
<event id="[input]帐号" name="setValue" value="${account}"/>
<event id="[input]密码" name="setValue" value="1"/>
<event id="[input]姓名" name="setValue" value="${accountName}"/>
<event id="[input]性别" name="select" value="女"/>
<event id="[input]生日" name="setDate" value="1980-01-01"/>
<event id="[input ]国籍" name="select" value="中国"/>
<event id="[button]保存" name="click"/>
<event id="[gridRow]${account}" name="assertExist"/>
<event id="[gridRow]${account}" name="check"/>
<event id="[button]删除" name="click"/>
<event id="[ gridRow]${ account }" name="assertNotExist"/>

  以上测试脚本完全基于界面上“可见”的内容进行编写,大家应该不需要看下面的解读,也能明白脚本的意思:

  1)第1、8、11行表示点击“新增”、“保存”、“删除”按钮;

  2)第2~7行表示为输入域赋值,赋值方式有输入文本、输入日期、选择下拉选项;

  3)第10行表示选中表格中的指定行,即,选中指定行前面的Radio按钮;

  4)第9、12行表示断言表格中指定的行“存在”或“不存在”;

  5)${ account }表示使用外部的变量account;

  让断言不再成为自动化测试的负担

  在本系列的第一篇文章“我们的测试为什么不够敏捷”中,根据实例总结出敏捷自动化测试的两大阻碍:“脚本维护困难”、“断言条件繁琐”。本文针对在不失自动化测试有效性的前提下如何降低断言成本来分享一些实践经验。

  目前业界常见的自动化测试工具在断言方面大多都是采用“指哪儿打哪儿”的细粒度模式,即,在自动化测试过程中只能对设置为断言条件的字段(页面元素)进行断言。而且这些测试工具即使提供录制脚本的功能,但对于断言往往还需要测试人员手工补充插入。

  除了补充、维护断言条件的工作量大之外,这种断言模式还存在一些明显的不足,下面结合一个实际的例子(如下图)进行分析:

  无法感知未设为断言对象的字段上发生的错误

  以上图为例,如果在“增加用户”的测试脚本之后只对列表中的“用户姓名是否存在” 进行断言,那么就可能遗漏“入职日期没有提交成功”的错误。而且由于页面的信息量往往很大,我们是不可能对所有字段都设置为断言条件的。

 难以对于UI样式或UI逻辑进行断言

  以上图为例,有一个UI样式类的缺陷(左侧菜单树的根节点 “console”下面多出来一条虚 线)和一个UI逻辑类的缺陷(右侧用户列表只有一页,但“下一页”和“最后一页”图标依然是可以点击的,即没有灰显)。此类缺陷即使对于经验丰富、心思缜密的测试人员,在人工测试时也是很可能发现不了的,并且在自动化测试过程中也很难进行断言。

  即使存在上述问题,测试脚本中是否有充分的断言,依然是评判自动化测试有效性的一个重要指标。但实施过自动化测试的人应该都会有这样的体会:“大部分断言在大部分情况下只是佐证软件是运行正常的”。

  当然,所有人都应该是非常期待看到这样的结果,毕竟谁也不希望每次回归测试时都是用例大面积不通过。只是辛辛苦苦写这些断言语句的测试人员心里未免有些“小遗憾”。

  本系列上篇文章中谈到“很多人一提到自动化测试脚本,马上就想到需要提供录制工具”,但如果换个角度思考,很可能就是“柳暗花明又一村”。

  在这里,我们同样换个角度思考,假设我们的自动化测试主要目标是为了证明软件运行正常,那么我们会怎么做?

  笔者这边的一个经验就是“按照完整的业务流程来组织测试用例,只对少量、必要的关键点进行断言”。以“租户对虚拟化资源的申请使用”为例,来具体看看测试用例的组织方式:

  1)新租户注册;

  2)管理员登录系统,对注册租户进行审批,然后退出系统;

  3)审批后的租户登录系统;

  4)租户申请所需要的虚拟化资源(比如,40G硬盘、2核CPU、2G内存),然后退出系统;

  5)管理员登录系统,对租户申请的资源进行审批,然后退出系统;

  6)租户登录系统,在已申请资源的基础上创建安装指定操作系统的虚拟机;

  7)断言虚拟机是否创建成功;

  8)租户退出系统;

  9)管理员登录系统,删除租户;

  10)断言租户之前申请的资源是否被完全释放;

  11)租户再次登录系统,断言是否无法登录;

  上述测试用例就是按照完整的业务流程进行组织,并且只对少量关键点(7、10、11)进行断言,如果整个用例可以运行通过,就能证明这个业务是没有问题的。

  另外还有一个值得考虑的现象,就是相对于自动化测试而言,一个优秀的测试人员在人工测试时是如何判断功能正确与否的呢?他不会死板的只盯着某几个输入域的值,他一定还会同时关注页面上所有数据的正确性、会更加关注业务流程是否正确、会更敏锐的发现页面样式或UI逻辑类的缺陷。

  为了兼顾“证明软件正常运行”和“人性化的识别软件缺陷”,一个优秀的测试工具应该考虑提供以下多种断言机制。

  一、 控件级细粒度断言

  即前面提到的最常见的断言方式。在测试过程中,可以在任何位置增加断言脚本,来判断页面指定控件是否存在、控件显示值是否为预期结果等。通常建议只对关键校验点进行断言。

  二、 页面级粗粒度断言

  通过对比前(之前测试通过)后(后续持续发布)版本在测试用例路径和输入参数相同的情 况下,整个页面内容(包括截图和数据)是否严格相同来做粗粒度断言。

  通过页面截图进行断言有两个实现要点:首先要选择一个合适的截图方案(笔者推荐采用Selenium WebDriver提供的TakesScreenshot接口);其次需要提供图片对比工具,以便测试人员可以一眼看出两个版本页面截图的差异。

  下面是笔者在测试框架中实现的截图自动化对比功能的实际效果。下图中左侧部分是“实际结果截图”、右侧是“预期结果截图”、中间部分是差异对比,测试人员一眼便可看出其中的Bug:“表格行选中的翻页缓存(在当前页选中几条记录,翻到下一页再翻回本页,需要保持之前的行选中状态)功能丢失了”。

  通过页面数据进行断言的实现方式相对简单一些,首先要提取页面上所有的数据(或文本),接着进行格式化,然后再自动化对比。 “页面级粗粒度断言”的特点及应用场景如下:

 1、无需编写任何断言语句;

  2、需要能够提供可用于自动对比的历史正确版本,特别适用于可以持续构建的项目;

  3、能判断出UI样式和UI逻辑类的错误;

  4、由于对比绝对精准,导致可能存在误判,因此需要人工对差异图片进行排查; 【注】由于很多Web页面都有渐入渐出、点击时按钮变色等很炫的效果,所以在两次截图的瞬间可能页面的动态样式是不一样的,这就是所谓的“误判”。笔者对于一个“动态样式”适中的项目采用这种断言方式,统计结果表明误判率在20%左右。

  5、鉴于回归测试的时候,通常大部分用例应该是可以通过的,所以“页面级粗粒度断言”的投入产出比非常占优势!

  三、 基于业务逻辑断言

  在测试设计时把有依赖关系的用例一起执行,如果某个步骤出现问题,即便不设置任何断言语句,在当前步骤或后续步骤的测试用例也会执行失败。下面以“增加、查询、修改、删除”这个最典型的流程来说明(如下图所示)。

  假定在“增加”环节出现问题,那么我们的测试用例执行情况可能出现如下几种结果:

  1、如果在“增加记录A”的用例中包含对是否增加成功的断言,那么测试用例从“增加记录A”开始出错;

  2、如果在“增加记录A”中不包含断言,而是在“查询A”的用例中包含是否有查询结果的断言,那么测试用例会从“查询A”开始出错;

  3、如果在“查询A”中也不包含断言,那么测试用例会从“修改查询结果”开始出错。

  所谓“基于业务逻辑断言”,就是指上述第三种情况,其特点及应用场景如下:

  1、无需编写任何断言语句,但测试设计要考虑业务逻辑顺序;

  2、与“页面级粗粒度断言”相比,不需要提供可用于对比的历史正确版本,通常适用于项目刚开发或样式做整体调整等情况;

  3、断言错误的位置不精准,可能延后;

  4、执行过程每一步都做截图备份(通过Selenium WebDriver可以很方便的实现),可以非常有效的辅助定位准确的出错原因;

  5、鉴于回归测试的时候,通常大部分用例应该是可以通过的,所以“基于业务逻辑断言”的投入产出比非常占优势!

  四、 自定义扩展断言

  在人工测试时经常有些操作结果的正确与否在当前页面无法做出判断,需要到其它页面甚至系统外部(比如,数据库、输出日志)获取信息来做出判断。以最常见的“基于数据库进行断言”为例,测试工具需要支持把断言时用到“预期结果”和“实际结果”配置为对应的SQL语句。

  以上介绍了从测试工具的角度可以提供的多种断言机制,在自动化测试过程中应该根据项目实际情况,考虑采用上述多种断言的组合,以弥补控件级细粒度断言的不足。

  本系列文章至此,已经分享了如何降低测试脚本的编写、维护成本,如何在不失测试有效性的前提下减少断言成本。改善上述两大问题之后,自动化测试基本上就可以比较顺利的开展了。接下来如何让自动化测试的价值最大化呢?答案就是频繁的执行测试脚本。因此下一步要做的就是持续集成(或者称为每日构建)。下一篇文章会分享一个由测试团队主导的持续集成案例,敬请关注!

Posted in 介绍及安装说明.