first commit
This commit is contained in:
@ -0,0 +1,137 @@
|
||||
|
||||
|
||||
因收到Google相关通知,网站将会择期关闭。相关通知内容
|
||||
|
||||
|
||||
13 效率为王:脚本与数据的解耦 + Page Object模型
|
||||
在上一篇文章中,我用Selenium 2.0实现了我们的第一个GUI自动化测试用例,在你感觉神奇的同时,是否也隐隐感到一丝丝的担忧呢?比如,测试脚本中既有测试数据又有测试操作,所有操作都集中在一个脚本中等等。
|
||||
|
||||
那么,今天我就通过介绍GUI测试中两个非常重要的概念:测试脚本和数据的解耦,以及页面对象(Page Object)模型,带你看看如何优化这个测试用例。
|
||||
|
||||
测试脚本和数据的解耦
|
||||
|
||||
我在前面的文章中,和你分享过GUI自动化测试适用的场景,它尤其适用于需要回归测试页面功能的场景。那么,你现在已经掌握了一些基本的GUI自动化测试用例的实现方法,是不是正摩拳擦掌准备批量开发GUI自动化脚本,把自己从简单、重复的GUI界面操作中解放出来呢?
|
||||
|
||||
但是,你很快就会发现,如果在测试脚本中硬编码(hardcode)测试数据的话,测试脚本灵活性会非常低。而且,对于那些具有相同页面操作,而只是测试输入数据不同的用例来说,就会存在大量重复的代码。
|
||||
|
||||
举个最简单的例子,上一篇文章中实现的百度搜索的测试用例,当时用例中搜索的关键词是“极客时间”,假设我们还需要测试搜索关键词是“极客邦”和“InfoQ”的场景,如果不做任何处理,那我们就可能需要将之前的代码复制3份,每份代码的主体完全一致,只是其中的搜索关键词和断言(Assert)的预期结果不同。
|
||||
|
||||
显然,这样的做法是低效的。
|
||||
|
||||
更糟糕的是,界面有任何的变更需要修改自动化脚本时,你之前复制出来的三个脚本都需要做相应的修改。比如,搜索输入框的名字发生了变化,你就需要修改所有脚本中findElement方法的by.name属性。
|
||||
|
||||
而这里只有三个脚本还好,如果有30个或者更多的脚本呢,你会发现脚本的维护成本实在是太高了。那么,这种情况应该怎么处理呢?
|
||||
|
||||
相信你现在已经想到了,把测试数据和测试脚本分离。也就是说测试脚本只有一份,其中需要输入数据的地方会用变量来代替,然后把测试输入数据单独放在一个文件中。这个存放测试输入数据的文件,通常是表格的形式,也就是最常见的CSV文件。
|
||||
|
||||
然后,在测试脚本中通过data provider去CSV文件中读取一行数据,赋值给相应的变量,执行测试用例。接着再去CSV文件中读取下一行数据,读取完所有的数据后,测试结束。CSV文件中有几行数据,测试用例就会被执行几次。具体流程如图1所示。
|
||||
|
||||
|
||||
|
||||
图1 数据驱动测试的基本概念
|
||||
|
||||
这也就是典型的数据驱动(Data-driven)测试了。
|
||||
|
||||
|
||||
数据驱动很好地解决了大量重复脚本的问题,实现了“测试脚本和数据的解耦”。 目前几乎所有成熟的自动化测试工具和框架,都支持数据驱动的测试,而且除了支持CSV这种最常见的数据源外,还支持xls文件、JSON文件,YAML文件,甚至还有直接以数据库中的表作为数据源的,比如QTP就支持以数据库中的表作为数据驱动的数据源。
|
||||
|
||||
数据驱动测试的数据文件中不仅可以包含测试输入数据,还可以包含测试验证结果数据,甚至可以包含测试逻辑分支的控制变量。 图1中的“Result_LoginSuccess_Flag”变量其实就是用户分支控制变量。
|
||||
|
||||
数据驱动测试的思想不仅适用于GUI测试,还可以用于API测试、接口测试、单元测试等。 所以,很多API测试工具(比如SoapUI),以及单元测试框架都支持数据驱动测试,它们往往都是通过Test Data Provider模块将外部测试数据源逐条“喂”给测试脚本。
|
||||
|
||||
|
||||
页面对象(Page Object)模型
|
||||
|
||||
为了让你了解“页面对象(Page Object)模型”这个概念的来龙去脉,并能够深入理解这个概念的核心思想,我会先从早期的GUI自动化测试开始讲起。
|
||||
|
||||
早期的GUI自动化测试脚本,无论是用开源的Selenium开发,还是用商用的QTP(Quick Test Professional,现在已经改名为Unified Functional Testing)开发,脚本通常是由一系列的页面控件的顺序操作组成的,如图2所示的伪代码展示了一个典型的早期GUI测试脚本的结构。
|
||||
|
||||
|
||||
|
||||
图2 早期的GUI测试脚本伪代码示例
|
||||
|
||||
我先来简单介绍一下这个脚本实现的功能。
|
||||
|
||||
|
||||
第1-4行,输入用户名和密码并点击“登录”按钮,登录完成后页面将跳转至新页面;
|
||||
第5行,在新页面找到“图书”链接,然后点击链接跳转至图书的页面;
|
||||
第7-10行,在图书搜索框输入需要查找的书名,点击“搜索”按钮,然后通过assert验证搜索结果;
|
||||
第11-12行,用户登出。
|
||||
|
||||
|
||||
看完这段伪代码,你是不是觉得脚本有点像操作级别的“流水账”,而且可读性也比较差,这主要体现在以下几个方面:
|
||||
|
||||
|
||||
脚本逻辑层次不够清晰,属于All-in-one的风格,既有页面元素的定位查找,又有对元素的操作。
|
||||
脚本的可读性差。 为了方便你理解,示例中的代码用了比较直观的findElementByName,你可以很方便地从name的取值,比如“username”和“password”,猜出脚本所执行的操作。-
|
||||
但在实际代码中,很多元素的定位都会采用Xpath、ID等方法,此时你就很难从代码中直观看出到底脚本在操作哪个控件了。也就是说代码的可读性会更差,带来的直接后果就是后期脚本的维护难度增大。-
|
||||
有些公司自动化测试脚本的开发和维护是两拨人,脚本开发并调试完以后,开发人员就会把脚本移交给自动化测试执行团队使用并维护,这种情况下脚本的可读性就至关重要了。但即使是同一拨人维护,一段时间后,当时的开发人员也会遗忘某些甚至是大部分的开发步骤。
|
||||
由于脚本的每一行都直接描述各个页面上的元素操作,你很难一眼看出脚本更高层的业务测试流程。 比如图2的业务测试流程其实就三大步:用户登录、搜索书籍和用户登出,但是通过阅读代码很难一下看出来。
|
||||
通用步骤会在大量测试脚本中重复出现。 脚本中的某些操作集合在业务上是属于通用步骤,比如上面伪代码的第1-4行完成的是用户登录操作,第11-12行完成的是用户的登出操作。
|
||||
|
||||
|
||||
这些通用的操作,会在其他测试用例的脚本中被多次重复。无论操作发生变动,还是页面控件的定位发生变化时,都需要同时修改大量的脚本。
|
||||
|
||||
其实,我上面说到的这四点正是早期GUI自动化测试的主要问题,这也是我一直说“开发几个GUI自动化测试玩玩会觉得很高效,但是当你开发成百上千个GUI自动化测试的时候,你会很痛苦”的本质含义。
|
||||
|
||||
那怎么解决这个问题呢?你可能已经想到了软件设计中模块化设计的思想。
|
||||
|
||||
没错,就是利用模块化思想,把一些通用的操作集合打包成一个个名字有意义的函数,然后GUI自动化脚本直接去调用这些操作函数来构成整个测试用例,这样GUI自动化测试脚本就从原本的“流水账”过渡到了“可重用脚本片段”。
|
||||
|
||||
如图3所示,就是利用了模块化思想的伪代码。
|
||||
|
||||
|
||||
|
||||
图3 基于模块化的GUI测试用例伪代码示例
|
||||
|
||||
第1-6行就是测试用例,非常简单直接,一眼就可以看出测试用例具体在执行什么操作,而各个操作函数的具体内部实现还是之前那些“流水账”。当然这里对于测试输入数据完全可以采用测试驱动方法,这里为了直观我就直接硬编码了测试示例数据。
|
||||
|
||||
实际工程应用中,第1-6行的测试用例和第8-30行的操作函数通常不会放在一个文件中,因为操作函数往往会被很多测试用例共享。这种模块化的设计思想,带来的好处包括:
|
||||
|
||||
|
||||
解决了脚本可读性差的问题,脚本的逻辑层次也更清晰了;
|
||||
|
||||
解决了通用步骤会在大量测试脚本中重复出现的问题, 现在操作函数可以被多个测试用例共享,当某个步骤的操作或者界面控件发生变化时,只要一次性修改相关的操作函数就可以了,而不需要去每个测试用例中逐个修改。
|
||||
|
||||
|
||||
但是,这样的设计并没有完全解决早期GUI自动化测试的主要问题,比如每个操作函数内部的脚本可读性问题依然存在,而且还引入了新的问题,即如何把控操作函数的粒度,以及如何衔接两个操作函数之间的页面。
|
||||
|
||||
关于这两个新引入的问题,我会在后面的文章中为你详细阐述。我先来跟你聊聊,怎么解决早期GUI自动化测试的“可读性差、难以维护”问题。
|
||||
|
||||
现在,操作函数的内部实现还只是停留在“既有页面元素的定位查找,又有对元素的操作”的阶段,当业务操作本身比较复杂或者需要跨多个页面时,“可读性差、难以维护”的问题就会暴露得更加明显了。
|
||||
|
||||
那么,有什么更好的办法来解决这个问题吗?答案就是,我要分享的GUI自动化测试的第二个概念:页面对象(Page Object)模型。
|
||||
|
||||
页面对象模型的核心理念是,以页面(Web Page 或者Native App Page)为单位来封装页面上的控件以及控件的部分操作。而测试用例,更确切地说是操作函数,基于页面封装对象来完成具体的界面操作,最典型的模式是“XXXPage.YYYComponent.ZZZOperation”。
|
||||
|
||||
基于这个思想,上述用例的伪代码可以进化成如图4所示的结构。这里,我只给出了login函数的伪代码,建议你按照这种思路,自己去实现一下search和logout的代码,这样可以帮你更好的体会页面对象模型带来的变化。
|
||||
|
||||
|
||||
|
||||
图4 基于页面对象模型的伪代码示例
|
||||
|
||||
通过这样的代码结构,你可以清楚地看到是在什么页面执行什么操作,代码的可读性以及可维护性大幅度提高,也可以更容易地将具体的测试步骤转换成测试脚本。
|
||||
|
||||
总结
|
||||
|
||||
今天我给你讲了什么是数据驱动的测试,让你明白了“测试脚本和数据解耦”的实现方式以及应用场景。接着从GUI自动化测试历史发展演变的角度引出了GUI测试中的“页面对象模型”的概念。
|
||||
|
||||
“测试脚本和数据解耦”的本质是实现了数据驱动的测试,让操作相同但是数据不同的测试可以通过同一套自动化测试脚本来实现,只是在每次测试执行时提供不同的测试输入数据。
|
||||
|
||||
“页面对象模型”的核心理念是,以页面为单位来封装页面上的控件以及控件的部分操作。而测试用例使用页面对象来完成具体的界面操作。
|
||||
|
||||
希望这篇文章,可以让你更清楚地认识GUI自动化测试用例的逻辑以及结构。同时,你可能已经发现,这篇文章的内容并不是局限在某个GUI自动化测试框架上,你可以把这些设计思想灵活地运用其他GUI自动化测试项目中,这也是我希望达到的“授人以鱼,不如授人以渔”。
|
||||
|
||||
思考题
|
||||
|
||||
我在文中有这样一段描述:页面对象模型的核心理念是,以页面为单位来封装页面上的控件以及控件的部分操作。但是,现在业界对“是否应该在页面对象模型中封装控件的操作”一直有不同的看法。
|
||||
|
||||
有些观点认为,可以在页面对象模型中封装页面控件的操作;而有些观点则认为,页面对象模型只封装控件,而操作应该再做一层额外的封装。
|
||||
|
||||
你更认同哪种观点呢,说说你的理由吧。
|
||||
|
||||
欢迎你给我留言。
|
||||
|
||||
|
||||
|
||||
|
@ -0,0 +1,116 @@
|
||||
|
||||
|
||||
因收到Google相关通知,网站将会择期关闭。相关通知内容
|
||||
|
||||
|
||||
16 脑洞大开:GUI测试还能这么玩(Page Code Gen + Data Gen + Headless)?
|
||||
在前面的几篇文章中,我介绍了GUI自动化测试的数据驱动测试、页面对象(Page Object)模型、业务流程封装,以及测试数据相关的内容。
|
||||
|
||||
今天这篇文章,我将从页面对象自动生成、GUI测试数据自动生成、无头浏览器三个方面展开,这也是GUI测试中三个比较有意思的知识点。
|
||||
|
||||
页面对象自动生成
|
||||
|
||||
在前面的文章中,我已经介绍过页面对象(Page Object)模型的概念。页面对象模型,是以Web页面为单位来封装页面上的控件以及控件的部分操作,而测试用例基于页面对象完成具体操作。最典型的模式就是:XXXPage.YYYComponent.ZZZOperation。
|
||||
|
||||
基于页面对象模型的伪代码示例,如图1所示。
|
||||
|
||||
|
||||
|
||||
图1 基于页面对象模型的伪代码示例
|
||||
|
||||
如果你在实际项目中已经用过页面对象模型,你会发现开发和维护页面对象的类(Page Class),是一件很耗费时间和体力的事儿。
|
||||
|
||||
|
||||
你需要打开页面,识别出可以唯一确定某元素的属性或者属性集合,然后把它们写到Page Class里,比如图1的第2行代码username_input=findElementByName(“username”),就是通过控件的名字(username)来定位元素的。
|
||||
更糟糕的是,GUI的页面会经常变动,如果开发人员开发前端代码时没有严格遵循可测试性的要求,Page Class的维护成本就会更高。
|
||||
|
||||
|
||||
那么,什么方法能够解决这个问题呢?答案就是,页面对象自动生成技术,它非常适用于需要维护大量页面对象的中大型GUI自动化测试项目。
|
||||
|
||||
页面对象自动生成技术,属于典型的“自动化你的自动化”的应用场景。它的基本思路是,你不用再手工维护Page Class了,只需要提供Web的URL,它就会自动帮你生成这个页面上所有控件的定位信息,并自动生成Page Class。
|
||||
|
||||
但是,需要注意的是,那些依赖于数据的动态页面对象也会被包含在自动生成的Page Class里,而这种动态页面对象通常不应该包含在Page Class里,所以,往往需要以手工的方式删除。
|
||||
|
||||
目前,很多商用自动化工具,比如UFT,已经支持页面对象自动生成功能了,同时还能够对Page Class进行版本管理。
|
||||
|
||||
但是,开源的自动化方案,页面对象自动生成功能一般需要自己开发,并且需要与你所用的自动化测试框架深度绑定。目前,中小企业很少有自己去实现这一功能的。
|
||||
|
||||
不过,有个好消息是,目前国内应用还不算多、免费的Katalon Studio,已经提供了类似的页面对象库管理功能,如果感兴趣的话,你可以去试用一下。
|
||||
|
||||
GUI测试数据自动生成
|
||||
|
||||
GUI测试数据自动生成,指的由机器自动生成测试用例的输入数据。
|
||||
|
||||
乍一听上去是不是感觉有点玄乎?机器不可能理解你的业务逻辑,怎么可能自动生成测试数据呢?
|
||||
|
||||
你的这个想法完全合理,并且也是完全正确的。所以,我在这里说的“测试数据自动生成”,仅仅局限于以下两种情况:
|
||||
|
||||
|
||||
根据GUI输入数据类型,以及对应的自定义规则库自动生成测试输入数据。 比如,GUI界面上有一个“书名”输入框,它的数据类型是string。-
|
||||
那么,基于数据类型就可以自动生成诸如 Null、SQL注入、超长字符串、非英语字符等测试数据。-
|
||||
同时,根据自定义规则库,还可以根据具体规则生成各种测试数据。这个自定义规则库里面的规则,往往反映了具体的业务逻辑。比如,对于“书名”,就会有书名不能大于多少个字符、一些典型的书名(比如,英文书名、中文书名等)等等业务方面的要求,那么就可以根据这些业务要求来生成测试数据。-
|
||||
根据自定义规则生成测试数据的核心思想,与安全扫描软件AppScan基于攻击规则库自动生成和执行安全测试的方式,有异曲同工之处。
|
||||
|
||||
对于需要组合多个测试输入数据的场景,测试数据自动生成可以自动完成多个测试数据的笛卡尔积组合,然后再以人工的方式剔除掉非法的数据组合。-
|
||||
但是,这种方式并不一定是最高效的。对于输入参数比较多,且数据之间合法组合比较少或者难以明确的情况,先自动化生成笛卡尔积组合,再删除非法组合,效率往往还不如人为组合来得高。所以,在这个场景下是否要用测试数据自动生成方法,还需要具体问题具体分析。-
|
||||
更常见的用法是,先手动选择部分输入数据进行笛卡尔积,并删除不合法的部分;然后,在此基础上,再人为添加更多业务上有意义的输入数据组合。-
|
||||
比如,输入数据有A、B、C、D、E、F六个参数,你可以先选取最典型的几个参数生成笛卡尔积,假设这里选取A、B和C;然后,在生成的笛卡尔积中删除业务上不合法的组合;最后,再结合D、E和F的一些典型取值,构成更多的测试输入数据组合。
|
||||
|
||||
|
||||
无头浏览器
|
||||
|
||||
无头浏览器,即Headless Browser,是一种没有界面的浏览器。
|
||||
|
||||
什么?浏览器没有界面,还叫什么浏览器啊?别急,我将为你一一道来。
|
||||
|
||||
无头浏览器,其实是一个特殊的浏览器,你可以把它简单地想象成是运行在内存中的浏览器。它拥有完整的浏览器内核,包括JavaScript解析引擎、渲染引擎等。
|
||||
|
||||
与普通浏览器最大的不同是,无头浏览器执行过程中看不到运行的界面,但是你依然可以用GUI测试框架的截图功能截取它执行中的页面。
|
||||
|
||||
无头浏览器的主要应用场景,包括GUI自动化测试、页面监控以及网络爬虫这三种。在GUI测试过程中,使用无头浏览器的好处主要体现在四个方面:
|
||||
|
||||
|
||||
测试执行速度更快。 相对于普通浏览器来说,无头浏览器无需加载CSS以及渲染页面,在测试用例的执行速度上有很大的优势。
|
||||
|
||||
减少对测试执行的干扰。 可以减少操作系统以及其他软件(比如杀毒软件等)不可预期的弹出框,对浏览器测试的干扰。
|
||||
|
||||
简化测试执行环境的搭建。 对于大量测试用例的执行而言,可以减少对大规模Selenium Grid集群的依赖,GUI测试可以直接运行在无界面的服务器上。
|
||||
|
||||
在单机环境实现测试的并发执行。 可以在单机上很方便地运行多个无头浏览器,实现测试用例的并发执行。
|
||||
|
||||
|
||||
但是,无头浏览器并不完美,它最大的缺点是,不能完全模拟真实的用户行为,而且由于没有实际完成页面的渲染,所以不太适用于需要对于页面布局进行验证的场景。同时,业界也一直缺乏理想的无头浏览器方案。
|
||||
|
||||
在Google发布Headless Chrome之前,PhantomJS是业界主流的无头浏览器解决方案。但是,这个项目的维护一直以来做得都不够好,已知未解决的缺陷数量多达1800多个,虽然支持主流的Webkit浏览器内核,但是依赖的Chrome版本太低。所以,无头浏览器一直难以在GUI自动化测试中大规模应用。
|
||||
|
||||
但好消息是,2017年Google发布了Headless Chrome,以及与之配套的Puppeteer框架,Puppeteer不仅支持最新版本的Chrome,而且得到Google官方的支持,这使得无头浏览器可以在实际项目中得到更好的应用。
|
||||
|
||||
也正是这个原因,PhantomJS的创建者Ariya Hidayat停止了它的后续维护,Headless Chrome成了无头浏览器的首选方案。
|
||||
|
||||
那什么是Puppeteer呢?Puppeteer是一个Node库,提供了高级别的API封装,这些API会通过Chrome DevTools Protocol与Headless Chrome的交互达到自动化操作的目的。
|
||||
|
||||
Puppeteer也是由Google开发的,所以它可以很好地支持Headless Chrome以及后续Chrome的版本更新。
|
||||
|
||||
如果你也迫不及待地想要尝试把Headless Chrome应用到自己的GUI测试中,那还等什么,赶紧下载并开始吧。
|
||||
|
||||
总结
|
||||
|
||||
我分别介绍了无头浏览器、页面对象自动生成,以及GUI测试数据自动生成,这三个GUI测试中比较有意思的知识点,包括它们的概念、应用场景等内容。
|
||||
|
||||
|
||||
对于页面对象自动生成,商用测试软件已经实现了这个功能。但是,如果你选择开源测试框架,就需要自己实现这个功能了。
|
||||
|
||||
GUI测试数据自动生成,主要是基于测试输入数据的类型以及对应的自定义规则库实现的,并且对于多个测试输入数据,可以基于笛卡尔积来自动组合出完整的测试用例集合。
|
||||
|
||||
对于无头浏览器,你可以把它简单地想象成运行在内存中的浏览器,它拥有完整的浏览器内核。与普通浏览器最大的不同是,它在执行过程中看不到运行的界面。目前,Headless Chrome结合Puppeteer是最先进的无头浏览器方案,如果感兴趣,你可以下载试用。
|
||||
|
||||
|
||||
思考题
|
||||
|
||||
在你的工作中,还有哪些好的方法和实践可以提高GUI自动化测试的效率吗?
|
||||
|
||||
欢迎你给我留言。
|
||||
|
||||
|
||||
|
||||
|
Reference in New Issue
Block a user