DbUnit 再次登场
您可能回想起,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 组元素中重写它们。
比如,要运行两组测试,简单创建两个 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。
在清单 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 吧,您的用户将为此感谢您。