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