追求代码质量 – 利用Selenium和TestNG举办编程式测试
副标题#e#
Selenium 是一种 Web 测试框架,它搭建了验证 Web 应用措施的新途径。与 大大都实验模仿 HTTP 请求的 Web 测试东西差异,Selenium 执行 Web 测试时 ,就似乎它自己就是欣赏器。当运行自动的 Selenium 测试时,该框架将启动一 个欣赏器,并通过测试中描写的步调实际驱动欣赏器,用户将利用这种方法与应 用措施交互。
由于开拓人员和非开拓人员都可以或许利用 Selenium 轻松地编写测试,使得它 从浩瀚测试框架应用措施中脱颖而出。在 Selenium 中,可以通过编程的方法编 写测试,可能利用 Fit 样式的表,而且编写了测试后,可以使测试完全自动化 。利用一个 Ant 构件(例如说)运行完整的 Selenium 套件很是简朴,而且还 可以在一连集成(Continuous Integration,CI)情况中运行 Selenium 测试。
这个月,我将先容 Selenium,并逐一查察使它成为优秀 Web 测试框架的一 些特性 —— 尤其是在团结利用 TestNG、DbUnit 和 Cargo 这样的软件时。
验收测试
由于 Selenium 可以或许很好地模仿用户的行为,它经常用于举办验 收测试,即在完成的系统上运行一整套测试。验收测试凡是需要运行整个应用程 序,以使测试发挥浸染。假如您要测试一个 Web 应用措施,则需要会见应用程 序数据库,以及一台 Web 处事器,一个容器和运行应用措施所需的任何设置元 素。
利用 Selenium 举办编程式测试
在 Selenium 中,您可以利用本身喜爱的语言可能 Fit 样式的表通过编程来 编写测试。从测试的角度来说,不管利用什么语言,测试进程和功效都不会有显 著的不同。在此,我但愿研究 Selenium 的编程要领,因为在团结利用 TestNG 时,它提供了一些有趣的可行要领能性。
利用具有雷同 TestNG 这样的框架的 Selenium 举办编程式测试具有这样一 个利益,它答允您建设智能 fixture,而利用 Fit 样式的表则很难做到这一点 。TestNG 尤其适合与 Selenium 团结利用,因为它使您可以或许完成其他框架无法 做到的测试,譬喻利用依赖项举办测试,从头运行失败了的测试,以及利用单独 文件中界说的参数举办参数化测试。所有这些特性团结在一起,虽然可以或许使它在 浩瀚 Web 应用措施测试框架中脱颖而出,可是,正如您将看到的,在完全自动 化的验收测试中利用这些特性令它越发出众。
设置第一个测试
Selenium 架构实际上由两个逻辑实体构成:您编写的代码以及可以或许简化与测 试中的应用措施的交互的 Selenium 处事器。要乐成地执行测试,必需要启动并 运行 Selenium 处事器实例以及要测试的应用措施。(虽然,测试功效取决于您 编写的应用措施是否优秀!)
幸运的是,Selenium 处事器是一种轻量级措施,可以在实际的测试范畴内通 过编程启动和遏制它。Selenium 处事器(利用 Selenium 工具嵌入)的启动和 遏制由一个 fixture 来执行。
要通过编程的方法启动 Selenium 处事器,必需建设一个新的 Selenium 对 象,并汇报它要利用哪一种兼容的欣赏器 —— 我在下面的示例中利用的是 Firefox。您还必需提供运行处事器实例的位置(凡是是 localhost,但不是必 须的),以及被测试的应用措施利用的基 URL。
在清单 1 中,我设置了一个当地 Selenium 实例,利用它在当地安装的 Web 应用措施上驱动 Firefox(http://localhost:8080/gt15/)。正如您从参数中 揣度的一样,Selenium 是作为被测试的应用措施的署理,并相应地促进测试。
清单 1. 设置 SeleniumServer
Selenium driver =
new DefaultSelenium("localhost", SeleniumServer.getDefaultPort(),
"*firefox", "http://localhost:8080/gt15/");
driver.start();
//go to web pages and do stuff...
driver.stop();
建设了 Selenium 实例后,您可以 启动并在运行时 遏制它。这意味着您可 以通过编程与 Selenium 处事器交互,并通过一个测试措施使它驱动欣赏器。
#p#副标题#e#
驱动应用措施
通过编程与 Web 页面举办交互是一种利用当地 id 的应用。与页面元素举办 交互的第一步就是查找该元素,凡是可以利用 HTML 元素 ID 举办查找。 Selenium 还答允您利用 XPath、正则表达式,甚至是 JavaScript 来查找特定 的元素(假如您但愿这样做)。
清单 2 所示的 HTML 是利用 Groovlet 的简朴 Web 应用措施的一部门。这 段代码界说了包括输入和提交按钮的表单。假如但愿 Selenium 与该表单交互, 我必需为输入按钮提供 ID 以及相应的值。我还需要为提交按钮提供一个 ID, 这样 Selenium 才气 “单击” 它。单击按钮后,表单将被提交给 Groovlet — — 本例中为 FindWidget.groovy。
清单 2. 简朴的 HTML 表单
#p#分页标题#e#
<form method=post action="./FindWidget.groovy">
<table border="0" style="border-style: dotted">
<tr>
<td class="heading">Widget:</td>
<td class="value"><input type="text" name="widget"></td>
</tr>
<tr>
<td></td>
<td class="value"><input type="submit" value="Find Description" name="submit"></td>
</tr>
</table>
</form>
此刻就可以通过利用 ID widget(输入值)和 submit(单击按钮)与该 HTML 表单举办编程式交互,如清单 3 所示:
清单 3. 驱动简朴的 Web 页面
driver.type("widget", "pg98- 01");
driver.click("submit");
driver.waitForPageToLoad("10000");
//assert some return value...
Selenium 顶用于和 Web 页面元素举办交互的 API 很是的直观。对付输入字 段,我可以利用 type() 要领将值与 ID 关联起来。假如需要的话,可以通过编 程 click 按钮。在清单 3 中,我将 click 配置为 10 秒的期待时间 —— 足 够表单提交请求完成处理惩罚。当 FindWidget.groovy 中的代码运行其内容并返回 响应后,我可以利用该响应来查找特定页面元素,并验证所有内容是否正常事情 。
Selenium 和 TestNG
TestNG 以其机动性和参数化 fixture 成为界说 Selenium 的驱动验收测试 的首选。TestNG 可以或许界说测试依赖项并返回失败的测试,以及其易用性,使得 Selenium-TestNG 成为吸引人的组合。
让我们首先从一个可以或许答允用户建设、查找、更新或删除小部件的 Web 应用 措施开始。建设一个小部件需要三个属性:名称、范例和界说。图 1 显示了创 建小部件的表单:
图 1. 建设小部件的 Web 表单
请留意:表单位素的范例是具有三个差异选项的下拉列表,如图 2 所示:
图 2. 包括下拉列表的 Web 表单
单击 Create Widget 将促使 Groovlet 处理惩罚这一请求。假如所有内容正确的 话(即名字和界说不为空,而且数据库中不存在该实例),Groovlet 将建设一 个新的小部件实例并雷同图 3 所示的状态页面:
图 3. 返回的 Web 页面显示状态
团结利用 Selenium 和 TestNG 验证简朴的 Create Widget 用例是一种可管 理的应用:
设置并启动 Selenium 处事器的实例。
与 Create Widget Web 表单交互并提交它。
检讨功效页面是否包括具有小部件名称的乐成信息。
遏制 Selenium 处事器实例。
请留意:用例中的每一步都是通过 Selenium 完成的 —— 所以说,TestNG 仅仅辅佐举办查找。此刻,我们来实践一下。
Create Widget 测试用例
我但愿对 Selenium 处事器举办机动的设置,所以我将编写一个参数化 fixture(TestNG-Selenium 样式),一般可以利用它来为差异欣赏器、差异位 置甚至殽杂的 Web 应用措施地点(雷同 localhost 和产物)建设 Selenium 服 务器。清单 4 界说了我所设置的机动的 Selenium 处事器 fixture:
清单 4. 机动的 Selenium fixture
@Parameters({"selen-svr- addr","brwsr-path","aut-addr"})
@BeforeClass
private void init(String selenSrvrAddr, String bpath,
String appPath) throws Exception {
driver = new DefaultSelenium(selenSrvrAddr,
SeleniumServer.getDefaultPort(), bpath, appPath);
driver.start();
}
//....
@AfterClass
private void stop() throws Exception {
driver.stop();
}
必需将参数名与 TestNG 的 testng.xml 文件中的值链接起来;因此,我定 义了如清单 5 所示的三个参数。(默认环境下为 Firefox 界说了 brwsr-path 参数,可是我可以同样轻松地界说一组新的利用 Internet Explorer 的测试。 )
清单 5. TestNG testng.xml 文件中的参数值
<parameter name="selen-svr-addr" value="localhost"/>
<parameter name="aut-addr" value="http://localhost:8080/gt15/"/>
<parameter name="brwsr-path" value="*firefox"/>
#p#分页标题#e#
接下来,我将界说清单 6 所示的测试用例,它也包括一个参数,用于举办测 试的应用措施的基 URL。该测试将促使欣赏器在 Web 应用措施内打开特定页面 ,并操纵 图 1 所示的表单。
清单 6. 一个精采的测试用例
@Parameters({"aut-addr"})
@Test
public void verifyCreate(String appPath) throws Exception {
driver.open(appPath + "/CreateWidget.html");
driver.type("widget", "book-01");
driver.select("type", "book");
driver.type("definition", "book widget type book");
driver.click("submit");
driver.waitForPageToLoad("10000");
assertEquals(driver.getText("success"),
"The widget book-01 was successfully created.",
"test didn't return expected message");
}
通过挪用 driver.click("submit") 提交表单后,Selenium 将期待响应的加 载,然后我将断言乐成的建设信息。(留意:响应 Web 页面具有一个 ID 为 success 的元素。)
功效发生一个机动的文本类,它将检讨两种场景:一种是精采的场景,而另 一种是没有提供界说的界线用例,如清单 7 所示:
清单 7. 利用 TestNG 举办全部的处理惩罚
public class CreateWidgetUATest {
private Selenium driver;
@Parameters({"selen-svr-addr","brwsr-path","aut-addr"})
@BeforeClass
private void init(String selenSrvrAddr, String bpath,
String appPath) throws Exception {
driver = new DefaultSelenium(selenSrvrAddr,
SeleniumServer.getDefaultPort(), bpath, appPath);
driver.start();
}
@Parameters({"aut-addr"})
@Test
public void verifyCreate(String appPath) throws Exception {
driver.open(appPath + "/CreateWidget.html");
driver.type("widget", "book-01");
driver.select("type", "book");
driver.type("definition", "book widget type book");
driver.click("submit");
driver.waitForPageToLoad("10000");
assertEquals(driver.getText("success"),
"The widget book-01 was successfully created.",
"test didn't return expected message");
}
@Parameters({"aut-addr"})
@Test
public void verifyCreationError(String appPath) throws Exception {
driver.open(appPath + "/CreateWidget.html");
driver.type("widget", "book-02");
driver.select("type", "book");
//definition explicitly set to blank
driver.type("definition", "");
driver.click("submit");
driver.waitForPageToLoad("10000");
assertEquals(driver.getText("failure"),
"There was an error in creating the widget.",
"test didn't return expected message");
}
@AfterClass
private void stop() throws Exception {
driver.stop();
}
}
今朝为止,我已经界说了两种足够机动的 Selenium 测试,可以对多个欣赏 器举办测试,而且还可以对多个位置举办测试,这对初学者很是有利。尽量如此 ,我还想得到更高级点的应用,我开始思量测试中的逻辑是否可反复利用。好比 ,假如对一行运行两次 CreateWidgetUATest 测试类会奈何?如何确保我的 Web 应用措施运行的是当地呆板(或其他呆板)上最新版本的代码?
可反复 的验收测试
在执行 Selenium 测试时,必需运行 Selenium 处事器以及 要检讨的 Web 应用措施。言外之意,还必需运行应用措施中所有相关的架构依 赖干系 —— 对付大大都 Java™ Web 应用措施来说,即 Servlet 容器和相关的数据库。
正如在我的另一篇文章 repeatable system tests 中表明的一样,DbUnit 和 Cargo 是两种我最喜欢的技能,可以 在依赖数据库的 Web 应用措施中实现逻辑反复。DbUnit 打点数据库中的数据, 而 Cargo 使容器打点以通用的方法实现自动化。下面几节将向您展示如何团结 利用 Selenium 和 TestNG 从而确保实现逻辑反复的验收测试。
DbUnit 再次登场
#p#分页标题#e#
您大概追念起,DbUnit 通过有效地打点测试场景中的数据简化 了利用数据库的事情。通过利用 DbUnit,可以在测试前将一组已知的数据加载 到数据库中,这意味着您可以依赖这些在测试进程中泛起的数据。另外,在完成 测试后,还可以从数据库中删除测试功效发生的数据。DbUnit 作为一种利便的 fixture(JUnit 或 TestNG)简化了所有这些事情,它可以或许读取包括测试数据的 种子文件,逻辑插入、删除数据,或更新数据到相应的数据库表中。
由 于这里利用了 TestNG 驱动 Selenium,我将建设一个 DbUnit fixture,它将在 测试 级别上运行。TestNG 支持在五种粒度级别上运行 fixture。最低的两种级 别,要领和类是最常见的 —— 用于每个测试要领的 fixture 可能 用于整个类的 fixture。之后,TestNG 为一个测试荟萃(界说在 TestNG 设置 文件中并由 test 元素指定)界说了一个 fixture,为一组 测试(界说在 TestNG 的 Test 注释中)界说了一个 fixture。
测试细节
建设一个 DbUnit fixture 并在测试级别上运行,这意味着运行任何测试之 前,测试类的荟萃将共享沟通的逻辑,为数据库正确地播种。在本文的示例中, 在运行每个逻辑测试荟萃前,我但愿数据库具有一组清洁的数据。利用 DbUnit 的 CLEAN_INSERT 呼吁确保在先前运行的测试中建设的行被删除去 —— 因此, 我可以从头运行测试,该测试可以不绝建设数据而且不消思量数据库约束。
另外,我但愿 fixture 可以或许依赖参数化数据,这使我在运行某个测试之前, 可以或许机动地切换种子文件,甚至是特定命据库的位置。将 TestNG 与参数相关联 起来再简朴不外了:我所需做的仅仅是利用 Parameters 注释装饰 fixtrue,声 明要领签名中相应的参数,并提供 TestNG 设置文件中的值。
清单 8 界说了一个简朴的 DbUnit fixture,它利用所需的种子文件播种数 据库。请留意:该 fixture 被界说为包括五个 参数。(这大概很是多,可是在 fixture 中包括参数不是很好吗?)
清单 8. 测试荟萃的 DbUnit fixture
public class DatabaseFixture {
@Parameters({"seed-path","db-driver","db-url","db-user","db- psswrd"})
@BeforeTest
public void seedDatabase(String seedpath, String driver,
String url, String user, String pssword) throws Exception {
IDatabaseConnection conn = this.getConnection(driver, url, user, pssword);
IDataSet data = this.getDataSet(seedpath);
try {
DatabaseOperation.CLEAN_INSERT.execute(conn, data);
}finally {
conn.close();
}
}
private IDataSet getDataSet(String path) throws IOException, DataSetException {
return new FlatXmlDataSet(new File(path));
}
private IDatabaseConnection getConnection(String driver,
String url, String user, String pssword ) throws ClassNotFoundException,
SQLException {
Class.forName(driver);
Connection jdbcConnection =
DriverManager.getConnection(url, user, pssword);
return new DatabaseConnection(jdbcConnection);
}
}
要将实际的值与清单 8 中的参数相关联,我必需在 TestNG 的 testng.xml 文件中界说它们,如清单 9 所示:
清单 9. TestNG 的 testng.xml 文件中界说的特定于 DbUnit 的参数
<parameter name="seed-path" value="test/conf/gt15- seed.xml"/>
<parameter name="db-driver" value="org.hsqldb.jdbcDriver"/>
<parameter name="db-url" value="jdbc:hsqldb:hsql://127.0.0.1"/>
<parameter name="db-user" value="sa"/>
<parameter name="db-psswrd" value=""/>
通用参数值
此刻我已经界说了一个机动的 fixture,它将处理惩罚数据库状态和相应测试。 此刻可以筹备利用 TestNG 将所有内容毗连起来。凡是,第一步是相识但愿实现 的内容。在本例中,我想完成以下任务:
我但愿在运行任何逻辑测试荟萃前,DbUnit fixture 可以或许完本钱身任务。
我但愿将沟通的测试荟萃运行两次:一次用于 Firefox,一次用于 Internet Explorer。
TestNG 的 parameter 元素的浸染域是局部的,这对我来说是件功德。这样 ,我可以很容易地在 TestNG 设置文件中界说通用参数值,而且当需要时在 TestNG 的 test 组元素中重写它们。
#p#分页标题#e#
好比,要运行两组测试,简朴建设两个 test 元素。我可以通过 TestNG 的 package 元素将我的 fixture 和相关测试包罗进来,package 元素可以或许使包结 构中所有测试(或 fixture)的查找变得简朴。接着,我可以在两个界说了的 test 组中将 Firefox 和 Internet Explorer 的 brwsr-path 参数关联起来。 所有这些都显示在了 testng.xml 文件中,如清单 10 所示:
清单 10. 使 DbUnit 运行的机动的 testng.xml 文件
<suite name="User Acceptance Tests" verbose="1" >
<!-- required for DbUnit fixture -->
<parameter name="seed-path" value="test/conf/gt15- seed.xml"/>
<parameter name="db-driver" value="org.hsqldb.jdbcDriver"/>
<parameter name="db-url" value="jdbc:hsqldb:hsql://127.0.0.1"/>
<parameter name="db-user" value="sa"/>
<parameter name="db-psswrd" value=""/>
<!-- required for Selenium fixture -->
<parameter name="selen-svr-addr" value="localhost"/>
<parameter name="aut-addr" value="http://localhost:8080/gt15/"/>
<test name="GT15 CRUDs- Firefox" >
<parameter name="brwsr-path" value="*firefox"/>
<packages>
<package name="test.com.acme.gt15.Web.selenium" />
<package name="test.com.acme.gt15.Web.selenium.fixtures" />
</packages>
</test>
<test name="GT15 CRUDs- IE" >
<parameter name="brwsr-path" value="*iexplore"/>
<packages>
<package name="test.com.acme.gt15.Web.selenium" />
<package name="test.com.acme.gt15.Web.selenium.fixtures" />
</packages>
</test>
</suite>
我很兴奋地公布,我已经完成了建设一套可反复验收测试所需的所有工作。 剩下的东西就是处理惩罚 Web 应用措施容器自己。幸运地是,我可以利用 Cargo 来 完成。
Cargo 执行加载
Cargo 是一个创新的以通用方法自动化容器打点的开源项目,好比,用于将 WAR 文件陈设到 JBoss 的沟通 API 还可以启动和遏制 Tomcat。Cargo 还可以 自动下载并安装容器 —— Cargo API 的用途很遍及,从 Java 代码到 Ant 任 务,甚至是 Maven。
诸如 Cargo 这样的东西将处理惩罚编写逻辑反复测试用例所面临的一个大的挑战 ,它制止一种潜在的假设,即运行 的容器具有最新最好的应用措施代码。另外 ,还可以结构一个操作 Cargo 的本领自动完成以下任务的编译进程(譬喻在 Ant 内):
下载所需的容器。
安装该容器。
启动容器。
将选择的 WAR 或 EAR 文件陈设到容器上。
稍后,您还可以使 Cargo 遏制所选的容器。(而且,不需要对下载和安装容 器发出告诫,可能,假如当地呆板中已经存在了正确的版本,Cargo 将跳过步调 1 和 2。)
我但愿利用 Cargo 来确保启动并运行最新和最好的 Web 应用措施。而且, 我不需要思量在那边陈设 WAR 文件,可能必需确保正在利用的是最新的 WAR 文 件。我真正想到达的目标是利用户验收测试实现无事件 —— 我仅需要发出一个 呼吁,然后坐下来期待功效。甚至可以更好,在一个 CI 情况中,我不消期待; 当测试完成后我将得到一个通知!
测试容器打点
要在 Ant 内配置 Cargo,我需要界说一个任务,它将下载特定版本的 Tomcat 并将其安装到当地呆板上的姑且目次。接下来,将最新版本的代码陈设 到 Tomcat 上,如清单 11 所示:
清单 11. 配置 Cargo 的任务
<target name="ua-test" depends="compile-tests,war">
<taskdef resource="cargo.tasks">
<classpath>
<pathelement location="${libdir}/${cargo-jar}" />
<pathelement location="${libdir}/${cargo-ant-jar}" />
</classpath>
</taskdef>
<cargo containerId="tomcat5x" action="start" wait="false" id="${tomcat-refid}">
<zipurlinstaller installurl="${tomcat-installer-url}" />
<configuration type="standalone" home="${tomcatdir}">
<property name="cargo.remote.username" value="admin" />
<property name="cargo.remote.password" value="" />
<deployable type="war" file="${wardir}/${warfile}" />
</configuration>
</cargo>
<antcall target="_start-selenium" />
<cargo containerId="tomcat5x" action="stop" refid="${tomcat- refid}" />
</target>
清单 11 中的 target 利用 antcall 挪用另一个 target。实际上,清单 11 中最后的 cargo 任务封装了 _start-selenium target,而且确保运行测试后停 止 Tomcat。
#p#分页标题#e#
在清单 12 中界说的 _start-selenium target 中,我需要启动(并稍后停 止)Selenium 处事器。在此进程中,我的测试还将毗连到其 Selenium fixture 中的处事器实例。请留意:该 target 是如何引用另一个 target ——
清单 12. 启动和遏制 Selenium 处事器
<target name="_start-selenium">
<java jar="${libdir}/${selenium-srvr-jar}" fork="true" spawn="true" />
<antcall target="_run-ua-tests" />
<get dest="${testreportdir}/results.txt"
src="${selenium-srvr-loc}/selenium-server/driver/? cmd=shutDown" />
</target>
最后,该组中最后的 target 将通过 TestNG 实际运行我的编程式 Selenium 测试。留意,我是如何通过利用清单 13 中的 _run-ua-tests target 的 xmlfileset 元素,使 TestNG 利用我的 testng.xml 文件。
清单 13. 运行 TestNG testng.xml 文件中的测试
<target name="_run-ua-tests">
<taskdef classpathref="build.classpath" resource="testngtasks" />
<testng outputDir="${testreportdir}"
classpath="${testclassesdir};${classesdir}" haltonfailure="true">
<xmlfileset dir="./test/conf" includes="testng.xml" />
<classpath>
<path refid="build.classpath" />
</classpath>
</testng>
</target>
竣事语
正如您看到的一样,Selenium 极大地简化了用户验收测试,尤其当利用 TestNG 驱动的时候。固然编程式测试并不合用于所有人(非开拓人员大概更喜 欢 Selenium 的 Fit 样式的表),它确实让您相识到了 TestNG 不凡的机动性 。编程式测试还答允您利用 DbUnit 和 Cargo 构建本身的测试框架,从而确保 测试的逻辑可反复性。
开源 Web 测试框架的成长毫不会遏制,这对付追求代码质量的完美主义者是 个好动静。Selenium 是驱动欣赏器的开源 Web 测试框架中新呈现的东西之一, 它可以或许利用户验收测试自动化 —— 因此,它很是优秀。团结利用 Selenium 和 TestNG,正如我在本文中演示的一样,您将得到一个很是好的测试驱动,并从依 赖性测试以及参数测试中得到庞大的优势。实验利用 Selenium 和 TestNG 吧, 您的用户将为此感激您。