fishy 2008-3-13 11:06
用Selenium测试web应用
[i][font=SimSun]问题[/font]1[/i][i][font=SimSun]:当用户在网上商店购物时,一次完整的购买流程需要用户进行好几个步骤的操作(包括选择商品、填写订单信息、选择支付方式、确认订单等),涉及四到五个页面以及数十个类的协作。如何在开发过程中始终确保该流程能够正确无误、畅通无碍?[/font][/i]
[i][font=SimSun]问题[/font]2[/i][i][font=SimSun]:客户提出需求:在显示货物列表时,应该首先按货物名称排序,名称相同的货物再按照价格排序。我们已经实现了这一功能,并且有单元测试作为保障,但如何让客户看到我们的成果?[/font][/i]
[i][font=SimSun]问题[/font]3[/i][i][font=SimSun]:美工在制作页面时,一不小心把一个[/font]<form>[/i][i][font=SimSun]的[/font]id[/i][i][font=SimSun]属性删掉了。几天之后,另一个页面上的[/font]JavaScript[/i][i][font=SimSun]莫名其妙地失效,我们花了很多时间才发现这个问题。应该如何避免类似的情况再次发生?[/font][/i]
[font=SimSun]这三个问题对于做惯了[/font]web[font=SimSun]应用的读者来说一定不陌生——实际上,我们的每个项目都或多或少地遇到类似的问题。说穿了,这三个问题都是关于同一件事情:如何验证一个东西是正确的,以及如何便利而自动地重复这一验证过程。在代码层面上,[/font]xUnit[font=SimSun]单元测试工具(对于[/font]J2EE[font=SimSun]项目,就是[/font]Junit[font=SimSun])给了我们帮助。但是,当问题涉及到[/font]web[font=SimSun]界面和用户交互时,[/font]JUnit[font=SimSun]就显得有些力不从心了,这也是很多采用测试驱动开发([/font]TDD[font=SimSun])方法的团队只能把[/font]TDD[font=SimSun]贯彻到[/font]web controller[font=SimSun]层面的原因。[/font]
[table=98%,rgb(217,217,217)][tr][td=1,1,528][b][font=SimSun]单元测试[/font] vs. [/b][b][font=SimSun]功能测试[/font][/b]
[font=SimSun]正如它的名字所揭示的,[/font]JUnit[font=SimSun]是一个单元测试工具。而我们在前面提出的三个问题,实际上已经属于功能测试([/font]functional test[font=SimSun])或者验收测试([/font]acceptance test[font=SimSun])的范畴。尽管单元测试能够保证各个单元的正确,却无法确保将这些单元组合起来之后的效果。需要依靠功能测试工具,我们才能继续[/font]TDD[font=SimSun]的脚步。[/font]
[font=SimSun]另一方面,功能测试在很多时候应该由客户——或者是具有一定技术背景的客户代表(可能是项目的需求分析师)——来编写的(这也是“验收测试”这个名称的由来:通过这些测试就代表工作通过验收),因此编写这些测试不应该要求太高的编程能力。在这一点上,[/font]JUnit[font=SimSun]也是令人望而生畏的。[/font]
[/td][/tr][/table][font=SimSun]由[/font]ThoughtWorks[font=SimSun]员工开发并维护的[/font]Selenium[font=SimSun]([/font][url=http://selenium.thoughtworks.com/][color=#0000ff]http://selenium.thoughtworks.com[/color][/url][font=SimSun])正是帮助我们解决上述问题的得力工具。简单地说,[/font]Selenium[font=SimSun]是一个自动化的[/font]web[font=SimSun]应用功能测试工具——我知道,这个短语不足以让读者了解它所描述的对象。所以,在进一步介绍之前,我想先请读者来看一个活生生的例子。请打开你的浏览器,访问下列[/font]URL[font=SimSun]地址:[/font]
[url=http://www.openqa.org/selenium/demo1/TestRunner.html][color=#0000ff]http://www.openqa.org/selenium/demo1/TestRunner.html[/color][/url]
[font=SimSun]你将会看到[/font]Selenium[font=SimSun]的主操作界面(如图[/font]1[font=SimSun])。可以看到,整个页面被分成四个部分。左上角的“[/font]Test Suite[font=SimSun]”区域显示出当前运行的测试套件包含哪些测试用例;中间上部的“[/font]Current Test[font=SimSun]”区域显示出当前执行的测试用例;右上角的“[/font]Control Panel[font=SimSun]”区域是给用户操作的区域。至于下面的一大片空间,它会在执行测试的过程中起到重要的作用,我们稍后就会看到。[/font]
<!--[if !vml]-->[img=576,358]http://p.blog.csdn.net/images/p_blog_csdn_net/gigix/118915/o_01.PNG[/img]<!--[endif]-->
[align=center][align=center][font=SimSun][size=9pt]图[/size][/font][size=9pt]1[/size][font=SimSun][size=9pt]:[/size][/font][size=9pt]Selenium[/size][font=SimSun][size=9pt]主界面[/size][/font][size=9pt][/size][/align][/align][font=SimSun]点击“[/font]Control Panel[font=SimSun]”区域中的“[/font][b]All[/b][font=SimSun]”按钮,读者会——或许有点惊讶地——发现,屏幕上的文字和颜色开始飞快地发生变化。如果你还没有明白这是怎么回事,可以先把按钮上方的单选按钮放在“[/font]walk[font=SimSun]”上,然后再点击按钮。这时你会清楚地看到,原来[/font]Selenium[font=SimSun]正在逐个运行套件中的测试用例:执行测试用例中指定的操作,并进行指定的条件判断。至于屏幕下方的大块空白区域,此刻正在模拟着实际的用户操作。而那些淡绿色的横条,熟悉[/font]JUnit[font=SimSun]的你应该不难猜到,正是测试通过的象征——看到这些绿色横条让你感到心情愉悦,不是吗?[/font]
<!--[if !vml]-->[img=576,203]http://p.blog.csdn.net/images/p_blog_csdn_net/gigix/118915/o_02.PNG[/img]<!--[endif]-->
[align=center][align=center][font=SimSun][size=9pt]图[/size][/font][size=9pt]2[/size][font=SimSun][size=9pt]:测试套件执行完毕[/size][/font][/align][/align][font=SimSun]读者可以看到,“[/font]Control Panel[font=SimSun]”区域中还显示着本次测试的相关信息:耗时[/font]10[font=SimSun]秒,执行[/font]3[font=SimSun]个测试用例,共有[/font]10[font=SimSun]个判断条件,所有测试都通过,没有失败或未完成的测试。此外,如果点选一个测试用例,再点击“[/font][i]Selected[/i][font=SimSun]”按钮,就可以单独运行这一个测试用例;如果选中“[/font]Step[font=SimSun]”选项,就可以进行单步跟踪运行。这些功能,相信聪明的读者只需要稍微尝试一下就会全部掌握了。[/font]
[font=SimSun]经过几分钟的探索,读者应该能够明白这个[/font]Demo[font=SimSun]的奥妙所在了。没错,[/font]Selenium[font=SimSun]采用[/font]JavaScript[font=SimSun]来管理整个测试过程,包括读入测试套件、执行测试和记录测试结果——这在很大程度上得益于强大的[/font]JavaScript[font=SimSun]单元测试工具[/font]JSUnit[font=SimSun]([/font][url=http://www.edwardh.com/jsunit/][color=#0000ff]http://www.edwardh.com/jsunit/[/color][/url][font=SimSun]),正是有它的帮助,[/font]Selenium[font=SimSun]才能够模拟真实的用户操作,包括浏览页面、点击链接、输入文字、提交表单等等,并且能够对结果页面进行种种验证。也就是说,只要在测试用例中把预期的用户行为与结果都描述出来,我们就得到了一个可以自动运行的功能测试套件。而且,我们习惯的测试驱动开发方法也可以延伸到[/font]web[font=SimSun]表现层:我们可以先写测试、运行测试并看到它失败、然后编写功能代码让测试通过。[/font]
[font=SimSun]现在,如果你已经对[/font]Selenium[font=SimSun]产生了兴趣,我将带领你开始真正的[/font]Selenium[font=SimSun]测试之旅。首先,请到以下地址下载最新版本的[/font]Selenium[font=SimSun](当然,作为一个[/font]J2EE[font=SimSun]开发者,我假设你已经安装了[/font]JDK[font=SimSun]和[/font]servlet[font=SimSun]容器譬如[/font]Tomcat[font=SimSun]):[/font]
[url=http://www.openqa.org/selenium/download.action][color=#0000ff]http://www.openqa.org/selenium/download.action[/color][/url]
[table=98%,rgb(217,217,217)][tr][td=1,1,528][b]Selenium[/b][b][font=SimSun]的故事[/font][/b]
[font=SimSun]在等待下载的过程中,不妨先听我讲讲和[/font]Selenium[font=SimSun]有关的故事。正如我在前面提到过的,[/font]Selenium[font=SimSun]是[/font]ThoughtWorks[font=SimSun]员工在业余时间开发并维护的开源项目,并且在[/font]ThoughtWorks[font=SimSun]的项目中被广泛应用。不过,真正有趣的是它名字的来历:在[/font]Selenium[font=SimSun]出现之前,最著名的[/font]web[font=SimSun]应用功能测试工具当属[/font]Mercury Quanlity Center[font=SimSun]([/font][url=http://www.mercury.com/us/products/quality-center/][color=#0000ff]http://www.mercury.com/us/products/quality-center/[/color][/url][font=SimSun]),但那是一个商业工具,功能强大却也价格不菲,常常让开发者们又爱又恨。所以,自己动手开发开源功能测试工具的[/font]ThoughtWorker[font=SimSun]们把这个工具叫做[/font]Selenium[font=SimSun]——“[/font]mercury[font=SimSun]”有“水银”的意思,而“[/font]selenium[font=SimSun]”(硒元素)恰好是专解汞中毒的特效药。[/font]
[/td][/tr][/table][font=SimSun]把下载的压缩包解压之后,你会得到两个目录:[/font]doc[font=SimSun]和[/font]selenium[font=SimSun]。只要把后者复制到你的[/font]web[font=SimSun]服务器根目录(对于[/font]Tomcat[font=SimSun],就是[/font]webapps[font=SimSun]目录)下,就算是完成[/font]Selenium[font=SimSun]的安装了。安装好之后,可以启动[/font]web[font=SimSun]服务器,然后试着访问下列[/font]URL[font=SimSun]地址(假设你也像我一样,把[/font]Tomcat[font=SimSun]开在[/font]8080[font=SimSun]端口上):[/font]
[url=http://localhost:8080/selenium/TestRunner.html][color=#0000ff]http://localhost:8080/selenium/TestRunner.html[/color][/url]
[font=SimSun]在这里,你应该又会看到那个熟悉的主操作界面(如图[/font]3[font=SimSun])。不妨试着运行一下这些测试,看看它们是否能够在你本地的机器上正常运行。确认一切正常之后,我们再来编写自己的测试。[/font]
<!--[if !vml]-->[img=576,305]http://p.blog.csdn.net/images/p_blog_csdn_net/gigix/118915/o_03.PNG[/img]<!--[endif]-->
[align=center][align=center][font=SimSun][size=9pt]图[/size][/font][size=9pt]3[/size][font=SimSun][size=9pt]:在本地运行[/size][/font][size=9pt]Selenium[/size][/align][/align][font=SimSun]缺省情况下,[/font]Selenium[font=SimSun]会从[/font]tests[font=SimSun]目录下的[/font]TestSuite.html[font=SimSun]文件加载测试套件,但我们也可以指定从别的文件加载。首先,我们在[/font]tests[font=SimSun]目录下创建一个[/font]MyTestSuite.html[font=SimSun]文件,然后在其中定义我们的测试套件:[/font]
<html>
<head>
<title>My First Test Suite</title>
</head>
<body>
<table cellpadding="1" cellspacing="1" border="1">
<tbody>
<tr><td><b>Test Suite</b></td></tr>
<tr><td>
<a href="./MyTests/TestHello.html">Test Say Hello</a>
</td></tr>
</tbody>
</table>
</body>
</html>
[font=SimSun]然后,在[/font]tests/MyTests[font=SimSun]目录下创建[/font]TestHello.html[font=SimSun]文件,在其中描述我们要做的动作和期望得到的结果:[/font]
[color=maroon]<html>[/color]
[color=maroon]<head>[/color]
[color=maroon]
<title>Test Hello</title>[/color]
[color=maroon]</head>[/color]
[color=maroon]<body>[/color]
[color=maroon]<table cellpadding="1" cellspacing="1" border="1">[/color]
[color=maroon]
<tbody>[/color]
[color=maroon]
<tr>[/color]
[color=maroon]
<td rowspan="1" colspan="3">Test Say Hello To World<br>[/color]
[color=maroon]
</td>[/color]
[color=maroon]
</tr>[/color]
[color=maroon]
<tr>[/color]
[color=maroon]
<td>open</td>[/color]
[color=maroon]
<td>/sample/hello.jsp</td>[/color]
[color=maroon]
<td> </td>[/color]
[color=maroon]
</tr>[/color]
[color=maroon]
<tr>[/color]
[color=maroon]
<td>verifyTextPresent</td>[/color]
[color=maroon]
<td>Hello, World!</td>[/color]
[color=maroon]
<td> </td>[/color]
[color=maroon]
</tr>[/color]
[color=maroon]
</tbody>[/color]
[color=maroon]</table>[/color]
[color=maroon]</body>[/color]
[color=maroon]</html>[/color]
[font=SimSun]测试套件和测试用例的写法都是一目了然的。在测试用例中,我们首先访问[/font]/sample/hello.jsp[font=SimSun]这个地址([/font]open[font=SimSun]命令),然后验证页面上有“[/font]Hello, World![font=SimSun]”字样存在([/font]verifyTextPresent[font=SimSun]命令)。在[/font]Selenium[font=SimSun]的[/font]doc[font=SimSun]目录下,你可以找到完整的命令帮助列表。现在访问[/font][url=http://localhost:8080/selenium/TestRunner.html?test=tests/MyTestSuite.html][color=#0000ff]http://localhost:8080/selenium/TestRunner.html?test=tests/MyTestSuite.html[/color][/url][font=SimSun]这个地址,应该就可以看到我们的测试套件,当然现在运行它会看到红色的失败信息。[/font]
<!--[if !vml]-->[img=575,305]http://p.blog.csdn.net/images/p_blog_csdn_net/gigix/118915/o_04.PNG[/img]<!--[endif]-->
[align=center][align=center][font=SimSun][size=9pt]图[/size][/font][size=9pt]4[/size][font=SimSun][size=9pt]:运行我们的第一个测试,失败了[/size][/font][size=9pt][/size][/align][/align][font=SimSun]熟悉[/font]TDD[font=SimSun]过程的读者现在不仅不会失望,反而会感到兴奋,因为这个失败的测试为我们指出了目标。为了让测试通过,我们可以创建一个名为[/font]sample[font=SimSun]的[/font]web[font=SimSun]应用,在其中放上[/font]hello.jsp[font=SimSun]这个文件,让它向世界问好。然后,我们就可以享受成功的喜悦了。[/font]
<!--[if !vml]-->[img=576,359]http://p.blog.csdn.net/images/p_blog_csdn_net/gigix/118915/o_05.PNG[/img]<!--[endif]-->
[align=center][align=center][font=SimSun][size=9pt]图[/size][/font][size=9pt]5[/size][font=SimSun][size=9pt]:我们的第一个[/size][/font][size=9pt]Selenium[/size][font=SimSun][size=9pt]测试通过了[/size][/font][size=9pt][/size][/align][/align][font=SimSun]继续这个过程:编写测试[/font]-[font=SimSun]红[/font]-[font=SimSun]编写功能代码[/font]-[font=SimSun]绿……随着我们一步步前进,测试用例也会逐渐增加,最终构成一张庞大而严密的安全网。不仅是[/font]Java[font=SimSun]程序,在[/font]Selenium[font=SimSun]的帮助之下,界面的开发工作同样可以用[/font]TDD[font=SimSun]的方式来进行。而且,由于是架设在[/font]JavaScript[font=SimSun]的基础上,[/font]Selenium[font=SimSun]并不仅限于[/font]J2EE web[font=SimSun]应用的测试,实际上各种[/font]web[font=SimSun]应用都有它的用武之地。[/font]
[font=SimSun][size=12pt]作为一篇简介,本文只能帮助读者对[/size][/font][size=12pt]Selenium[/size][font=SimSun][size=12pt]建立一个最基本的了解,更多的知识与技巧还有待读者去探索。譬如说,在持续集成的环境下,可以用[/size][/font][size=12pt]Ant[/size][font=SimSun][size=12pt]来驱动[/size][/font][size=12pt]Selenium[/size][font=SimSun][size=12pt]测试,并将测试结果汇报给[/size][/font][size=12pt]CruiseControl[/size][font=SimSun][size=12pt],从而实现更加严格的集成管理。在下次的文章中,我将向读者介绍[/size][/font][size=12pt]ThoughtWorks[/size][font=SimSun][size=12pt]公司采用测试驱动开发的一些技巧与实践(当然也包括[/size][/font][size=12pt]Selenium[/size][font=SimSun][size=12pt]的使用心得)。现在,让我们先说再见吧,希望你编程愉快。[/size][/font]