谈及开源Web 自动化测试工具,相信很多人立刻会想到Selenium。本文给大家介绍的是另一款 开源Web 自动化测试工具Sahi。Sahi的网站上有关于与Selenium的对比,不过这不是我们今天探讨的主题。这篇文章的主要目的是向读者简单的介绍一下Sahi并分享一下个人使用Sahi测试Dojo应用的经验,希望对大家能有所帮助。
将Node.js用于数据密集型实时应用(DIRT)
缓存、NoSQL和网格计算——银行能教给我们什么?
Visual Studio 11 Beta 和 .NET Framework 4.5 Beta版免费下载中!
在开始介绍Sahi之前,我们一起来看看在开发Web 自动化测试(特指Web 2.0应用)时常面临的两大技术问题。
根据个人经验,以下几点会给页面元素的识别带来障碍:
通常的解决方案:
那我们来看看Sahi关于元素识别的策略:
因此,Sahi基本上能够较好地解决前面提到的三大关于元素识别的障碍。
通常Web 2.0应用中有很多AJAX的应用。由于请求响应的返回是异步的,自动化测试程序如何决定是否可以继续下一个操作或者是开始验证呢?如果下一步操作在AJAX请求响应还没有返回时就执行了,毫无疑问会导致测试用例的失败,并且是误判。
通常的做法是:
Sahi能够判断AJAX请求是否已经处理完毕,然后继续下一步操作,这一点对用户是“隐式”的,也就是说用户不需要写任何代码。事实是,绝大多数情况下用户确实不需要自己写代码处理页面等待的问题,但是,有时应用的某个功能是执行多个AJAX请求完成的(例如,长时间操作的进度条显示),此时Sahi便无法胜任。这种情况下,用户只能利用Sahi提供的等待固定时间以及基于条件等待的API自己编写代码实现页面等待。
图1.Sahi架构图
Web自动化测试的本质就是模拟用户事件(单击、双击、输入文本等操作)获取结果状态并验证是否符合预期。如上图所示,Sahi的核心一个用Java编写的代理服务器。它位于Web应用与浏览器当中。当HTTP请求响应通过Sahi代理服务器时,便被注入了用来回放测试用例的Javascript。这些Javascript中,一部分是Sahi本身用来驱动脚本运行的代码,另一部分是用户代码被Sahi代理服务器解析成的Javascript。目前Sahi支持三种编程语言:Sahi脚本, Java和Ruby。
运行<SAHI_HOME>/bin/dashboard.sh可以启动Sahi的Dashboard窗口。Dashboard窗口中显示了所有Sahi预配置并且用户系统上存在的浏览器。如果需要手工添加新的浏览器,可以点击下方的Configure修改浏览器配置文件。
图2.Sahi Dashboard
点击浏览器图标,会弹出相应的浏览器窗口(此时Sahi已经自动给浏览器配置了Sahi代理服务器)。
图3.Sahi初始页面
在浏览器窗口中按住ALT键并双击鼠标左键,就会弹出Sahi控制器窗口。(通常这只在IE中工作,在Firefox和Chrome中你需要按住ALT+CTRL)。Sahi控制器可以工作在所有Sahi支持的浏览器上。录制和回放是Sahi控制器窗口中最重要的两个标签页。
录制标签页
图4.Sahi控制器 – 录制标签页
输入文件路径后点“录制”便开始录制,点“停”即停止录制,非常简单。标签页的中部是一个对象识别器,在页面上按住CTRL键,并将鼠标左键悬停在某个元素上,对象识别器就显示出能够识别该元素的Sahi语句。另外,你可以在下方的输入框中直接输入Sahi语句并查看运行结果。
回放标签页
图5.Sahi控制器 – 回放标签页
回放标签页不仅能够一次性运行脚本,还可以单步运行,甚至可以中途暂停,这给调试代码带了很大便利。点击下方的链接可以查看解析后的脚本以及运行日志等。
Sahi脚本基于Javascript,不同的是Sahi脚本中所有的变量必须带有$前缀。Sahi代理服务器负责将用户编写的Sahi脚本解析成Javascript并在Rhino引擎中执行(Rhino是一个开源的使Javascript运行于JVM的项目)。所以,Sahi脚本能够执行文件甚至数据库操作也就不足为怪了。Sahi脚本定义.sah文件中,但是所有直接访问DOM节点的函数必须定义在browser tag中。
下面与大家分享一些我个人使用Sahi测试Dojo应用的经验。为了使示例代码能够被读者方便地运行,选取http://demos.dojotoolkit.org/demos/form/demo.html 假设为我们将要测试的应用。这是一个用来演示Dojo表单Widget的页面。
<SAHI_HOME>/userdata/scripts/sahidojodemo/appobjs/JobAppFormPage.sah <SAHI_HOME>/userdata/scripts/sahidojodemo/tasks/JobAppFormTasks.sah <SAHI_HOME>/userdata/scripts/sahidojodemo/testcases/JobAppFormTests.sah <SAHI_HOME>/userdata/scripts/sahidojodemo/testcases/myapp.suite <SAHI_HOME>/userdata/scripts/sahidojodemo/testcases/testdata.csv <SAHI_HOME>/userdata/scripts/sahidojodemo/core.sah <SAHI_HOME>/userdata/scripts/sahidojodemo/run.sh
接下来介绍一下我用Sahi测试Dojo应用是遵循的几个原则。
面向对象早已不是什么新鲜事物。其实,UI自动化测试程序直觉上来讲可以采用过程式的编程模式,因为它本身就是将很多行为串接起来。为什么在测试Dojo应用时要使用面向对象的理念?原因很简单,因为Dojo Widget本身就采用了面向对象的思想。因此,在我的自动化测试框架中,每个Dojo Widget都对应于一个Javascript的“类”,不仅封装了DOM结构而且更便于代码重用。
从事Web自动化测试的读者恐怕对IBM框架不会感到陌生。IBM框架由三层组成:应用对象、任务和测试用例。潜在于应用对象、任务和测试用例包之下的基本原理是:
下面是对应用对象、任务和测试用例的解释说明:
应用对象:储存有关你的应用程序中的GUI元素信息。同时在这里也可以编写你的Getter 方法,这些 Getter 方法可以返回对象,使 调用者能够对这些GUI元素进行查询和操作。一般情况下,这些方法在Task层中进行调用。
任务:在这里你将编写可重用的方法,这些方法在你的应用程序中执行通用功能。同时在这里,你将编写可以处理和查询复杂的特定应用程序控件的方法。在任务中的方法可以被测试用例调用。
测试用例:导航一个应用程序,验证其状态,并记录其结果的方法。
通常页面上每个元素都会有一个label并且它是“可见”的。所谓“可见”,是指label的值是不需要借助于工具直接能看到的。例如id、name等,必须通过查看源码或者一定的工具,如Firebug查看其属性值。因此通过借助label的元素识别方法可以提高开发效率(因为你不在需要去用工具查看元素属性值了)。
三个目录appobjs,tasks以及testcases即是IBM框架中的三层架构,其中的JobAppFormPage.sah,JobAppFormTasks.sah以及JobAppFormTests.sah分别应用对象、任务和测试用例程序文件(Sahi脚本)。
下面以DojoWidget和Textbox两个类为例讲解Widget的封装。
function DojoWidget($self) { this.getLabel = function () { var $widId = getAttribute($self, "widgetid") _set($labelText, getLabelTextByFor($widId)) return $labelText } this.hasError = function () { var $class = getAttribute($self, "class") return $class.indexOf("dijitError") == -1 ? false : true } } var $DojoTextbox = function Textbox($elem) { var $self = findEnclosingWidget($elem, "dijitValidationTextBox") DojoWidget.call(this, $self) var $textbox = _textbox("dijitReset dijitInputInner", _in($self)) this.setValue = function ($value) { _setValue($textbox, $value) var $current = this.getValue() _assertEqual($value, $current) } this.getValue = function () { return _getValue($textbox) } this.blur = function () { _blur($textbox) } }
core.sah中定义了所有的Dojo widget类。所有的Dojo widget类都继承DojoWidget。DojoWidget定义了一些widget通用的函数,例如getLabel和hasError。$self变量通过函数findEnclosingWidget获得,这个变量代表了Dojo widget最外层的元素。此函数通过检查父节点中是否有widgetid属性,并且检查class属性的值是否包含指定的标示widget类型的字符串(例如,DojoTextbox的类型字符串是dijitValidationTextBox)来识别widget的最外层元素。Widget的继承通过call函数实现,它将$self传给DojoWidget类的构造器。$textbox的识别使用了_in函数,这种方法保证了元素识别的准确性。事实上,无论一个widget本身有多复杂,通过_in函数就可以将内部元素查找与外界隔离。大家或许注意到this.setValue函数中有个比较奇怪的地方,this.getValue()的返回值是先赋值给$current变量然后进行断言判断的。为什么不写成“_assertEqual($value,this.getValue())”呢?这是因为目前Sahi不支持这样的语句,或许将来会支持。
findByLabel的实现
function findByLabel($labelText, $className) { var $label = _label($labelText) var $wid = getAttribute($label, "for") _set($id, findIdByWID($wid)) var $div = _byId($id) return new $className($div) } <browser> function findIdByWID($wid) { var $widget = dojo.query("[widgetid='" + $wid + "']")[0] return $widget.getAttribute("id") } </browser>
通过元素label标识元素的原则通过findByLabel函数实现。它有两个参数,第一个是label的文本内容,第二个是目标widget的实现类。实现原理很简单。label元素的for属性值就是widget的widgetid的值。因此,我们通过widgetid就可以找到widget元素。但事实上,从下面大家看出来我们是先利用的dojo.query找到了元素对应的id,然后通过_byId获得widget元素。为什么用这种迂回的方法呢?根据Sahi的文档,理论上我们是可以通过修改concat.js文件增加widgetid查找属性(具体参见http://sahi.co.in/w/tweaking-sahi-apis)可以实现利用_div,_table或者_span等函数直接获得widget元素。不幸的是,当前版本中存在的一个bug导致自定义属性不能被识别。所以,目前只能先通过widgetid找id的方法迂回解决。另外,值得一提的是因为findIdByWID函数用到了dojo的库函数,因此它被定义在browser tag中。
Sahi自带对CSV,Excel以及数据库访问的函数。示例代码示范了如何使用CSV进行数据驱动测试。让我们一起来看看JobAppFormTests.sah中的testSimple函数。被注释掉的部分是一般的定义测试数据的方法。_readCSVFile函数加载testdata.csv到$data变量,它事实上一个两维数组。_dataDrive函数能够自动遍历数组数据调用fillForm函数。
function testSimple() { /* var $eduValue="masters" var $nameValue="my name" var $addressValue="Shanghai" var $stateValue="California" fillForm($nameValue,$eduValue,$addressValue,$stateValue) */ var $data = _readCSVFile("./testdata.csv") _dataDrive(fillForm, $data) }
testdata.csv内容:
Tom, high school, Address1, Alaska Mike, masters, Address2, Florida John, PhD, Address3, Hawaii
另外,Sahi提供了类似于JUnit的测试框架。所有以test开头的函数都被认为是测试用例,如果有setUp和tearDown函数,它们会分别在每个测试用例运行前后执行。并且所有测试文件还是可以组织到一个.suite文件中作为一套测试用例运行。更详细的介绍,请大家参考Sahi的官方文档。Sahi也能支持拖放,大家可以参考示例代码中Slider widget的实现。文件上传是很多Web自动化测试的局限,不过,Sahi得益于它Proxy的架构也实现了文件上传功能。
总的来说,Sahi是一款不错的Web自动化测试工具,尤其是它对元素关联查找的支持以及页面隐式等待的机制对Web2.0应用的测试是很有帮助的。希望读者阅读完本文能有所收获。如果,想了解更多关于Sahi的信息,请访问Sahi的官方网站( http://sahi.co.in/w/) 并且可以通过访问 http://www.slideshare.net/narayanraman 观看Sahi的推广演示文档。如果对Sahi与Selenium的比较感兴趣,可以访问 http://blog.sahi.co.in/2010/04/sahi-vs-selenium.html 。
沈锐目前工作于 IBM 中国开发实验室(CDL)。主要从事 IBM 存储产品的 Web UI 测试,如 IBM Storwize V7000、SVC 以及 SONAS 等。
感谢 郑柯对本文的审校。
给InfoQ中文站投稿或者参与内容翻译工作,请邮件至 [email protected]。也欢迎大家通过新浪微博( @InfoQ)或者腾讯微博( @InfoQ)关注我们,并与我们的编辑和其他读者朋友交流。