摘要
无论你开发大或小的应用, 从安全来讲测试是一个很重要的组成部分.
但是如何可靠持续的测试, 尤其是当你的人员不能有效的测试应用的每一次改变? 所以呢,把这个工作交给软件是最合适的了.
这么多年来, 开发者开发了很多应用和技术来满足这个需求. 当然, 它们运行的非常好. 但是我们总是寻找更新的, 更简单的 (有时候是更'酷'的) 方式来做我们的工作. 所以最近我开始学习一种更有前途的新技术: PhantomJS.
PhantomJS是什么?
PhantomJS 是一个无界面的,包含了WebKit浏览器引擎和JavaScript API的脚本解释器. 速度快并且支持各种web标准: DOM 操作, CSS 选择器, JSON, Canvas 和SVG.
PhantomJS 的创建者是 Ariya Hidayat. 它是一个非常棒的技术,但是通过网站上的了解和所有的APIs文档, 我遇到了 CasperJS.
CasperJS是什么?
Casper 是一个用JavaScript编写的基于PhantomJS的导航脚本和测试工具.它充许我们像PHPUnit或 JUnit一样测试我们的网站,测试我们的代码.
它是一个非常棒的工具 – 一是它是非常简单的,用JavaScript编写,满足各种各样的测试需求,一是它可以方便的和我们的开发环境集成. 完美的组合前端和后端.
这本篇文章中,我将浏览一下CasperJS的基础用法。我将扮演一个典型的用户访问New Relic(新的遗址)网站的一页,特别是‘真实用户监控’部分。
我之所以选择这一页是因为它有大量方面对一个标准网站或应用极为常见,如图像,表单,按钮,链接和文本。它内容丰富并且有许多东西可供我们处理与测试。现在,我确信New Relic的勤快的伙计已经做过这些,但我也非常相信他们不会介意我为这个网站建立一个简单的测试集和断言。
特征CasperJS具有一系列特征。 本文中,我会聚焦于 测试人员 API。它具有一些列功能与断言,都是你期望一个好的测试API所具有的,包括:
* assertTextExists (文本存在断言)
* assertTitle (标题断言)
* assertHttpStatus (HTTP状态断言)
* assertDoesntExist (不存在断言)
* assertUrlMatch (Url匹配断言)
我将使用这些断言来显示CasperJS是如何工作的。它也包含一组其他特性,但我不会去深入探讨——仅仅足够做一个工作示例而已。不过,我鼓励你研究一下精彩的在线API文档。它阅读起来令人愉悦,因为它相当清晰和简洁。
准备
如果你没有准备好,在我们继续以前,先安装 PhantomJS 或 Casper 。 现在,让我们马上开始吧。
开始
当我第一次加载New Relic网站的'真实用户监控'页面,看起来像下面图片这样(自撰写本文以后它已经变化了)。我突出了注册表格的起点。
如果你点击了这个表单里的任何元素,它其余的部分就会显露出来,看起来就是下图这样。
测试将着眼于表单,但我们也要看看title标签,表单左边的图片,及其上的文字。
#p#
代码:
首先,为节省时间,我初始化了两个变量:一个用来存储URL,一个用来存储网站名称,因为我要在测试中的‘success’信息里显示它们。
- var url = 'http://newrelic.com/product/real-user-monitoring';
- var siteName = 'NewRelic';
Casper带有4个内建记录级别:
* debug
* info
* warning
* error
默认情况下,CasperJS会在‘error’级别过滤日志。所以如果你开始记录日志后没有看到任何东西,可能就是这个原因。为确保显示日志输出,我把它设置为‘debug’。而且我关闭了‘verbose’选项,如果它激活,我们会看到关于所有东西的信息,这会相当干扰。
- var casper = require('casper').create({
- verbose: false,
- logLevel: 'debug'
- });
你可以在Casper API页面上发现其它配置选项。comment函数,如下面演示的,使我们能记录输出——在这种情况下是输出到控制台。我在这里使用一个简单的消息说明测试开始了:
- casper.test.comment('Starting Testing');
函数‘casper.start’开始运行测试:
- casper.start(url, function() {
- this.test.assert(
- this.getCurrentUrl() === url, 'url is the one expected'
- );
- this.test.assertHttpStatus(200, siteName + ' is up');
这是我所做的:我加载在‘url’中指定的URL,并假定我们可以载入想要的网址。在成功加载页面之后,开始调用一些断言来确认所载入的页面上的内容是我们所期望的。
在这种情况下,我对返回的状态码进行了一个快速检查,它应该是200,我得到的也是200。让我们进行另一个断言:
- this.test.assertTitle(
- 'Real User Monitoring, End User Experience Monitoring : New Relic',
- siteName + ' has the correct title'
- );
上面这个测试非常简单。它断言载入的页面的标题应该是什么,并输出一个含有该信息的消息。
在下面的代码里,我首先检查确认存在一个ID为‘nr-signup-form’的表单。如果这个表单不存在,那对表单内的元素进行检查就没有意义了,对吧?
- this.test.assertExists(
- 'form[id="nr-signup-form"]',
- siteName + ' has a form with name "nr-signup-form"'
- );
现在,在我继续之前,我要提到的是,当我断言元素存在或不存在于加载的页面上时,我可以用两种不同的方法:
如果你想要更多XPath的信息,请检阅后面‘延伸阅读’部分中的一些相关链接,或阅读 我写并发到maltblue的文章。CSS选择器应该有很好的自我解释性。但为以防万一,我在“延伸阅读”部分中同样包含了关于它的链接。
现在让我们用断言来搜索输入域控件:
- this.test.assertExists(
- {type: 'xpath', path: '//input[@id="FullName"]' },
- 'the element exists'
- );
- this.test.assertExists(
- {type: 'xpath', path: '//input[@id="Company"]' },
- 'the element exists'
- );
- this.test.assertExists(
- {type: 'xpath', path: '//input[@id="Email"]' },
- 'the element exists'
- );
- this.test.assertExists(
- {type: 'xpath', path: '//input[@id="Password"]' },
- 'the element exists'
- );
- this.test.assertExists(
- {type: 'xpath', path: '//input[@id="PromoCode"]' },
- 'the element exists'
- );
在上面的五个断言中,我查找了五个输入域控件,它们的ID分别是‘FullName’, ‘Company’, ‘Email’, ‘Password’ 和‘PromoCode’。用XPath查找选择列表控件元素同样容易。
#p#
在下面的测试中,我假定有四个选择列表控件,它们的ID分别是:‘country’, ‘group’, ‘company_size’ 和‘HostEstimate’。
- this.test.assertExists(
- {type: 'xpath', path: '//select[@id="country"]' },
- 'the element exists'
- );
- this.test.assertExists(
- {type: 'xpath', path: '//select[@id="group"]' },
- 'the element exists'
- );
- this.test.assertExists(
- {type: 'xpath', path: '//select[@id="company_size"]' },
- 'the element exists'
- );
- this.test.assertExists(
- {type: 'xpath', path: '//select[@id="HostEstimate"]' },
- 'the element exists'
- );
现在,这些测试还非常简单。那么,让我们来把它们变得复杂点。
- this.test.assertExists(
- {
- type: 'xpath',
- path: '//button[@id="sign-up-button"
- and @class="small-button"
- and @type="submit"]'
- }, 'the element exists'
- );
在上面的测试中,我搜索‘Create Free Account’按钮,它在页面中的位置如下图所示。
我在查询中使用了一些条件运算符。我们要寻找一个具有下列条件的按钮:
* ID 为 ‘sign-up-button’
* class 为‘small-button’
* type 为‘submit’
虽然看起来似乎我关注XPath比CasperJS更多,但为了更多的功能和配置,请耐心忍受我多说一点吧。
- var x = require('casper').selectXPath;
- var checkboxXpath = "//input[@type='checkbox' and
- @id='checkbox1']/parent::*/a/label[
- contains(., 'Java') or
- contains(., 'Python') or
- contains(., 'Ruby') or
- contains(., 'PHP') or
- contains(., '.NET') or
- contains(., 'Node.js')
- ]";
- this.test.assertExists(x(checkboxXpath), 'the element exists');
我在上面的测试中所做的,是通过把'x'初始化为‘selectXPath’助手来使XPath测试变得简化一点。我要查找那一页上所有选择应用程序类型的复选框,所以XPath查询查找复选框时用一个伴随的label标签来匹配所提供的应用程序语言的名称。
为专门显示我并非只会使用XPath(虽然它极其强大而且简单),下面的两个测试也使用CSS选择器来检查一个输入域控件和列表项目也存在。
- this.test.assertVisible('input#Company');
- this.test.assertExists('li.nav-enterprise', 'the element exists');
搜索表单元素和列表项目很简单,但若你的页面有很多资源,对于图片或Flash的载入又怎么检测呢?这有一个例子:
- this.test.assertResourceExists(
- '/images/tshirt-dude-form.png', 'T-shirt Image exists'
- );
对于上面的测试,我断言穿着所展示T恤的男人的照片已载入。 最后一个测试断言某些文本存在,在本情况中就是在穿着T恤的男人镜头上方的‘Sign up for free!’文本:
- this.test.assertTextExists(
- 'Sign up for free!', 'page body contains "Sign up for free!"'
- );
然后我们这样结束‘start’函数:
- casper.test.comment('Ending Testing');
测试基本完成了,所以我输出另一个日志记录来让用户知道这一点:
- casper.run(function() {
- this.test.done(16);
- this.echo('So the whole suite ended.');
- require('utils').dump(casper.test.getFailures());
- require('utils').dump(casper.test.getPasses());
- this.exit();
- });
我可以写出我想要的所有测试,但若不调用“run”则什么都不会发生。所以“run”是揭开上述工作的序幕。我所做的是检查脚本是否已经运行了16个测试,并且在最后用‘this.echo()’输出最后一行文字表示整个测试已结束。
当我们运行测试时,有时得到最后结果的总结会挺好。用‘getFailures()’ 和 ‘getPasses()’函数,我们可以看到测试通过和失败的总结。跟在这一切最后,我调用“this.exit()”来清盘。这个可以退出 CasperJS和PhantomJS,如果需要,可以指定一个退出编号。
把这个更进一步,这一次我调用‘casperjs’略有不同:我传递‘test’,如下所示,伴随着“xunit”开关。 这个将测试结果输出到一个 XUnit XML文件.
- $ casperjs test mytest.js --xunit=log.xml
在CasperJS的下一个主要版本中,如果‘test’没被传递到命令中,你将无法运行和呈现测试。提前知道这个很重要,可以避免你的脚本被意外打断。
#p#
自动化测试
你是否想通过设置来自动化你的测试?我的意思是,我们可以手工进行一些测试,那很棒,但它令人乏味且很费时。令人高兴的是,有一个扩展有助于你进行自动化测试。当我开始写这个系列时没有意识到这一点。我想给@casperjs_org一个巨大感谢,他使我意识到谷歌浏览器的扩展resurectio。
克隆或下载一个拷贝并安装它,然后重新打开我们之前用过的 New Relic页,在扩展按钮列表中点击‘resurectio’。
在你想构成一个自动测试的网页上,单击网页地址栏里的“Go(转入)”。从现在开始你所做的任何事情都将被包含成为生成的测试脚本的一部分。
我点遍每个表单元素,从“Email”到“Company Size”。然后再次点击扩展按钮以停止录制,接着点击'Export CasperJS”,这会在浏览器中打开另一个页面来显示所生成的脚本。
复制那些到一个脚本,运行它,你会看到和我们先前创造的脚本一样,你会得到象下面的截图中看到的输出。
如你的见,它能很好地完成设置环境和建立所有测试断言的工作,我们所做的就是运行它。这使得用CasperJS来让测试准备就绪变得十分容易。
延伸阅读
* XPath
* CSS选择器参考
* JUnit
* PHPUNit
结论
我希望你喜欢这个对 CasperJS 的温柔介绍,欣赏它给我们的威力和灵活性,通过phantomjs去设计深入的用户接受测试套件。
我们看到我们能怎样用最少的努力去测试New Relic网站的‘Real User Monitoring’页面的内容,尽管要有一些手动的工作来确定所需的XPath表达式和CSS选择器。
但对于一个免费的解决方案来说,它确实提供了很多的能力。我相信你会同意这个观点。所以你觉得它怎么样?你能预见到你或你的组织将它作为你的部署 和/或 持续集成解决方案的一部分吗?
在下一部分,我将从测试中叉出来讲casperjs的其它部分,包括:
* HTTP 身份认证
* 鼠标事件
* 表单提交
* 冒充用户代理
* 制作AJAX请求
译文链接:http://www.oschina.net/translate/simpler-ui-testing-with-casperjs