可重复的系统测试

发表于:2008-04-03来源:作者:点击数: 标签:JWebUnit
在测试加入到 servlet 容器的 Web 应用程序时,编写符合逻辑的可重复的测试尤其需要技巧。在 Andrew Glover 的提高代码 质量 的这个续篇中,他介绍了 Cargo,这是一个以通用方式自动化容器管理的 开源 框架,有了这个框架,您可以随时编写符合逻辑的可重复的
在测试加入到 servlet 容器的 Web 应用程序时,编写符合逻辑的可重复的测试尤其需要技巧。在 Andrew Glover 的提高代码质量的这个续篇中,他介绍了 Cargo,这是一个以通用方式自动化容器管理的开源框架,有了这个框架,您可以随时编写符合逻辑的可重复的系统测试。

在本质上,像 JUnit 和 TestNG 一样的测试框架方便了可重复性测试的创建。由于这些框架利用了简单 Boolean 逻辑(以 assert 方法的形式)的可靠性,这使得无人为干预而运行测试成为可能。事实上,自动化是测试框架的主要优点之一 —— 我能够编写一个用于断言具体行为的相当复杂的测试,且一旦这些行为有所改变,框架就会报告一个人人都能明白的错误。

利用成熟的测试框架会带来框架 可重复性的优点,这是显而易见的。但逻辑的 可重复性却取决于您。例如,考虑创建用于验证 Web 应用程序的可重复测试的情况,一些 JUnit 扩展框架(如 JWebUnit 和 HttpUnit)在协助自动化的 Web 测试方面非常好用。但是,使测试的 plumbing 可重复则是开发人员的任务,而这在部署 Web 应用程序资源时很难进行。

实际的 JWebUnit 测试的构造过程相当简单,如清单 1 所示:


清单 1. 一个简单的 JWebUnit 测试
package test.come.acme.widget.Web;import net.sourceforge.jwebunit.WebTester;import junit.framework.TestCase;public class WidgetCreationTest extends TestCase { private WebTester tester; protected void setUp() throws Exception { this.tester = new WebTester(); this.tester.getTestContext(). setBaseUrl("http://localhost:8080/widget/"); } public void testWidgetCreation() { this.tester.beginAt("/CreateWidget.html"); this.tester.setFormElement("widget-id", "893-44"); this.tester.setFormElement("part-num", "rt45-3"); this.tester.submit(); this.tester.assertTextPresent("893-44"); this.tester.assertTextPresent("suclearcase/" target="_blank" >ccessfully created."); }}

这个测试与一个 Web 应用程序通信,并试图创建一个基于该交互的小部件。该测试随后校验此部件是否被成功创建。读过本系列之前部分的读者们也许会注意到该测试的一个微妙的可重复性问题。您注意到了吗?如果这个测试用例连续 运行两次会怎样呢?

由这个小部件实例(即,widget-id)的验证方面可以判断出,可以安全地做出这样的假设,即此应用程序中的数据库约束很可能会阻止创建一个已经存在的额外的小部件。由于缺少了一个在运行另一个测试前删除此测试用例的目标小部件的过程,如果再连续运行两次,这个测试用例非常有可能会失败。

幸运的是,如前面文章中所探讨的那样,有一个有助于数据库-依赖性(database-dependent)测试用例可重复性的机制 —— 即 DbUnit。

使用 DbUnit

改进 清单 1 中的测试用例来使用 DbUnit 是非常简单的。DbUnit 只需要一些插入数据库的数据和一个相应的数据库连接,如清单 2 所示:


清单 2. 用 DbUnit 进行的数据库-依赖性测试
package test.come.acme.widget.Web;import java.io.File;import java.io.IOException;import java.sql.Connection;import java.sql.DriverManager;import java.sql.SQLException;import org.dbunit.database.DatabaseConnection;import org.dbunit.database.IDatabaseConnection;import org.dbunit.dataset.DataSetException;import org.dbunit.dataset.IDataSet;import org.dbunit.dataset.xml.FlatXmlDataSet;import org.dbunit.operation.DatabaseOperation;import net.sourceforge.jwebunit.WebTester;import junit.framework.TestCase;public class RepeatableWidgetCreationTest extends TestCase { private WebTester tester; protected void setUp() throws Exception { this.handleSetUpOperation(); this.tester = new WebTester(); this.tester.getTestContext(). setBaseUrl("http://localhost:8080/widget/"); } public void testWidgetCreation() { this.tester.beginAt("/CreateWord.html"); this.tester.setFormElement("widget-id", "893-44"); this.tester.setFormElement("part-num", "rt45-3"); this.tester.submit(); this.tester.assertTextPresent("893-44"); this.tester.assertTextPresent("successfully created."); } private void handleSetUpOperation() throws Exception{ final IDatabaseConnection conn = this.getConnection(); final IDataSet data = this.getDataSet(); try{ DatabaseOperation.CLEAN_INSERT.execute(conn, data); }finally{ conn.close(); } } private IDataSet getDataSet() throws IOException, DataSetException { return new FlatXmlDataSet(new File("test/conf/seed.xml")); } private IDatabaseConnection getConnection() throws ClassNotFoundException, SQLException { Class.forName("org.hsqldb.jdbcDriver"); final Connection jdbcConnection = DriverManager.getConnection("jdbc:hsqldb:hsql://127.0.0.1", "sa", ""); return new DatabaseConnection(jdbcConnection); }}

加入了 DbUnit,测试用例真的是可重复的了。在 handleSetUpOperation() 方法中,每当运行一个测试用例时,DbUnit 对数据执行一个 CLEAN_INSERT。此操作本质上将一个数据库的数据清空并插入一个新的数据集,从而删除任何之前创建的小部件。

再一次探讨什么是 DbUnit?

DbUnit 是一个 JUnit 扩展,用于在运行测试时将数据库放入一个已知状态中。开发人员使用 XML 种子文件将特定数据插入到测试用例所依赖的数据库中。因而,DbUnit 便利了依赖于一个或多个数据库的测试用例的可重复性。

但那并不意味着已经结束了对测试用例可重复性这一话题的探讨。事实上,一切才刚刚开始。

重复系统测试

我喜欢将 清单 1 和 清单 2 中定义的测试用例称为系统测试。因为系统测试运行安装完整的应用程序,如 Web 应用程序,它们通常包含一个 servlet 容器和一个相关联的数据库。这些测试的目的在于校验那些设计为端对端操作的外部接口(如 Web 应用程序中的 Web 页面)。

弹性优先级
作为总体规则,应在任何可能的时候避免测试用例继承。许多 JUnit 扩展框架都提供特定的可继承测试用例,以便利于测试一个特定的架构。然而由于 Java™ 平台的单一继承范例,使得从框架中继承类的测试用例饱受缺乏弹性之苦。通常,这些相同的 JUnit 扩展框架提供了代理 API,这使得联合各种不具有严格继承结构的框架变得十分简单。

由于设计它们的目的是为了测试功能完整的应用程序,因而系统测试趋向于增加运行次数而不是减少设置测试的总时间。例如,清单 1 和 清单 2 中展示的逻辑测试在运行前 需要下列步骤:

创建一个 war 文件,该文件包含所有相关 Web 内容,如 JSP 文件、servlet、第三方的 jar 文件、图像等。

将此 war 文件部署到目标 Web 容器中。(如果该容器尚未启动,启动该容器。)

启动任何相关的数据库。(如果需要更新数据库模式,在启动前进行更新。)

现在,对于一个微不足道的小测试要做大量的辅助性工作!如果证明这个过程是耗时的,那么您认为这个测试会间隔多长时间运行一次呢?面对要使系统测试在逻辑上可重复(在一个连续的集成环境中)这一需求,这个步骤列表的确令人望而生畏。

原文转自:http://www.ltesting.net