Web 应用程序的验收测试常常涉及一些手工任务,例如打开一个浏览器,并执行一个测试用例中所描述的操作。但是手工执行的任务容易出现操作人员人为的错误,也比较费时间。因此,尽可能将这些任务自动化,以消除人为因素,这是一种很好的做法。于是 Selenium 之类的测试工具就有了用武之地。Selenium 帮助您自动化验收测试,从而可以构建经过更严格测试、因而更为可靠也更易于维护的软件。
验收测试也称黑盒测试和功能测试,是测试和检验应用程序是否能按照涉众(stakeholder)的功能性需求、非功能性需求和其他重要需求来运行的一种方法。验收测试是单元测试和组合测试的补充,后两者通常是使用 xUnit 框架编写的。验收测试也可以使用编程语言来编写,但是 Selenium 和其他类似的工具,例如 Fitnesse,也支持用特定于工具的文档格式编写测试。
验收测试与单元测试和组合测试有以下不同之处:
- 应用程序是作为一个完整的端到端实体来测试的,而不是像单元测试和组合测试那样,只是测试一个类或一组类。
- 验收测试是在用户界面(例如一个浏览器)上执行的,而不是在 Web 应用程序界面上执行的。
- 编写测试用例的人不一定知道应用程序的内部结构,因此也被称作黑盒测试。非技术性用户也可以编写验收测试。
背景知识
在讨论 Selenium 之前,我要介绍关于以下三个话题的一些背景知识,因为这些话题虽然不是本文的主题,但是和本文密切相关:
- 持续集成
- Ajax
- Ruby/Ruby on Rails
持续集成
持续集成的目标是自动化构建和测试过程,以便每天自动运行一次或多次这些过程,而不是每个月手动地运行一次。使用持续集成的最大好处是,代码的更改会定期地自动被集成。如果系统受损,没有构建成功,Apache Continuum 和 Luntbuild 之类的持续集成工具可以自动通过发送电子邮件通知团队(见 参考资料)。
Ajax
Ajax 是 Asynchronous JavaScript and XML 的缩写,这是为相当老的技术新创造的一个术语。Ajax 背后的主要思想是,由于只需更新部分页面而不是整个页面,所以 Web 应用程序可以更快地对用户操作做出响应。
Ajax 将更多的复杂性引入到 Web 应用程序中,这一点也反映在测试中。这是因为 Ajax 就像它的名称所表明的那样,使用 JavaScript 和异步 HTTP 请求来更新页面内容。每个浏览器在实现中与其他浏览器相比有一些小小的不同。Selenium 是测试和检测这些差异的很好的工具,因为它在大多数流行的浏览器中都能够运行。
Ruby/Ruby on Rails
Ruby 是一种开放源码的解释型脚本语言,用于快捷、容易地进行面向对象程序设计。它提供了大量的库,而且简单易用,还具有可扩展性和可移植性。该语言是由 Yukihiro “Matz” Matsumoto 创造的,目的是让程序员将更多的注意力放在手头的任务上,摆脱语法的烦恼。
Rails 是由 David Heinemeier Hansson 创造的一种全栈的(full-stack)、开放源码的 Ruby Web 框架。Rails 的目标是使现实中的应用程序编写起来需要的代码更少,并且比 J2EE 和 XML 之类的语言更容易。所有层都能够无缝地一起工作,因此可以使用一种语言编写从模板到控制流乃至业务逻辑的各种东西。Rails 使用 YAML 而不是 XML 配置文件以及注释形式的反射和运行时扩展。这里不存在编译阶段 —— 程序修改后将直接运行。
什么是 Selenium?
Selenium 是 ThoughtWorks 专门为 Web 应用程序编写的一个验收测试工具。据 Selenium 主页所说,与其他测试工具相比,使用 Selenium 的最大好处是:
Selenium 测试直接在浏览器中运行,就像真实用户所做的一样。Selenium 测试可以在 Windows、Linux 和 MacintoshAnd 上的 Internet Explorer、Mozilla 和 Firefox 中运行。其他测试工具都不能覆盖如此多的平台。
使用 Selenium 和在浏览器中运行测试还有很多其他好处。下面是主要的两大好处:
- 通过编写模仿用户操作的 Selenium 测试脚本,可以从终端用户的角度来测试应用程序。
- 通过在不同浏览器中运行测试,更容易发现浏览器的不兼容性。
Selenium 的核心,也称 browser bot,是用 JavaScript 编写的。这使得测试脚本可以在受支持的浏览器中运行。browser bot 负责执行从测试脚本接收到的命令,测试脚本要么是用 HTML 的表布局编写的,要么是使用一种受支持的编程语言编写的。
Selenium 适用于以下浏览器:
Internet Explorer | Mozilla | Firefox | Safari | |
---|---|---|---|---|
Windows XP | 6.0 | 1.6+, 1.7+ | 0.8+, 0.9+, 1.0 | |
Red Hat Linux | 1.6+, 1.7+ | 0.8+, 0.9+, 1.0+ | ||
Mac OS X 10.3 | 不支持 | 1.6+, 1.7+ | 0.8+, 0.9+, 1.0+ | 1.3+ |
Selenium 命令
通过 Selenium 命令,脚本编写者可以描述 browser bot 在浏览器中所执行的操作。可以将这些命令分成两类 —— 操作(action) 和断言(assertion):
- 操作模拟用户与 Web 应用程序的交互。例如,单击一个按钮和填写一个表单,这些都是常见的用户操作,可以用 Selenium 命令来自动化这些操作。
- 断言验证一个命令的预期结果。常见的断言包括验证页面内容或当前位置是否正确。
在 Selenium 网站上可以找到可用命令的完整列表(见 参考资料)。
Selenium 模式
可以按两种模式来使用 Selenium:test runner 和 driven。这两种模式在复杂性和编写方式方面有所不同。driven 测试脚本编写起来往往要更复杂一些,因为它们是用编程语言编写的。但是如果使用 Python 或 Ruby 之类的高级动态编程语言,那么这种复杂性方面的差异就很小。
两种模式之间最大的不同点在于,如果使用 driven 脚本,测试有一部分在浏览器之外运行,而如果使用 test runner 脚本的话,测试是完全在浏览器中运行的。
不管是 test runner 还是 driven 测试用例,都可以与持续集成工具集成。
test runner 模式
Selenium test runner 脚本,也称测试用例(test case),是用 HTML 语言通过一个简单的表布局编写的,如 清单 1 所示。
清单 1. Selenium 测试用例的结构
<table border="1"> <tr> <td>First command</td> <td>Target</td> <td>Value</td> </tr> <tr> <td>Second command</td> <td>Target</td> <td>Value</td> </tr> </table>
test runner 脚本通常与所测试的应用程序(AUT)部署在同一个服务器上。这是因为 browser bot 使用 JavaScript 来模拟用户操作。这些脚本在一个受限制的沙箱环境中运行。如果需要绕过这些限制,可以使用一个代理。
test runner 脚本使用与 xUnit 框架相同的测试套件(test suite)和测试用例概念。测试用例和命令按照它们在测试套件和测试用例中出现的顺序依次执行。在 清单 1 中:
- 第一列包含命令 或断言。
- 第二列包含命令或断言的目标(target)。这里可以用多种受支持的组件定位符中的一种来指定目标。通常使用的是组件的 ID 或名称,但 XPath 和 DOM 定位符也是受支持的。
- 第三列包含用于为命令或断言指定参数的值。例如,当使用
type
命令时,这一列可能就是一个文本域所期望的值。
即使对于非技术人员来说,test runner 脚本也易于阅读和编写。当在一个浏览器中打开 清单 1 中的例子时,将得到类似这样的一个表:
First command | Target | Value |
---|---|---|
Second command | Target | Value |
接下来,我将描述如何使用命令和断言编写一个简单但是完整的测试用例。
测试用例实例
执行 清单 2 中的测试脚本时,它将执行以下操作:
- 通过进入 /change_address_form.html 打开变更地址页面。
- 在 ID 为
address_field
的文本框中输入Betelgeuse state prison
。 - 单击名为
Submit
的输入区。注意,这里使用 XPath 找到 Submit 按钮,这导致表单数据被发送到服务器。 - 验证页面是否包含文本
Address change successful
。
清单 2. 在测试用例中使用命令和断言的例子
<table> <tr> <td>open</td> <td>/change_address_form.html</td> <td></td> </tr> <tr> <td>type</td> <td>address_field</td> <td>Betelgeuse state prison</td> </tr> <tr> <td>clickAndWait</td> <td>//input[@name='Submit']</td> <td></td> </tr> <tr> <td>verifyTextPresent</td> <td>Address change successful</td> <td></td> </tr> </table>
测试套件
要达到对应用程序的完全测试覆盖,通常需要不止一个测试用例。这就是 Selenium 使用测试套件的原因。测试套件用于将具有类似功能的一些测试用例编成一组,以便让它们按顺序运行。
测试套件和测试用例一样,都是用简单的 HTML 表编写的。Selenium 执行的缺省测试套件的名称是 TestSuite.html。清单 3 展示了一个测试套件,该套件像通常的用户一样测试应用程序。注意,测试套件使用一个只包含一列的表,表中的每一行指向一个包含某个测试用例的文件。
清单 3. 测试套件示例
<table> <tr> <td>Test suite for the whole application</td> </tr> <tr> <td><a href="test_main_page.html">Access main page</a></td> </tr> <tr> <td><a href="test_login.html">Login to application</a></td> </tr> <tr> <td><a href="test_address_change.html">Change address</a></td> </tr> <tr> <td><a href="test_logout.html">Logout from application</a></td> </tr> </table>
接下来我将把目光转移到 driven 测试脚本。
driven 模式
driven Selenium 脚本是用多种受支持的编程语言中的一种编写的 —— 目前可用的有 Java、Ruby 和 Python 驱动程序。这些脚本在浏览器之外的一个单独的进程中运行。驱动程序的任务是执行测试脚本,并通过与运行在浏览器中的 browser bot 进行通信来驱动浏览器。驱动程序与 browser bot 之间的通信使用一种简单的特定于 Selenium 的连接语言 Selenese。
driven 脚本比 test runner 脚本更强大、更灵活,可以将它们与 xUnit 框架集成。driven 脚本的缺点(与 test runner 脚本相比)是,这种脚本编写和部署起来更复杂。这是因为驱动程序必须执行以下任务:
- 启动服务器。
- 部署所测试的应用程序(AUT)。
- 部署测试脚本。
- 启动浏览器。
- 发送命令到 browser bot。
- 验证 browser bot 执行的命令的结果。
driven 脚本更依赖于应用程序运行时环境。例如,Java 驱动程序使用一个嵌入式 Jetty 或 Tomcat 实例来部署所测试的应用程序。目前,已经有人在致力于将 Selenium 集成到 Ruby on Rails 中,但是在我撰写本文之际,这个集成版本还没有被发布。
清单 4 摘自一个使用 Ruby 驱动程序的 driven 测试脚本。注意,我省略了用于启动服务器和浏览器的步骤,这个测试脚本代码几乎和 test runner 脚本一样简单。
清单 4. 使用 Ruby 驱动程序的例子
puts selenium.open('/logout.html')
puts selenium.verify_location('/index.html')
现实中的需求
在接下来的两节(现实中的需求 和 现实中的用例)中,我将描述如何在现实场景中使用 Selenium,并针对用 Ruby on Rails 和一点儿 Ajax 技术编写的一个简单的股票报价查看器应用程序编写 Selenium 测试用例。虽然这个应用程序是用 Ruby on Rails 编写的,但是也可以将这个例子应用于任何 Web 应用程序,因为测试脚本是按 test runner 模式以 HTML 编写的。这个示例应用程序是用 Ruby 1.8.3 和 Ruby on Rails 0.14.2 测试的,但是它也可能可以使用更旧的或更新的版本。
如果有 Linux,那么发行版中通常已经包括了 Ruby。在命令提示符下运行 ruby -v
,检查您所拥有的版本。对于大多数平台,都可以在http://www.ruby-lang.org/ 上找到一个 Ruby 发行版。
接下来的步骤是通过 RubyGems 打包系统安装 Ruby on Rails。为此,只需执行 gem install rails --include-dependencies
。在某些平台上,必须执行一些额外的步骤,所以请访问 Ruby on Rails 网站,以获得更多细节。
在我撰写本文之际,目前可用的 Selenium 版本是 0.6。我已经将它集成在示例应用程序中(见 下载 小节),我的做法是从http://selenium.thoughtworks.com/ 下载 Selenium Core 包,然后将名为 selenium 的文件夹复制到用于静态内容的文件夹。在 Ruby on Rails 应用程序中,这个文件夹的名称是 public。在 J2EE Web 应用程序中,可以将 selenium 文件夹放在 Web 应用程序的根目录或 WAR 归档文件中。
最后一步是下载示例应用程序。从 下载 小节中获得这个包。解压应用程序,并打开一个命令提示符。然后转入应用程序被解压到的那个目录。为了启动应用程序,运行 ruby script/server
。应该看到 Rails 成功启动了,如 图 1 所示。
图 1. 从命令提示符下运行 Ruby on Rails
现实中的用例
在本节中,我将列出示例应用程序的用例。通过这些简化的用例,可以编写模拟用户所执行步骤的验收测试,并验证这些步骤的结果是否与预期相符。股票报价应用程序实现了以下四个用例:
- 登录
- 查看股票
- 查看股票细节
- 退出
实现这些用例的代码已经编写好了;可以在 app 目录中找到该代码,测试用例在 public/selenium/tests 文件夹中。
登录用例
大多数人都知道登录页面是如何工作的 —— 输入用户名和密码,然后将数据提交到服务器。如果凭证有效,就可以成功登录,并看到受安全保护的资源。在示例应用程序中,这个测试用例包含以下用户操作和断言,必须将它转换成一个 Selenium 测试用例:
- 单击登录链接。
- 验证系统是否要求用户进行登录。
- 输入用户名。
- 输入密码。
- 按下登录按钮。
- 验证是否登录成功。
图 2 展示了用于这些需求的 Selenium 测试用例。注意,我是在运行测试之后截取屏幕快照的。绿色箭头表示成功地通过验证的断言。
图 2. 登录和查看股票测试用例
查看股票测试用例
查看股票页面显示一个公司列表。用于这个页面的测试用例非常简单,所以被包括在前一个测试用例的后面。该测试用例验证当前位置是否为 /main/list_stocks,以及页面是否包含文本 Click on a company name to view details
。
查看股票细节用例
查看股票细节用例是在查看股票页面上触发的。用户在一个公司名称上单击鼠标时,就触发了到服务器的一个 Ajax 请求。服务器的响应包括该公司的详细信息,这些信息将插入到当前页面中,而不必重新装载完整的页面。用于这个用例的测试用例执行以下用户操作:
- 单击公司名称
Acme Oil
。 - 验证页面上是否显示该公司的详细信息。
- 单击公司名称
Acme Automotive
。 - 验证页面上是否显示该公司的详细信息。
由于使用了 Ajax,请求是异步发生的。在一般的 Web 应用程序中,所有东西通常都是同步的,因此这一点提出了一种不同的挑战。可以像测试其他功能一样来测试 Ajax 功能。惟一的不同是,必须让 Selenium 暂停,等待 Ajax 命令完成。为此,可以使用 pause
命令来等待 Ajax 命令的完成。另外,Joseph Moore 在他最近的 blog 贴中提到,还可以使用 waitForValue
和 waitForCondition
命令代替 pause 命令(见 参考资料)。
图 3 展示了被转换成 Selenium 用例的需求。
图 3. 查看股票细节测试用例
注意 pause
命令:必须使用这些命令,以便等待异步请求完成和更新页面内容。如果没有 500 毫秒的暂停,测试将失败(如 图 4 所示)。
图 4. 失败的查看股票细节测试用例
pause
命令还测试 Ajax 功能的非功能性需求。500 毫秒对于 pause 命令是一个很好的值,因为 Ajax 请求应该快速地执行和完成。可以试着去掉pause
命令,看看结果如何。如果测试在您的机器上失败,那么试着将这个值增加到 1000 毫秒。
退出用例
退出用例很容易实现,简单来说只有以下两步:
- 单击退出链接。
- 验证是否成功退出。
图 5 展示了最后这个测试用例。
图 5. 退出用例
所有测试都被添加到 图 6 左侧显示的缺省测试套件中。
图 6. 示例应用程序的测试套件
执行测试套件
最后要做的是在 Mozilla Firefox 和 Microsoft Internet Explorer 中执行测试套件。为此,在浏览器中打开http://localhost:3000/selenium/TestRunner.html
,然后单击 图 6 中所示的 All 按钮。失败的测试用例和断言将被标记为红色,但是这里,在两个浏览器中所有用例都应该可以成功完成(同样见 图 6)。注意,我使用的是 Mozilla Firefox 1.0.7 和 Internet Explorer 6.0。
还可以单步调试测试套件,这意味着 Selenium 将很慢地执行测试套件,这样当测试套件在浏览器中执行时,就可以看到它的每一步。
结束语
Selenium 是软件工程师、设计人员和测试人员的工具箱中又一个有用且重要的工具。通过将该工具与持续集成工具相结合,团队就可以将验收测试自动化,并构建更好的软件,因为他们可以更容易、更早、更频繁地发现 bug。Selenium 的另一个优点是可以节省时间,使开发人员和测试人员不必将时间花在本可以(也应该)自动化的手工任务上,从而让团队将精力放在更有价值的活动上。