极限编程方法的兴起将测试驱动开发和持续集成带入了主流 Java 开发实践。如果没有采用正确的工具,在 Java 服务器端开发中使用这些技术很快会成为一场噩梦。在本文中,软件开发人员 Philippe Girolami 描述了如何处理持续集成,以及如何联合使用 DbUnit 和 JUnit,以便在每次测试之前通过设置数据库状态来端到端地控制测试环境。
软件开发中最重要的一种做法就是测试。通过推荐测试优先的开发和持续集成,极限编程(Extreme Programming,XP)将这一逻辑推到了极限,在这里测试是尽可能频繁地自动进行的。不过,大多数非 XP 开发都进行了某种形式的测试,也许称为非回归测试、黑箱测试、功能测试或者其他的名字。很多项目使用关系数据库存储数据,因而所有测试策略都需要考虑在每次测试过程中数据库中所发生的事情:如果测试使测试数据库处于不一致状态,那么后面的所有测试都可能失败!一种避免这种情况的方法是在每次测试之前将数据库状态设为一个已知的相关状态。在本文中,我将介绍我们的小组是如何结合 JUnit 使用 DbUnit 做到这一点的,以及如何用 Anthill 自动生成测试报告。尽管设置看起来很费功夫,但是实际上并不是这样,并且它已经证明自己是一个有用的工具。
表示数据库内容
DbUnit 扩展了 JUnit,它使数据库在测试之间处于一种已知状态,帮助避免造成后面的测试失败或者给出错误结果的问题,如果测试会破坏数据库就会出现这些问题。它可以读取表的内容并用 FlatXmlDataSet 将它在存储为 XML,如清单 1 所示:
清单 1. FlatXmlDataSet 示例
<dataset>
<OPERATOR
ID='APC (Washington/Baltimore)'
CODE='ABC5APC'
ENCODED_STRING='aabbcc'/>
<OPERATOR
ID='ASA Ritabell'
CODE='ABC6ASA R'
ENCODED_STRING='bbccdd'/>
<OPERATOR
ID='Advanced Info. Service PLC'
CODE='ABC1Adva'
ENCODED_STRING='ccddee'/>
<OPE_OPERATOR
ID='Aerial Communications Inc.'
CODE='ABC2Aeri'
ENCODED_STRING='ddeeff'/>
</dataset>
这个数据集表示名为 OPE_OPERATOR 的数据库表中的三列,如表 1 中最后三行所描述的:
表 1. 清单 1 中数据的表定义
OPE_OPERATOR
ID INT
CODE VARCHAR
ENCODED_STRING VARCHAR
每个 XML 实体标识数据库中的一个表,而每个属性表示一列的值。
在自己的项目中设置 DbUnit
设置 DbUnit 很简单。有关项目文件下载的信息,请参阅 参考资料的内容。可以将所有三个 JAR 文件加到项目的编译目标中以进行测试。
如果是一个多 schema 环境,那么要将 DbUnit.qualified.table.names 属性设置为 true 。使用 Oracle 的开发团队通常是这种情况:每一个用户有自己的 schema。这可以使您免于在每个表名前面加上 schema 名称的前缀,并可以在团队中共享测试数据。
回页首
查询表的内容
DbUnit 使您可以容易地执行 JDBC 查询并获取它们的值。使用 DbUnit JDBC 包装器而不是纯粹的 JDBC 有几个理由:
可以用 SQL 查询创建一个 Dataset ,并使用 DbUnit 的 assertion(断言)方法(在后面描述)。
可以用 SQL 查询创建一个 Dataset ,并将它保存为一个 FlatXmlDataSet 。可以在以后将它重新装载到数据库中。
可以容易地从任何行中获取列的内容,无需进行迭代。
清单 2 中的代码创建一个结果 ITable,它包含了查询的结果。首先检查行计数是否为 1,然后检查第一行(从 0 开始计)中, FK_OTHER_ID 列包含数字 1234。
清单 2. DbUnit 的查询功能
String query = "SELECT * FROM MEDIA WHERE ID= "+id;
ITable databaseData =
dbConnection.createQueryTable("EXPECTED_DATA",query);
assertEquals(1, databaseData.getRowCount());
BigDecimal foreignKey = (BigDecimal) databaseData.getValue(0,
"FK_OTHER_ID");
assertEquals(new BigDecimal(1234)), foreignKey);
回页首
使用 assert 方法检查数据库内容
DbUnit 有断言方法,如清单 3 所示的那些,可以用于比较表的两组数据或者表的两个表示。如果需要在运行一次测试而不是多次查询后检查表的确切内容,一般会用它们。
清单 3. DbUnit 的附加断言方法
public static void assertEquals(ITable expected, ITable actual);
public static void assertEquals(IDataSet expected, IDataSet actual);
回页首
创建数据
根据数据库的大小、架构的稳定性如何以及开发的进展情况,可能要从头开始创建或者从生产数据库中拷贝测试数据。清单 4 显示了从已经存在的数据库中提取内容的例子(可以在 清单 6中找到 getConnection() 方法):
清单 4. 用现有的数据库创建 FlatXmlDataSets
public void extractTables(String targetDirectory,String[] tableNames)
throws Exception {
IDatabaseConnection connection = getConnection();
for (int i = 0; i < tableNames.length; i++) {
String tableName = tableNames[i];
IDataSet partialDataSet = connection.createDataSet
(new String[] { tableName });
FlatXmlDataSet.write
(partialDataSet, new FileOutputStream
(targetDirectory + "/" + tableName + ".xml"));
}
}
如果导出一个完整的生产数据库,可能必须要删除过多的行--或者像在这里一样,用一个查询而不是直接用连接创建一个数据集。提取本身对于很大的表来说可能是个问题--我们的小组只能用查询提取某些表的一部分。从表中删除行也有些问题,主要涉及到浏览所有外键并保证数据的一致性的困难。
回页首
添加测试数据
添加测试数据有时可能乏味的。我们的经验是度过正确添加数据的最初困难阶段后,就可以达到这样一个层次,不仅添加数据变得容易了,而且对数据库结构的理解也有了极大提高。
即使使用 Enterprise JavaBeans (EJB) 技术隐藏数据库,这种第一手知识仍然非常有用。因为开发人员对数据库有了更好的理解,因而可以更快地检查其内容,从而使调试更容易了。这在重构代码和数据库时又会给予我们极大的帮助。
回页首
用 DbUnit 和 JUnit 创建基类
好的 JUnit 实践鼓励开发人员扩展基类 TestCase 以获得特化(specialization)行为。DbUnit 提供了自己的特化-- DatabaseTestCase ,通过它可以特化行为以满足自己的需要。
首先,创建一个名为 ProjectDatabaseTestCase 的基本测试用例,并向它添加实用工具方法,如清单 5 所示。然后,重新定义 setUp() 和 teardown() ,以使它们能够创建和销毁通过 DbUnit 到数据库的连接。
清单 5. 基类定义和设置数据库的基本方法
public class ProjectDatabaseTestCase extends DatabaseTestCase
{
/** Use this connection to perform database setup */
protected IDatabaseConnection connection;
public DatabaseTestCase (String s)
{
super(s);
}
protected void setUp() throws Exception
{
super.setUp();
connection = getDbUnitConnection();
}
protected void tearDown() throws Exception
{
connection.close();
super.tearDown();
}
}
清单 6 显示了前述方法使用的类中的不同方法:
清单 6. 不同的实用工具方法
/**
* This method returns a DbUnit database connection
* based on the schema name
*/
private IDatabaseConnection getDbUnitConnection() throws Exception
{
IDatabaseConnection connection = new DatabaseConnection (getJDBCConnection(), getSchemaName());
return connection;
}
private IDataSet getFlatXmlDataSet(String tableName) throws Exception
{
URL url = DatabaseTestCase.class.getResource( "/"+ tableName + ".xml");
if (url == null)
throw new Exception("could not find file for " + tableName);
File file = new File(url.getPath());
return new FlatXmlDataSet(file);
}
/** Implement yourself */
private Connection getJDBCConnection() throws Exception
{
/* Get your JDBC connection through a data source of JDBC itself */
}
* Implement yourself */
private Connection getSchemaName() throws Exception
{
}
上述代码中应当注意的一些内容:
没有显示 getJDBCConnection() 方法,因为它的实现取决于希望如何获得 JDBC 连接:当 DataSource 为 Serializable 时通过应用服务器的 JNDI 树,或者直接使用 JDBC。
getDbUnitConnection() 方法返回 DbUnit 的一个到数据库的连接。DbUnit 的 DatabaseConnection 构造函数可以带一个 schema 名作为参数。这样,就不必在所有表名前面加上 schema 名的前缀了。
getFlatXmlDataSet() 方法用位于类路径上的一个 XML 文件的内容创建 DbUnit 数据集。
最后,该实际将数据插入测试表中。DbUnit 可以有不同的数据库操作,我使用了其中的两种:
DELETE_ALL ,它删除表中所有行。
CLEAN_INSERT ,它删除表中所有行并插入数据集提供的行。
ProjectDatabaseTestCase 中的下面四个方法可以满足您的需要:
insertFileIntoDb() :在数据库中插入文件。
emptyTable() :清理数据库表。
insertAllFilesIntoDb() :插入项目的所有文件。
emptyAllTables() :清理项目的所有表。
清单 7 显示了这些方法的使用:
清单 7. 底层测试用来设置数据库的方法
/** A method to insert all tables into the database.
* Specify all tables to be inserted
*/
protected void insertAllFilesIntoDb() throws Exception
{
insertFileIntoDb("PRODUCT");
(...)
insertFileIntoDb("ACCOUNT");
}
/**
* This method inserts the contents of a FlatXmlDataSet file
* into the connection
*/
protected void insertFileIntoDb(String tableName) throws Exception
{
DatabaseOperation.CLEAN_INSERT.execute(connection,getFlatXmlDataSet(tableName));
}
/** Empty a table */
protected void emptyTable(String tableName) throws Exception
{
IDataSet dataSet = new DefaultDataSet(new DefaultTable(tableName));
DatabaseOperation.DELETE_ALL.execute(connection, dataSet);
}
/** Empty all the tables from the database */
protected void emptyAllTables() throws Exception
{
emptyTable("ACCOUNT");
(...)
emptyTable("PRODUCT");
}
回页首
合到一起
完成了基类后,用 DbUnit 干净地建立数据库,执行一个方法,并检查返回值是很容易的事,如清单 8 所示:
清单 8. 在实际的测试用例中合到一起
public void setUp() throws Exception
{
super.setUp();
emptyAllTables();
service = Service.getInstance();
}
public void testFindProductByPrimaryKey() throws Exception
{
insertFileIntoDb("PRODUCT");
ProductDTO productDTO = service.findProductByPrimaryKey(new Integer(12));
assertNotNull(productDTO);
assertEquals("product Name", productDTO.getName());
}
public void testCreateAProduct() throws Exception
{
service.createProduct("newly created product name");
String query = "SELECT * FROM PRODUCT";
ITable databaseData = dbConnection.createQueryTable("EXPECTED_DATA",query);
assertEquals(1, databaseData.getRowCount());
String productName = (String) databaseData.getValue(0, "NAME");
assertEquals("newly created product name", productName);
}
在这个测试中,我清空了数据库,插入一个表的内容,并通过检查它返回的元素是否有正确的属性来检查用主键查找产品的 finder 方法是否正常工作。然后测试对象创建工作,并用 DbUnit 的查询程序验证数据库的内容。
需要注意的一件重要事情是,清理数据库是在建立测试而不是结束时进行的。我不想依赖于每次测试都干净地结束。
在插入数据时要关注的事情
数据库完整性约束迫使您以给定的顺序插入或者删除数据。在编写 insertAllFiles() 和 emptyAllTables() 方法时,您会发现顺序并非是随意的,事实上它是由完整性约束所限定的。
另一个潜在的陷井是,一些列可能看来没有插入。几乎总是会出现这种情况,因为在 FlatXmlDataSet 中的第一行缺少一列。看来 DbUnit 不能识别所有其他行中的这一列。例如,插入清单 9 中定义的数据集会使表 ACC_ACCOUNT 包含两行,它们惟一的非空列是主键 PK_ACC_ID :
清单 9. NAME of ACCOUNT 列的第二行会缺失
<dataset>
<ACCOUNT ID='1' />
<ACCOUNT ID='2' NAME='first name' />
</dataset>
总是要保证第一行描述包含表中的所有列。如果需要插入一个 NULL 值,要使这一行成为第二行,如清单 10 所示:
清单 10. NAME of ACCOUNT 列的第二行将不会缺失
<dataset>
<ACCOUNT ID='2' NAME='first name' />
<ACCOUNT ID='1' />
</dataset>
回页首
组织测试数据
DbUnit 可以在文件中存储 XML 数据集。它甚至允许在一个文件中存储整个数据库。清单 11 显示了表 ACCOUNT 和 MEDIA 的内容:
清单 11. 两个表的 FlatXmlDataSet 示例
<dataset>
<ACCOUNT NAME='first name' />
<ACCOUNT NAME='second name' />
<MEDIA ID='123' />
<MEDIA ID='234' />
</dataset>
决定如何存储测试数据很重要。是将每一个表的内容存储到单独的文件中,还是将与系统主要实体有关的所有表的所有行存储到一个文件中?它们都不是完美的解决方案(silver bullet)。
对于第一种情况,保证跨表的数据一致更困难,但是用一个已经存在的数据库创建查询更容易。在第二种情况下,为每一个测试创建测试集更容易,但是事实上大多数系统不是围绕一个主要实体设计的,因此这使它不那么实用。我们的方法是每个表有一个文件。
用 Anthill 实现持续集成
Anthill 是一个免费的自动构建工具(请参阅 参考资料),它规划您的构建并发布结果,帮助精通 XP 的小组使用持续集成。一次构建包含用 CVS 这样的版本控制工具检查源代码、运行一个构建脚本、发布结果并通知用户结果。它很好地与 ANT 集成,使您可以重用常用的构建脚本。
回页首
在 Anthill 中运行测试包并报告结果
XP 专家一直建议将持续集成作为确保减少集成错误一种方式:通过以足够高的频率集成所有代码,保证容易追溯到源代码中的问题。集成可能是非常耗时的任务--检查、构建和部署代码,然后运行验收试验。幸运的是,其中大多数可以用 Anthill 或者 CruiseControl 这样的工具自动化。如果还没有使构建过程自动化(例如用 Ant),那您应当这样做。如果构建过程是自动化的,应当在构建中加入一个测试部分。如果您是顽固的 XP 用户,这些应当是您的验收测试。如果您像我们一样,那么这些就是您要编写的所有测试--不管是单元、验收或者其他测试。
我们的构建过程基于 Ant 并计划使用 Anthill。我们的主要挑战是让 Anthill 报告失败的测试并且仍然发布测试结果。Anthill 捕获的是:如果构建脚本失败,就不执行发布脚本,在这种情况下不能将测试报告提供给开发人员。我们的方法是让 Anthill 检查属性为 true 还是 false,而使它在发布脚本的最后才失败。
运行测试的目标
下面是关于运行测试的简要总结。我们使用的是最批量化的方法,但是任何方法都可以工作。要点有:
测试必须具有分支,以便在类路径包含 JDK 1.3 中的 XML 解析器时可以正常工作。
如果出现错误或者失败,则 testsuite.error 和 testsuite.failure 属性必须设置为 true。如果没有错误或者失败的话,则不改变它们。
清单 12 显示了运行特定模块的所有测试的例子:
清单 12. 运行一个模块的测试
<target name="test-common">
<mkdir dir = "${project.reports}/common"/>
<junit fork="true" errorproperty="testsuite.error" failureproperty="testsuite.failure">
<classpath>
<pathelement location="${out.classes.dir}"/>
<fileset dir = "${shared.lib.dir}">
<patternset refid="necessary.jars"/>
</fileset>
</classpath>
<formatter type="xml"/>
<batchtest todir="${project.reports}/common">
<fileset dir="${out.src.dir}">
<include name="**/Test*.java"/>
</fileset>
</batchtest>
</junit>
</target>
使测试结果可被发布脚本使用
清单 13 显示了如何在编译过程中运行所有测试:
清单 13. build.xml 的代码片段:运行所有的测试并设置结果
<target name = "all-tests" depends = "test-module1,test-module2">
<property name="testsuite.error" value="false"/>
<property name="testsuite.failure" value="false"/>
<propertyfile file="${deployDir}/tests.results">
<entry key="testsuite.error" value="${testsuite.error}"/>
<entry key="testsuite.failure"
value="${testsuite.failure}"/>
</propertyfile>
</target>
一个需要了解的重要的 Ant 技巧是,Ant 只在属性没有值时才设置属性的值。所以在依次运行每一个测试时, testsuite.error 和 testsuite.failure 属性只有当出现错误或者失败时才会是 true。
这里的困难是向主 Ant 脚本报告测试脚本的结果。不幸的是,这并不是一项简单的任务,因为在 Anthill 的过程中有两个不同的 Ant 构建文件,在 Ant 中不能在构建脚本之间传递这种参数。不过,有一个“简单”的解决方案:将测试的结果保存到文件中,之后发布脚本读取这个文件。
清单 13 使用了这种 Ant 技巧,它显示了如何使用 <property> 命令保证 testsuite.error 和 testsuite.failure 属性在测试脚本结束时总是有一个值,以及如何将它保存为文件。
如果测试失败,使发布脚本在结束时失败
用清单 14 让发布脚本在任何测试失败时均失败。这只不过是为了检查在构建脚本中保存的每一个属性是否为 true。
清单 14. 当有错误或者失败时使发布脚本失败
<condition property="must.fail">
<or>
<istrue value="${testsuite.error}"/>
<istrue value="${testsuite.failure}"/>
</or>
</condition>
<fail message="Tests didn't run 100%. Check the log and make
necessary changes!" if="must.fail"/>
http://www.ibm.com/developerworks/cn/java/j-dbunit/
软件开发中最重要的一种做法就是测试。通过推荐测试优先的开发和持续集成,极限编程(Extreme Programming,XP)将这一逻辑推到了极限,在这里测试是尽可能频繁地自动进行的。不过,大多数非 XP 开发都进行了某种形式的测试,也许称为非回归测试、黑箱测试、功能测试或者其他的名字。很多项目使用关系数据库存储数据,因而所有测试策略都需要考虑在每次测试过程中数据库中所发生的事情:如果测试使测试数据库处于不一致状态,那么后面的所有测试都可能失败!一种避免这种情况的方法是在每次测试之前将数据库状态设为一个已知的相关状态。在本文中,我将介绍我们的小组是如何结合 JUnit 使用 DbUnit 做到这一点的,以及如何用 Anthill 自动生成测试报告。尽管设置看起来很费功夫,但是实际上并不是这样,并且它已经证明自己是一个有用的工具。
表示数据库内容
DbUnit 扩展了 JUnit,它使数据库在测试之间处于一种已知状态,帮助避免造成后面的测试失败或者给出错误结果的问题,如果测试会破坏数据库就会出现这些问题。它可以读取表的内容并用 FlatXmlDataSet 将它在存储为 XML,如清单 1 所示:
清单 1. FlatXmlDataSet 示例
<dataset>
<OPERATOR
ID='APC (Washington/Baltimore)'
CODE='ABC5APC'
ENCODED_STRING='aabbcc'/>
<OPERATOR
ID='ASA Ritabell'
CODE='ABC6ASA R'
ENCODED_STRING='bbccdd'/>
<OPERATOR
ID='Advanced Info. Service PLC'
CODE='ABC1Adva'
ENCODED_STRING='ccddee'/>
<OPE_OPERATOR
ID='Aerial Communications Inc.'
CODE='ABC2Aeri'
ENCODED_STRING='ddeeff'/>
</dataset>
这个数据集表示名为 OPE_OPERATOR 的数据库表中的三列,如表 1 中最后三行所描述的:
表 1. 清单 1 中数据的表定义
OPE_OPERATOR
ID INT
CODE VARCHAR
ENCODED_STRING VARCHAR
每个 XML 实体标识数据库中的一个表,而每个属性表示一列的值。
在自己的项目中设置 DbUnit
设置 DbUnit 很简单。有关项目文件下载的信息,请参阅 参考资料的内容。可以将所有三个 JAR 文件加到项目的编译目标中以进行测试。
如果是一个多 schema 环境,那么要将 DbUnit.qualified.table.names 属性设置为 true 。使用 Oracle 的开发团队通常是这种情况:每一个用户有自己的 schema。这可以使您免于在每个表名前面加上 schema 名称的前缀,并可以在团队中共享测试数据。
回页首
查询表的内容
DbUnit 使您可以容易地执行 JDBC 查询并获取它们的值。使用 DbUnit JDBC 包装器而不是纯粹的 JDBC 有几个理由:
可以用 SQL 查询创建一个 Dataset ,并使用 DbUnit 的 assertion(断言)方法(在后面描述)。
可以用 SQL 查询创建一个 Dataset ,并将它保存为一个 FlatXmlDataSet 。可以在以后将它重新装载到数据库中。
可以容易地从任何行中获取列的内容,无需进行迭代。
清单 2 中的代码创建一个结果 ITable,它包含了查询的结果。首先检查行计数是否为 1,然后检查第一行(从 0 开始计)中, FK_OTHER_ID 列包含数字 1234。
清单 2. DbUnit 的查询功能
String query = "SELECT * FROM MEDIA WHERE ID= "+id;
ITable databaseData =
dbConnection.createQueryTable("EXPECTED_DATA",query);
assertEquals(1, databaseData.getRowCount());
BigDecimal foreignKey = (BigDecimal) databaseData.getValue(0,
"FK_OTHER_ID");
assertEquals(new BigDecimal(1234)), foreignKey);
回页首
使用 assert 方法检查数据库内容
DbUnit 有断言方法,如清单 3 所示的那些,可以用于比较表的两组数据或者表的两个表示。如果需要在运行一次测试而不是多次查询后检查表的确切内容,一般会用它们。
清单 3. DbUnit 的附加断言方法
public static void assertEquals(ITable expected, ITable actual);
public static void assertEquals(IDataSet expected, IDataSet actual);
回页首
创建数据
根据数据库的大小、架构的稳定性如何以及开发的进展情况,可能要从头开始创建或者从生产数据库中拷贝测试数据。清单 4 显示了从已经存在的数据库中提取内容的例子(可以在 清单 6中找到 getConnection() 方法):
清单 4. 用现有的数据库创建 FlatXmlDataSets
public void extractTables(String targetDirectory,String[] tableNames)
throws Exception {
IDatabaseConnection connection = getConnection();
for (int i = 0; i < tableNames.length; i++) {
String tableName = tableNames[i];
IDataSet partialDataSet = connection.createDataSet
(new String[] { tableName });
FlatXmlDataSet.write
(partialDataSet, new FileOutputStream
(targetDirectory + "/" + tableName + ".xml"));
}
}
如果导出一个完整的生产数据库,可能必须要删除过多的行--或者像在这里一样,用一个查询而不是直接用连接创建一个数据集。提取本身对于很大的表来说可能是个问题--我们的小组只能用查询提取某些表的一部分。从表中删除行也有些问题,主要涉及到浏览所有外键并保证数据的一致性的困难。
回页首
添加测试数据
添加测试数据有时可能乏味的。我们的经验是度过正确添加数据的最初困难阶段后,就可以达到这样一个层次,不仅添加数据变得容易了,而且对数据库结构的理解也有了极大提高。
即使使用 Enterprise JavaBeans (EJB) 技术隐藏数据库,这种第一手知识仍然非常有用。因为开发人员对数据库有了更好的理解,因而可以更快地检查其内容,从而使调试更容易了。这在重构代码和数据库时又会给予我们极大的帮助。
回页首
用 DbUnit 和 JUnit 创建基类
好的 JUnit 实践鼓励开发人员扩展基类 TestCase 以获得特化(specialization)行为。DbUnit 提供了自己的特化-- DatabaseTestCase ,通过它可以特化行为以满足自己的需要。
首先,创建一个名为 ProjectDatabaseTestCase 的基本测试用例,并向它添加实用工具方法,如清单 5 所示。然后,重新定义 setUp() 和 teardown() ,以使它们能够创建和销毁通过 DbUnit 到数据库的连接。
清单 5. 基类定义和设置数据库的基本方法
public class ProjectDatabaseTestCase extends DatabaseTestCase
{
/** Use this connection to perform database setup */
protected IDatabaseConnection connection;
public DatabaseTestCase (String s)
{
super(s);
}
protected void setUp() throws Exception
{
super.setUp();
connection = getDbUnitConnection();
}
protected void tearDown() throws Exception
{
connection.close();
super.tearDown();
}
}
清单 6 显示了前述方法使用的类中的不同方法:
清单 6. 不同的实用工具方法
/**
* This method returns a DbUnit database connection
* based on the schema name
*/
private IDatabaseConnection getDbUnitConnection() throws Exception
{
IDatabaseConnection connection = new DatabaseConnection (getJDBCConnection(), getSchemaName());
return connection;
}
private IDataSet getFlatXmlDataSet(String tableName) throws Exception
{
URL url = DatabaseTestCase.class.getResource( "/"+ tableName + ".xml");
if (url == null)
throw new Exception("could not find file for " + tableName);
File file = new File(url.getPath());
return new FlatXmlDataSet(file);
}
/** Implement yourself */
private Connection getJDBCConnection() throws Exception
{
/* Get your JDBC connection through a data source of JDBC itself */
}
* Implement yourself */
private Connection getSchemaName() throws Exception
{
}
上述代码中应当注意的一些内容:
没有显示 getJDBCConnection() 方法,因为它的实现取决于希望如何获得 JDBC 连接:当 DataSource 为 Serializable 时通过应用服务器的 JNDI 树,或者直接使用 JDBC。
getDbUnitConnection() 方法返回 DbUnit 的一个到数据库的连接。DbUnit 的 DatabaseConnection 构造函数可以带一个 schema 名作为参数。这样,就不必在所有表名前面加上 schema 名的前缀了。
getFlatXmlDataSet() 方法用位于类路径上的一个 XML 文件的内容创建 DbUnit 数据集。
最后,该实际将数据插入测试表中。DbUnit 可以有不同的数据库操作,我使用了其中的两种:
DELETE_ALL ,它删除表中所有行。
CLEAN_INSERT ,它删除表中所有行并插入数据集提供的行。
ProjectDatabaseTestCase 中的下面四个方法可以满足您的需要:
insertFileIntoDb() :在数据库中插入文件。
emptyTable() :清理数据库表。
insertAllFilesIntoDb() :插入项目的所有文件。
emptyAllTables() :清理项目的所有表。
清单 7 显示了这些方法的使用:
清单 7. 底层测试用来设置数据库的方法
/** A method to insert all tables into the database.
* Specify all tables to be inserted
*/
protected void insertAllFilesIntoDb() throws Exception
{
insertFileIntoDb("PRODUCT");
(...)
insertFileIntoDb("ACCOUNT");
}
/**
* This method inserts the contents of a FlatXmlDataSet file
* into the connection
*/
protected void insertFileIntoDb(String tableName) throws Exception
{
DatabaseOperation.CLEAN_INSERT.execute(connection,getFlatXmlDataSet(tableName));
}
/** Empty a table */
protected void emptyTable(String tableName) throws Exception
{
IDataSet dataSet = new DefaultDataSet(new DefaultTable(tableName));
DatabaseOperation.DELETE_ALL.execute(connection, dataSet);
}
/** Empty all the tables from the database */
protected void emptyAllTables() throws Exception
{
emptyTable("ACCOUNT");
(...)
emptyTable("PRODUCT");
}
回页首
合到一起
完成了基类后,用 DbUnit 干净地建立数据库,执行一个方法,并检查返回值是很容易的事,如清单 8 所示:
清单 8. 在实际的测试用例中合到一起
public void setUp() throws Exception
{
super.setUp();
emptyAllTables();
service = Service.getInstance();
}
public void testFindProductByPrimaryKey() throws Exception
{
insertFileIntoDb("PRODUCT");
ProductDTO productDTO = service.findProductByPrimaryKey(new Integer(12));
assertNotNull(productDTO);
assertEquals("product Name", productDTO.getName());
}
public void testCreateAProduct() throws Exception
{
service.createProduct("newly created product name");
String query = "SELECT * FROM PRODUCT";
ITable databaseData = dbConnection.createQueryTable("EXPECTED_DATA",query);
assertEquals(1, databaseData.getRowCount());
String productName = (String) databaseData.getValue(0, "NAME");
assertEquals("newly created product name", productName);
}
在这个测试中,我清空了数据库,插入一个表的内容,并通过检查它返回的元素是否有正确的属性来检查用主键查找产品的 finder 方法是否正常工作。然后测试对象创建工作,并用 DbUnit 的查询程序验证数据库的内容。
需要注意的一件重要事情是,清理数据库是在建立测试而不是结束时进行的。我不想依赖于每次测试都干净地结束。
在插入数据时要关注的事情
数据库完整性约束迫使您以给定的顺序插入或者删除数据。在编写 insertAllFiles() 和 emptyAllTables() 方法时,您会发现顺序并非是随意的,事实上它是由完整性约束所限定的。
另一个潜在的陷井是,一些列可能看来没有插入。几乎总是会出现这种情况,因为在 FlatXmlDataSet 中的第一行缺少一列。看来 DbUnit 不能识别所有其他行中的这一列。例如,插入清单 9 中定义的数据集会使表 ACC_ACCOUNT 包含两行,它们惟一的非空列是主键 PK_ACC_ID :
清单 9. NAME of ACCOUNT 列的第二行会缺失
<dataset>
<ACCOUNT ID='1' />
<ACCOUNT ID='2' NAME='first name' />
</dataset>
总是要保证第一行描述包含表中的所有列。如果需要插入一个 NULL 值,要使这一行成为第二行,如清单 10 所示:
清单 10. NAME of ACCOUNT 列的第二行将不会缺失
<dataset>
<ACCOUNT ID='2' NAME='first name' />
<ACCOUNT ID='1' />
</dataset>
回页首
组织测试数据
DbUnit 可以在文件中存储 XML 数据集。它甚至允许在一个文件中存储整个数据库。清单 11 显示了表 ACCOUNT 和 MEDIA 的内容:
清单 11. 两个表的 FlatXmlDataSet 示例
<dataset>
<ACCOUNT NAME='first name' />
<ACCOUNT NAME='second name' />
<MEDIA ID='123' />
<MEDIA ID='234' />
</dataset>
决定如何存储测试数据很重要。是将每一个表的内容存储到单独的文件中,还是将与系统主要实体有关的所有表的所有行存储到一个文件中?它们都不是完美的解决方案(silver bullet)。
对于第一种情况,保证跨表的数据一致更困难,但是用一个已经存在的数据库创建查询更容易。在第二种情况下,为每一个测试创建测试集更容易,但是事实上大多数系统不是围绕一个主要实体设计的,因此这使它不那么实用。我们的方法是每个表有一个文件。
用 Anthill 实现持续集成
Anthill 是一个免费的自动构建工具(请参阅 参考资料),它规划您的构建并发布结果,帮助精通 XP 的小组使用持续集成。一次构建包含用 CVS 这样的版本控制工具检查源代码、运行一个构建脚本、发布结果并通知用户结果。它很好地与 ANT 集成,使您可以重用常用的构建脚本。
回页首
在 Anthill 中运行测试包并报告结果
XP 专家一直建议将持续集成作为确保减少集成错误一种方式:通过以足够高的频率集成所有代码,保证容易追溯到源代码中的问题。集成可能是非常耗时的任务--检查、构建和部署代码,然后运行验收试验。幸运的是,其中大多数可以用 Anthill 或者 CruiseControl 这样的工具自动化。如果还没有使构建过程自动化(例如用 Ant),那您应当这样做。如果构建过程是自动化的,应当在构建中加入一个测试部分。如果您是顽固的 XP 用户,这些应当是您的验收测试。如果您像我们一样,那么这些就是您要编写的所有测试--不管是单元、验收或者其他测试。
我们的构建过程基于 Ant 并计划使用 Anthill。我们的主要挑战是让 Anthill 报告失败的测试并且仍然发布测试结果。Anthill 捕获的是:如果构建脚本失败,就不执行发布脚本,在这种情况下不能将测试报告提供给开发人员。我们的方法是让 Anthill 检查属性为 true 还是 false,而使它在发布脚本的最后才失败。
运行测试的目标
下面是关于运行测试的简要总结。我们使用的是最批量化的方法,但是任何方法都可以工作。要点有:
测试必须具有分支,以便在类路径包含 JDK 1.3 中的 XML 解析器时可以正常工作。
如果出现错误或者失败,则 testsuite.error 和 testsuite.failure 属性必须设置为 true。如果没有错误或者失败的话,则不改变它们。
清单 12 显示了运行特定模块的所有测试的例子:
清单 12. 运行一个模块的测试
<target name="test-common">
<mkdir dir = "${project.reports}/common"/>
<junit fork="true" errorproperty="testsuite.error" failureproperty="testsuite.failure">
<classpath>
<pathelement location="${out.classes.dir}"/>
<fileset dir = "${shared.lib.dir}">
<patternset refid="necessary.jars"/>
</fileset>
</classpath>
<formatter type="xml"/>
<batchtest todir="${project.reports}/common">
<fileset dir="${out.src.dir}">
<include name="**/Test*.java"/>
</fileset>
</batchtest>
</junit>
</target>
使测试结果可被发布脚本使用
清单 13 显示了如何在编译过程中运行所有测试:
清单 13. build.xml 的代码片段:运行所有的测试并设置结果
<target name = "all-tests" depends = "test-module1,test-module2">
<property name="testsuite.error" value="false"/>
<property name="testsuite.failure" value="false"/>
<propertyfile file="${deployDir}/tests.results">
<entry key="testsuite.error" value="${testsuite.error}"/>
<entry key="testsuite.failure"
value="${testsuite.failure}"/>
</propertyfile>
</target>
一个需要了解的重要的 Ant 技巧是,Ant 只在属性没有值时才设置属性的值。所以在依次运行每一个测试时, testsuite.error 和 testsuite.failure 属性只有当出现错误或者失败时才会是 true。
这里的困难是向主 Ant 脚本报告测试脚本的结果。不幸的是,这并不是一项简单的任务,因为在 Anthill 的过程中有两个不同的 Ant 构建文件,在 Ant 中不能在构建脚本之间传递这种参数。不过,有一个“简单”的解决方案:将测试的结果保存到文件中,之后发布脚本读取这个文件。
清单 13 使用了这种 Ant 技巧,它显示了如何使用 <property> 命令保证 testsuite.error 和 testsuite.failure 属性在测试脚本结束时总是有一个值,以及如何将它保存为文件。
如果测试失败,使发布脚本在结束时失败
用清单 14 让发布脚本在任何测试失败时均失败。这只不过是为了检查在构建脚本中保存的每一个属性是否为 true。
清单 14. 当有错误或者失败时使发布脚本失败
<condition property="must.fail">
<or>
<istrue value="${testsuite.error}"/>
<istrue value="${testsuite.failure}"/>
</or>
</condition>
<fail message="Tests didn't run 100%. Check the log and make
necessary changes!" if="must.fail"/>
http://www.ibm.com/developerworks/cn/java/j-dbunit/
相关推荐
数学建模拟合与插值.ppt
【项目资源】:包含前端、后端、移动开发、操作系统、人工智能、物联网、信息化管理、数据库、硬件开发、大数据、课程资源、音视频、网站开发等各种技术项目的源码。包括STM32、ESP8266、PHP、QT、Linux、iOS、C++、Java、python、web、C#、EDA、proteus、RTOS等项目的源码。【项目质量】:所有源码都经过严格测试,可以直接运行。功能在确认正常工作后才上传。【适用人群】:适用于希望学习不同技术领域的小白或进阶学习者。可作为毕设项目、课程设计、大作业、工程实训或初期项目立项。【附加价值】:项目具有较高的学习借鉴价值,也可直接拿来修改复刻。对于有一定基础或热衷于研究的人来说,可以在这些基础代码上进行修改和扩展,实现其他功能。【沟通交流】:有任何使用上的问题,欢迎随时与博主沟通,博主会及时解答。鼓励下载和使用,并欢迎大家互相学习,共同进步。
mysql相关资源.txt
此项目为一个HTML+CSS+JS的国漫分享网站,用户可以在此网站中观看自己喜欢的国漫。此网站共有4个页面,分别为首页,最新动态,热门推荐,分类。页面动漫图片齐全,内容可更改。可用于期末课程设计或个人课程设计。
Python爬虫爬取漫画
模拟退火算法应用。C++语言编程用模拟退火算法解决旅行商问题。该资源包含模拟退火算法C++语言的源代码。模拟退火算法是一种基于概率的全局优化算法,最初来自于物理学中的退火过程。它通过模拟金属冷却时原子排列逐渐趋于最低能量状态的过程来寻找问题的最优解。模拟退火算法常用于解决非线性、组合优化问题,特别适合于大规模、复杂的搜索空间。
传感器试题及答案.doc
【项目资源】:包含前端、后端、移动开发、操作系统、人工智能、物联网、信息化管理、数据库、硬件开发、大数据、课程资源、音视频、网站开发等各种技术项目的源码。包括STM32、ESP8266、PHP、QT、Linux、iOS、C++、Java、python、web、C#、EDA、proteus、RTOS等项目的源码。【项目质量】:所有源码都经过严格测试,可以直接运行。功能在确认正常工作后才上传。【适用人群】:适用于希望学习不同技术领域的小白或进阶学习者。可作为毕设项目、课程设计、大作业、工程实训或初期项目立项。【附加价值】:项目具有较高的学习借鉴价值,也可直接拿来修改复刻。对于有一定基础或热衷于研究的人来说,可以在这些基础代码上进行修改和扩展,实现其他功能。【沟通交流】:有任何使用上的问题,欢迎随时与博主沟通,博主会及时解答。鼓励下载和使用,并欢迎大家互相学习,共同进步。
本文档主要讲述的是MongoDB数据表基本操作;希望对大家会有帮助;感兴趣的朋友可以过来看看
本数据集提供了2011至2021年间全国各省废气和废水中主要污染物的排放量统计数据。数据涵盖了二氧化硫、氮氧化物、烟尘和颗粒物等关键污染物的排放量,为研究中国环境状况和污染物排放趋势提供了宝贵信息。数据显示,2011-2021年间,各省的二氧化硫排放量从数十万吨到数百万吨不等,其中广东、广西、海南等省份的排放量较高。氮氧化物排放量同样显示出地域差异,北京、天津等北方城市的排放量相对较低,而一些工业大省如河北、山西的排放量较高。颗粒物排放量统计显示,工业源和生活源是主要的排放源,其中工业源排放量占比较大。这些数据不仅对环保政策制定者具有参考价值,也为学术研究提供了实证基础。
脉冲宽度测量单片机课程设计.doc
【项目资源】:包含前端、后端、移动开发、操作系统、人工智能、物联网、信息化管理、数据库、硬件开发、大数据、课程资源、音视频、网站开发等各种技术项目的源码。包括STM32、ESP8266、PHP、QT、Linux、iOS、C++、Java、python、web、C#、EDA、proteus、RTOS等项目的源码。【项目质量】:所有源码都经过严格测试,可以直接运行。功能在确认正常工作后才上传。【适用人群】:适用于希望学习不同技术领域的小白或进阶学习者。可作为毕设项目、课程设计、大作业、工程实训或初期项目立项。【附加价值】:项目具有较高的学习借鉴价值,也可直接拿来修改复刻。对于有一定基础或热衷于研究的人来说,可以在这些基础代码上进行修改和扩展,实现其他功能。【沟通交流】:有任何使用上的问题,欢迎随时与博主沟通,博主会及时解答。鼓励下载和使用,并欢迎大家互相学习,共同进步。
【项目资源】:包含前端、后端、移动开发、操作系统、人工智能、物联网、信息化管理、数据库、硬件开发、大数据、课程资源、音视频、网站开发等各种技术项目的源码。包括STM32、ESP8266、PHP、QT、Linux、iOS、C++、Java、python、web、C#、EDA、proteus、RTOS等项目的源码。【项目质量】:所有源码都经过严格测试,可以直接运行。功能在确认正常工作后才上传。【适用人群】:适用于希望学习不同技术领域的小白或进阶学习者。可作为毕设项目、课程设计、大作业、工程实训或初期项目立项。【附加价值】:项目具有较高的学习借鉴价值,也可直接拿来修改复刻。对于有一定基础或热衷于研究的人来说,可以在这些基础代码上进行修改和扩展,实现其他功能。【沟通交流】:有任何使用上的问题,欢迎随时与博主沟通,博主会及时解答。鼓励下载和使用,并欢迎大家互相学习,共同进步。
【项目资源】:包含前端、后端、移动开发、操作系统、人工智能、物联网、信息化管理、数据库、硬件开发、大数据、课程资源、音视频、网站开发等各种技术项目的源码。包括STM32、ESP8266、PHP、QT、Linux、iOS、C++、Java、python、web、C#、EDA、proteus、RTOS等项目的源码。【项目质量】:所有源码都经过严格测试,可以直接运行。功能在确认正常工作后才上传。【适用人群】:适用于希望学习不同技术领域的小白或进阶学习者。可作为毕设项目、课程设计、大作业、工程实训或初期项目立项。【附加价值】:项目具有较高的学习借鉴价值,也可直接拿来修改复刻。对于有一定基础或热衷于研究的人来说,可以在这些基础代码上进行修改和扩展,实现其他功能。【沟通交流】:有任何使用上的问题,欢迎随时与博主沟通,博主会及时解答。鼓励下载和使用,并欢迎大家互相学习,共同进步。
全国矢量地图数据【国道+高速公路】ArcGIS Shape格式数据集是一种专门用于地理信息系统(GIS)的矢量数据集,包含中国范围内国道和高速公路的详细路网信息。该数据集广泛应用于交通规划、导航、物流分析和灾害应急等领域,具有高精度和易用性。 数据集特点: 1. 数据内容: 国道:包括以“G”开头的国家级公路,如G1京哈高速、G107国道等。 高速公路:包括全国范围内的所有高速公路网,覆盖主要经济区、城市和边境口岸。 属性数据: 道路编号(国道或高速公路编号)。 道路名称。 道路等级(如一级、二级、快速路等)。 起点和终点坐标。 道路长度(单位:公里)。 相关属性(如路段建成年份、设计速度、车道数等)。 2. 数据格式: **Shapefile(.shp)**格式,支持主流GIS软件(如ArcGIS、QGIS)及数据处理工具(如Python、Matlab)。 3. 投影坐标系: 一般采用WGS84地理坐标系,或可根据需求转换为**GCJ-02(火星坐标系)**以配合国内导航应用。
4
【项目资源】:包含前端、后端、移动开发、操作系统、人工智能、物联网、信息化管理、数据库、硬件开发、大数据、课程资源、音视频、网站开发等各种技术项目的源码。包括STM32、ESP8266、PHP、QT、Linux、iOS、C++、Java、python、web、C#、EDA、proteus、RTOS等项目的源码。【项目质量】:所有源码都经过严格测试,可以直接运行。功能在确认正常工作后才上传。【适用人群】:适用于希望学习不同技术领域的小白或进阶学习者。可作为毕设项目、课程设计、大作业、工程实训或初期项目立项。【附加价值】:项目具有较高的学习借鉴价值,也可直接拿来修改复刻。对于有一定基础或热衷于研究的人来说,可以在这些基础代码上进行修改和扩展,实现其他功能。【沟通交流】:有任何使用上的问题,欢迎随时与博主沟通,博主会及时解答。鼓励下载和使用,并欢迎大家互相学习,共同进步。
Oracle从8.1.6开始提供分析函数,分析函数用于计算基于组的某种聚合值,它和聚合函数的不同之处是对于每个组返回多行,而聚合函数对于每个组只返回一行。 感兴趣的朋友可以过来看看
商道融绿ESG评级季度数据及分项ESG评级2015-2022年的数据集提供了一个全面评估上市公司在环境、社会和公司治理(ESG)方面表现的视角。该数据集覆盖了2015至2022年间的数据,包含了沪深A股上市公司以及港股通的香港上市公司。数据集包含了多个维度的指标,如公司代码、公司名称、评级日期、ESG综合评级、历史评级、财务状况和所属行业等,共计13个指标。这些指标不仅包括了ESG评级,还涵盖了公司的财务分析,例如总市值、流通市值、市盈率PE(TTM)、每股收益EPS(TTM)、每股营业收入(TTM)以及每股经营活动产生的现金流量净额(TTM)等。此外,数据集还提供了证监会行业和Wind行业的分类信息,为研究者提供了一个多角度分析上市公司ESG表现的工具。该数据集对于投资者、资产管理公司以及企业自身在可持续发展报告撰写和风险管理中具有重要参考价值。
WPF树菜单拖拽功能,下级目录拖到上级目录,上级目录拖到下级目录.zip