如何使用 Selenium 和 JUnit 一起构建用于测试 Oracle Application Development Framework Web UI 的单元测试
作者:John Stegeman
2010 年 3 月发布
下载:
除了对各个组件和代码片断进行测试之外,测试 Web 应用程序的用户界面 (UI) 是否能按预期那样工作也很重要。传统上,此类用户界面测试由坐在计算机前的真人手动完成,他们通过运行测试脚本来验证一切是否正常工作。然而,手动进行 UI 测试有一些缺点:
下载和安装 Selenium IDE
,使用 Selenium 的第一步是下载和安装 Selenium IDE,该工具用于记录组成一个测试的用户操作。Selenium IDE 是一个 Mozilla Firefox 浏览器插件,这意味着您只有安装了 Firefox 2.x 或 3.x 才能使用它。Selenium Remote Control (Selenium RC) 支持 Firefox 2.x 和 3.x。如果尚未安装 Firefox,可从 http://getfirefox.com 下载。编写此文时的最新版本是 3.5.4,我将在本文中使用该版本。
安装 Firefox 后,下一步是安装 Selenium IDE 插件。为此,可以用 Firefox 打开 Selenium 下载页面,然后单击 Selenium IDE 的下载链接。单击该下载链接后,作为一种安全措施,Firefox 将阻止安装该插件。您将在 Web 页面顶部看到如下信息:
图 1:Firefox 安全性
只需单击 Allow 即可允许安装 Selenium IDE。Firefox 将提示您对该插件的安装进行确认。允许安装进行,在出现提示时,单击 Restart Firefox 按钮。
图 2:安装 Selenium IDE 后重启 Firefox
Firefox 重启后,会出现 Add-On 窗口告知您安装成功。
编写您的第一个 UI 测试
现在您已安装了 Selenium IDE,可以开始记录您的第一个 UI 测试了。我们从一个非常简单的使用公共网站的测试开始,这样我们可以了解 Selenium 无需应付 Oracle ADF 应用程序复杂情况的工作过程。首先,我们需要讲一下我们的 UI 测试应该做些什么,我们的预期的结果是什么。我们来建立这样一个简单的测试:转到 Oracle Technology Network (OTN) 主页,单击 Oracle JDeveloper 11g 下载链接,接受许可协议、然后验证存在 Oracle JDeveloper 11g 的下载链接。您可以通过一系列操作和预期结果指定该测试(注意,OTN 页面布局随时可能改变,您可能需要修改这里的 OTN 使用说明):
您会发现,如果花些时间写下测试的每一步骤以及预期结果(像上面那样),就可以轻松地编写出良好的 UI 测试。注意,这个简单测试的各个步骤在撰写本文时是正确的。如果 Oracle 更改了这个网站,或者您的默认语言不是英语,您可能需要修改这些步骤。要使用 Selenium IDE 记录这个测试,只需按以下步骤进行:
图 9:断言存在 Agreement 单选按钮
对于熟悉 HTML 的您来说,请注意这么一点:该页面实际上有两个名为 Agreement 的元素,幸而用以 Selenium 的工作方式,它只会看到(稍后单击)的第一个元素,因此我们的测试是可行的。如果您对更高级的测试用例使用 Selenium,您将需要参考 文档以了解更多信息。
您所记录的测试用例在 Selenium IDE 中应类似如下所示(对于任何多余的步骤,只需单击它们然后按 Delete 键即可删除):
图 10:第一个完整的 UI 测试
使用 Selenium IDE 中的 File / Save 命令保存该测试用例。现在您可以快捷地运行一次该测试用例,单击 Play current test case 按钮:
您会看到测试用例在 Selenium IDE 中成功执行(您的主浏览器窗口将显示实际 Web 页面以及该测试执行的各个操作)。您将看一个测试失败的例子,只需更改最后一个 assertElementPresent 命令的目标,使该命令搜索一个不存在的链接。我是这么做的:更改我的测试用例,使搜索目标为“link=I don't exist”,然后运行该测试。下面是我所看到的 Selenium IDE 中的显示(如果您这么做,请记住在完成后将您的测试改回去):
图 11:UI 测试失败
您刚创建的这个简单的 UI 测试很有用,您可以在 Selenium IDE 中反复运行这个测试,观察它是成功还是失败。然而,使用 Selenium IDE 手动运行该测试因为仍需要有人来手动启动,因此并不能享受到完全自动化测试的所有好处。为了享受自动化的全部好处,我们需要将这个 Selenium 测试转换为另一种可在所选测试框架 (JUnit) 中使用的形式。幸运的是,Selenium IDE 允许我们这么做。要创建 JUnit 测试类,依次单击 Selenium IDE 的 File 菜单和 Export Test Case As... 子菜单,最后单击 Java (JUnit) - Selenium RC 菜单项:
图 12: 创建 JUnit 测试类
为该测试文件指定一个合适的名称(如“jdevDownloadTest.java”)。如果观察一下生成的源代码,您将注意到:首先,生成的测试类使用旧的 JUnit 3.x 命名惯例驱动的测试方法标识(方法必须以“test”开头)。稍后,当我们在 Oracle JDeveloper 11g 中建立该类时,将对此进行修改。您还会注意到,测试类是名为 SeleniumTestCase 的 Selenium 基类的一个扩展。SeleniumTestCase 作为另一个名为 Selenium Remote Control (Selenium RC) 的 Selenium 程序包的一部分提供。Selenium RC 有两个主要组成部分:一个能够启动和终止浏览器、驱动 Selenium 测试执行的服务器,以及针对几种编程语言和测试框架(包括 JUnit)的一组客户端库。为了使您的 UI 测试完全自动化进行,您需要从 Selenium 的下载页面下载 Selenium RC。下载完之后,将 Selenium RC zip 文件解压缩到您计算机上一个合适的目录。
为了在 Oracle JDeveloper 11g 中运行所生成的 UI 测试,我们需要执行一些准备步骤:
第一步是为 Selenium Java 客户端驱动程序创建一个 Oracle JDeveloper 11g 库定义。如果您一直在跟踪学习本系列的文章,就会记得,我们的应用程序中使用的所有第三方的库都保存在 Subversion 中,这样我们可以在任何计算机上构建和测试我们的应用程序。我们将继续使用我们一直在开发的应用程序来包含此 UI 测试。在 3pLibs 项目目录中创建一个名为“selenium”的目录,将 selenium-client-driver.jar 文件复制到该目录中。记住同时要将该文件添加到 Subversion 中(如果为此您需要帮助,请参阅本系列中的 Subversion 文章)。最后,使用 Oracle JDeveloper 11g 的 Tools 菜单中的 Manage Libraries 命令创建一个名为“Selenium Java Driver”的 User 库:
图 13:在 Oracle JDeveloper 11g 中创建 Selenium Java Driver 库
为使这些 UI 测试可用于我们的 Ant 脚本,我们使用 Ant 文章中的方法将该库添加到 3pLibs 项目并生成一个 3pLibs.xml 文件(使用 JDevLibsForAnt 扩展)。生成该文件并且如 Ant 一文中所述整理其条目后,该文件将如下所示:
图 14:3pLibs.xml 文件
和往常一样,确保将该文件添加到 Subversion 中。通常情况下,确保将所有新文件添加到 Subversion 并提交更改。
下一步是在我们的应用程序中添加一个新的 Oracle JDeveloper 11g 项目以存放我们的 UI 测试。为此,从 Oracle JDeveloper 11g 的 Application Navigator 菜单中选择 New Project...。选择创建一个 Generic Project,指定其名称为“UITests”。在 Application Navigator 中双击该新项目,然后将 Selenium Java Driver 库和 JUnit 4 Runtime 库添加到该项目中:
图 15:添加 Selenium 和 JUnit 库
下一步是将生成的 JUnit 测试类放入 UITests 项目中。我一般这么做:首先在 Oracle JDeveloper 中创建一个类,然后将生成的 Selenium 测试类的代码复制并粘贴到我创建的这个类中。在 UITests 项目中创建一个名为 testJDevDownload 的新类(我将其放入 com.stegeman.otn.view.tests 包中)。将代码整理为您喜欢的格式,使用 Oracle JDeveloper 导入代码助手导入 com.thoughtworks.selenium.SeleneseTestCase 类。现在,我们可以添加一些 JUnit 批注(使用导入代码助手来获取合适的导入)并更改我们要测试的网站的 URL,完成后,该测试类如下所示:
图 16:完整的 testJDevDownload 测试类
如果现在运行该测试类,您会看到运行失败,因为尚未启动 Selenium Server:
图 17:由于没有 Selenium Server 而导致测试失败
幸运的是,启动 Selenium Server 是很容易的。只需打开一个命令提示符(或 *nix shell),转到您之前将 Selenium RC 解压缩到的 selenium-server-x.y.z 子目录,然后运行 java -jar selenium-server.jar 命令。现在您应能够在 Oracle JDeveloper 11g 中运行测试类并看到该测试执行成功。Selenium RC Server 负责启动 Firefox、运行测试、关闭 Firefox、将成功/失败结果返回给您的测试类。如果您想在 Microsoft Internet Explorer 上而不是 Firefox 上运行该测试,可以将测试类中的 setUp 调用中的字符串“*chrome”替换为“*iexplore”。
现在,您已了解了编写 UI 测试的基本流程,可以继续编写针对 Oracle ADF Faces RC 应用程序的 UI 测试。为了对 Oracle ADF Faces RC 应用程序使用 Selenium IDE,您需要了解一些有关的重要事项:
为了编写我们的 UI 测试,首先以操作和预期结果的形式来描述该测试用例。您可能还记得,在本系列的早期文章中,我们的应用程序是一个简单的程序,该程序有一个页面列出了各个部门,用户可通过该页面的上下文菜单转到第二个页面以编辑某个部门。对我们的 UI 测试定义如下:
对于此 UI 测试应注意,为了测试成功,要求数据库处于某种状态。换言之,数据库中按字母顺序排列第一的部门必须名为“Accounting”。如何确保某个数据库状态超出了本文的讨论范围,但有一些工具,如 DbUnit(参见“资源”一节),可以有所帮助。另一个可能的解决方案是,作为测试套件的一部分获取“标准”数据库的一个备份并进行恢复。
然而,在我们启动 Selenium IDE 并开始记录我们的测试之前,先回顾一下以前针对 Oracle ADF Faces RC 应用程序的建议。首先,我们这个简单应用程序中的所有相关组件已设置了 id 属性值,因而在这方面没有问题。我们还知道,该应用程序在 Department List 页面中使用了一个上下文菜单,因此我们需要就如何在这种情况下使用 Selenium ID 来探求某种可行的方法。最后,Department List 页面确实有一个具有 lazy 内容交付设置的 af:table,所以我们需要在测试中插入特定的 Selenium 命令,以便等待 af:table 完全加载后再继续进行该测试。
为了有助于理解 Oracle ADF Faces RC 页面的 HTML 结构,您可能希望下载并安装用于 Mozilla Firefox 的 Firebug 插件。Firebug 插件提供了一个控制台窗口,当浏览器运行您的页面时,该窗口可显示该页面的结构,因而可帮助您在生成的 HTML 中找到特定的元素。
现在来对我们的应用程序编写 UI 测试。首先在 Oracle JDeveloper 11g 中运行 Department List 页面(提示:右键单击,选择 Run)。这样可确保集成的 Oracle WebLogic Server 已启动、我们的应用程序已部署并运行。出现 Department List 页面后,如前所述打开 Selenium IDE。开始记录之前,将 Department List 页面的 URL 复制并粘贴到 Firefox 地址栏中(去除了针对会话 ID 和控制状态的额外的 URL 参数),按 Enter。(该 URL 应类似如下所示: http://127.0.0.1:7101/otnapp-ViewController-context-root/faces/listDepartments。) 该 Web 页面出现后,单击 Accounting 部门(这应该是列出的第一个部门)。在 Selenium IDE 中,您现在应能看到两个命令(open 以及其后的 click)。如果您现在运行该 UI 测试,该测试会失败。)您能猜到其中的原因吗?我们知道,表的内容交付设置为了 lazy,这意味着表数据将在后续的 Ajax 请求中加载进来。当 Selenium 运行该测试时,它会知道页面已加载,但不知道自己需要等待后续 Ajax 请求完成。因此,click 命令所引用的元素尚不存在。为了解决此问题,我们在该测试中插入一个显式的命令以便等待该元素出现。在 Selenium IDE 中,右键单击 click 命令,然后选择 Insert New Command。然后,您可以提供 waitForElementPresent 命令及其参数,如下所示:
图 18:等待初始 Ajax 请求完成
用作该命令的 Target 值的字符串应相当易于理解:该字符串引用 ID 为“t1::db”的 HTML DIV 元素中的第一个表 (table[1]) 的主体 (tbody) 中的第一个表行 (tr[1]) 的第一个单元格 (td[1])。该 DIV 元素的 ID 就是我们的 af:table 的 ID (t1) 加上一个后缀“::db”,这是 Oracle ADF Faces RC 为 af:table 的内容生成的 ID。下一步是验证第一个部门的名称为“Accounting”。您可通过以下方法轻松完成这一步:在 Selenium IDE 中复制并粘贴 click 命令,然后将该命令改为 assertText,将其 Target 和 Value 设置为如下:
图 19:断言第一个部门名称为“Accounting”
该 assertText 命令断言指定的组件中的 text 属性与指定的值匹配。当您开发更复杂的 UI 测试时,可参阅 Selenium 文档以了解有哪些命令可供使用。这就是该测试的第一步。第二步是调用 Accounting 部门的上下文菜单:选择 Edit 上下文菜单项。因为我们定义的上下文菜单代替了浏览器的默认上下文菜单,所以我们无法使用 Selenium IDE 的上下文菜单来创建该命令。用于调用该上下文菜单的 Selenium 命令是 contextMenuAt(该命令名称非常直观)。和上一步一样,将 click 命令复制并粘贴到 UI 测试脚本的未尾。将该命令更改为 contextMenuAt,将其 Target 和 Value 设置为如下所示(5,5 代表相对于该组件左上角的象素偏移值):
图 20:调用上下文菜单
Selenium IDE 应仍处于记录状态,因此当您右键单击 Accounting 部门,出现上下文菜单后选择 Edit 时会记录下对该上下文菜单的 Edit 命令的调用。可以看到,Selenium 在测试脚本中插入了一个 clickAndWait 命令。然而,由于该上下文菜单的出现需要一点时间,我们要在 contextMenuAt 命令和 clickAndWait 命令之间再插入一个 waitForElementPresent 命令来等待该上下文菜单出现,如下所示:
图 21:等待出现上下文菜单
该命令的 Target 值正是 clickAndWait 命令将要单击的那个元素。在您的浏览器中显示的 Department edit 页面上,您可以断言存在 Save 和 Cancel 按钮:右键单击,然后找到 Selenium 命令 assertText cb1 Save 和 assertText cb2 Cancel。
该测试的第 3 步是键入 Accounting2 作为 Department 的名称并单击 Cancel 按钮。您可以手动完成这一过程,而 Selenium IDE(仍处于记录状态)会将相应的命令插入到 UI 测试脚本中。您需要作点小改动,因为 Selenium 为按钮单击操作插入一个 click 命令,而不是我们需要的 clickAndWait 命令(该命令将等待请求处理完毕)。和前面一样,lazy 内容交付设置意味着我们需要一个显式的命令以等待该表加载进来,然后再断言 Department 名称未改变,还是“Accounting”。为了简单起见,您可以将 waitForElementPresent 和 assertText 命令复制并粘贴到测试脚本中,测试脚本现在应如下所示:
图 22:第 3 步之后的 UI 测试
现在可完成测试脚本的第 4 步和第 5 步,只需复制并粘贴相应的命令(不需要再进行任何更多的记录了)。完整的测试脚本应如下所示:
图 23:完整的 Oracle ADF Faces RC UI 测试
现在应能够通过 Selenium IDE 成功运行该测试了。您还可以创建一个 JUnit 测试类(如您在第一个测试中所作),整理该测试(添加 JUnit 批注并指定您要测试的网站的正确的基本 URL, http://localhost:7101),然后在 Oracle JDeveloper 中成功地运行该测试类。该测试类的最终版本包含在本文随附的 Oracle JDeveloper 11g Application Workspace 中。
大多数情况下,我建议使用 Selenium IDE 进行测试用例的粗略的初始记录,将测试用例导出为 JUnit 格式,然后在 Oracle JDeveloper 中修改和重新调整这些测试。Selenium IDE 在记录测试用例时使用“Selenese”格式,该格式是基于 HTML 的,无法处理复杂的构造,如方法调用/子例程、循环等。Selenium RC 文档中有一节内容介绍了其中的一些优点。还有一节有关测试设计考虑因素的内容给出了编写测试用例的一些最佳实践。
至此,您已了解如何使用 Selenium 创建基本的 UI 测试,您可以进一步创建更高级的可在不同浏览器上进行的测试并编写更复杂的测试用例。正如您所看到的,为了构建强健有效的 UI 测试,有不少的细微之处需要您来发现和了解。您应阅读 Selenium 文档来了解编写 UI 测试脚本时可使用的命令的范围。另外,对于大多数的开源项目来说,互联网提供了良好的信息来源(从其他用户编撰的博文到交互式论坛)。
在本系列的最后一篇文章中,您将学习如何使用 Hudson 持续集成服务器来使单元测试、构建和部署到测试应用服务器以及对您的 Oracle ADF 应用程序的 UI 测试自动化进行。
John Stegeman ( http://stegemanoracle.wordpress.com) 是 Cambridge Solutions(一家跨国 BPO 和 IT 服务公司)的 EMEA 地区的 Oracle ACE 总监(Oracle 融合中间件)兼首席架构师。他从 1990 年开始就使用 Oracle 产品工作,从版本 3 开始就使用 Oracle JDeveloper 了。