数据库操作的单元测试.doc_第1页
数据库操作的单元测试.doc_第2页
数据库操作的单元测试.doc_第3页
数据库操作的单元测试.doc_第4页
数据库操作的单元测试.doc_第5页
已阅读5页,还剩18页未读 继续免费阅读

下载本文档

版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领

文档简介

数据库单元测试1目录1.测试环境的搭建(DBunit+HSQLDB)11.1.DBunit的简介11.1.1.DBunit简单介绍和原理11.1.2.DBunit的三大核心组件11.1.3.DBunit的安装使用21.2.HSQLDB简介31.2.2.什么是HSQLDB31.2.3.HSQLDB安装和使用51.2.4.HSQLDB使用71.2.5.HSLDB的使用注意事项82.数据库单元测试测试流程介绍12.1.数据库单元测试的原因12.2.测试关注点12.3.测试流程13.数据库单元测试最佳实践23.1.数据库单元测试最佳实践23.1.1.从“易测试的”应用程序体系结构开始。23.1.2.使用精确的断言。23.1.3.外化断言数据。33.1.4.编写全面的测试。43.1.5.创建稳定、有意义的测试数据集。53.1.6.创建专用的测试库。63.1.7.有效地隔离测试。73.1.8.分割测试套件。83.1.9.使用适当的框架(如 DbUnit)简化过程。93.2.DBunit使用最佳实践93.2.1.每一个开发人员需要搞一个数据库实例。93.2.2.使用XML文件作为DataSet93.2.3.DBUnit的最佳实践是尽可能使用最小的数据集。103.2.4.DatabaseOperation.CLEAN_INSERT 策略103.2.5.为相互关联的测试场景创建多个种子文件是一个很有效的策略.103.3.自我总结103.3.1.对各种类型的方法的测试策略:103.3.2.写一个基TestCase。103.3.3.测试数据库的有效方法。103.3.4.seed文件的设置103.3.5.一般的测试的步骤114.数据库单元测试指南124.1.测试代码的包结构124.2.对于各种类型方法的测试策略124.2.1.查询类的方法154.2.2.更新类的方法175.参考资料11. 测试环境的搭建(DBunit+HSQLDB)1.1. DBunit的简介1.1.1. DBunit简单介绍和原理为依赖于其他外部系统(如数据库或其他接口)的代码编写单元测试是一件很困难的工作。在这种情况下,有效的单元必须隔离测试对象和外部依赖,以便管理测试对象的状态和行为。使用mock object对象,是隔离外部依赖的一个有效方法。如果我们的测试对象是依赖于DAO的代码,mock object技术很方便。但如果测试对象变成了DAO本身,又如何进行单元测试呢?开源的DbUnit项目,为以上的问题提供了一个相当优雅的解决方案。使用DbUnit,开发人员可以控制测试数据库的状态。进行一个DAO单元测试之前,DbUnit为数据库准备好初始化数据;而在测试结束时,DbUnit会把数据库状态恢复到测试前的状态。官网:DBUnit的设计理念就是在测试之前,备份数据库,然后给对象数据库植入我们需要的准备数据,最后,在测试完毕后,读入备份数据库,回溯到测试前的状态;而且又因为DBUnit是对JUnit的一种扩展,开发人员可以通过创建测试用例代码,在这些测试用例的生命周期内来对数据库的操作结果进行比较。1.1.2. DBunit的三大核心组件. IDatabaseConnection:数据连接dbUnit持有的对数据库的连接。基于jdbc的,以及基于datasource的都有。. IDataSet:数据源(5种)FlatXmlDataSet:每个XML元素是对应者一个表行。元素的名字就是表的名字。XML的属性就对应表的列名。如果需要指定为null值,只需要省略对应的属性就可以。也就是说,在XML文件中,第一行最好把所有的值都设置好了。要不然会有麻烦。(在使用过程当中并没有出现这样的情况。)DatabaseDataSet:将整个数据库作为一个DataSet。QueryDataSet:将数据库的查询结果作为一个DataSet。DefaultDataSet:用于编程目的。XlsDataSet:读写EXCEL文件,根据这个文件的内容来构建DataSet。ReplacementDataSet:Decorator类,可以用一些属性来替换DataSet中的值。. DatabaseOperation:数据库的操作代表在每个testcase开始之前以及结束之后,执行的数据库操作。DatabaseOperation.UPDATE:更新数据库。就是利用DataSet中的值去更新数据库,当然是假设原来的数据库中已经存在那些值,要不然会报错的。DatabaseOperation.INSERT:插入数据库。那么需要保证你插入的时候,数据库之间的外健关联关系,因此,表插入的顺序要正确。如果外健关联是程序维护的话,那么就不会有这个问题。MYSQL不会有这个问题。(HSQLDB存不存在这个问题)DatabaseOperation.DELETE:删除。只删除那些在Dataset中的记录。DatabaseOperation.DELETE_ALL:删除在DataSet中出现的所有的表。DatabaseOperation.TRUNCATE:截断在DataSet中出现的表。是按照DataSet中表出现的相反顺序进行截断的。DatabaseOperation.REFRESH:把DataSet中的内容刷新到数据库中。DataSet中在数据库中存在的会被刷新,在数据库中不存在的,会被插入。DatabaseOperation.CLEAN_INSERT:先执行DELETE_ALL,然后执行INSERT。清空,然后重新装入数据库。DatabaseOperation.NONE:什么也不做。TransactionOperation:Operation的decorator类,在Transaction中执行这些操作。IdentityInsertOperation:也是decorator类,是用于专门处理MS SQLSERVER中的ID自动生成的问题。IDENTITY。1.1.3. DBunit的安装使用只要从DBunit的官网(/projects/dbunit/files/)上下载与DBunit相关的jar包(如:dbunit-2.4.6.jar),另外DBunit还依赖和commons-logging.jar,所以也需要下载slf4j的slf4j-api-1.5.3.jar和slf4j-jcl-1.5.3.jar。注意,在owk中并未使用上面的dbunit-XX.jar而使用owk自定义的jarowk-dbunit-2.4.6.jar。DBunit的使用可参见第4部分中的测试示例。1.2. HSQLDB简介1.2.1. HSQLDB简介1.2.2. 什么是HSQLDBHSQLDB是一款纯Java的数据库软件,支持ANSI-92 SQL、SQL 99中的多数操作。HSQLDB很小巧,包括DB Engine、Jdbc driver、DB管理工具在一起,只有一个约600K的JAR包。HSQLDB性能很好,据其官网宣称,jdbc+hsql 快过 jdbc+mysql 20倍。HSQLDB非常适合嵌入式数据库、产品演示用数据库、调试和单元测试用数据库。 HSQLDB官网:/ . HSQLDB数据库包含的文件在介绍这些模式之前我们需要了解一些Hsqldb所涉及的一些文件。每个Hsqld数据库包含了2到5个命名相同但扩展名不同的文件,这些文件位于同一个目录下。例如,名位test的数据库包含了以下几个文件: perties test.script test.log test.data test.backup properties文件描述了数据库的基本配置。 script文件记录了表和其它数据库对象的定义。log文件记录了数据库最近所做的更新。data文件包含了cached(缓冲)表的数据,而backup文件是将data文件压缩备份,它包含了data文件上次的最终状态数据。所有这些文件都是必不可少的,千万不可擅自删除。但如果你的数据库没有缓冲表(cached table),test.data和test.backup文件是不会存在。. HSQLDB的三种运行模式一、Server模式Server模式提供了最大的可访问性。应用程序(客户端)通过Hsqldb的JDBC驱动连接服务器。在服务器模式中,服务器在运行的时候可以被指定为最多10个数据库。根据客户端和服务器之间通信协议的不同,Server模式可以分为以下三种:1)、 Hsqldb Serve这种模式是首选的也是最快的。它采用HSQLDB专有的通信协议。启动服务器需要编写批处理命令。Hsqldb提供的所有工具都能以java class归档文件(也就是jar)的标准方式运行。假如hsqldb.jar位于相对于当前路径的./lib下面。我们的命令将这样写:java -cp ./lib/hsqldb.jar org.hsqldb.Server -database.0 mydb -dbname.0 demoDB现在你可能会疑惑,-database.0 、 dbname.0为什么在后面加0。我们不是在前面说服务模式运行的时候可以指定10个数据库吗,如有多个数据库,则继续写命令行参数-database.1 aa -dbname.1 aa -database.2 bb-dbname.2 bb . .新建文本文件保存上面命令,文件名可以随意,将后缀名改成bat,然后直接执行批处理文件即可。在以后介绍的执行启动工具的命令采用同样方法。上面启动服务器的命令启动了带有一个(默认为一个数据库)数据库的服务器,这个数据库是一个名为mydb.*文件,这些文件就是mydb.Properties、mydb.script、mydb.log等文件。其中demoDB是mydb的别名,可在连接数据库时使用。2)、 Hsqldb Web Server这种模式只能用在通过HTTP协议访问数据库服务器主机,采用这种模式唯一的原因是客户端或服务器端的防火墙对数据库对网络连接强加了限制。其他情况下,这种模式不推荐被使用。运行web服务器的时候,只要将刚才命令行中的主类(main class)替换成:org.hsqldb.WebServer.3)、 Hsqldb Servlet这种模式和Web Server一样都采用HTTP协议,当如Tomcat或Resin等servlet引擎(或应用服务器)提供数据库的访问时,可以使用这种模式。但是Servlet模式不能脱离servlet引擎独立启动。为了提供数据库的连接,必须将HSQLDB.jar中的hsqlServlet类放置在应用服务器的相应位置。Web Server和Servlet模式都只能在客户端通过JDBC驱动来访问。Servlet模式只能启动一个单独的数据库。请注意做为应用程序服务器的数据库引擎通常不使用这种模式。二、 In-Process模式In-Process模式又称Standalone模式。这种模式下,数据库引擎作为应用程序的一部分在同一个JVM中运行。对于一些应用程序来说, 这种模式因为数据不用转换和通过网络的传送而使得速度更快一些。其主要的缺点就是默认的不能从应用程序外连接到数据库。所以当应用程序正在运行的时候,你不能使用类似于Database Manager的外部工具来查看数据库的内容。推荐的使用In-Process模式方式是:开发的时候为数据库使用一个HSQLDB 服务器实例,然后在部属的时候转换到In-Process内模式。一个In-Process模式数据库是从JDBC语句开始启动的,在连接URL中带有指定的数据库文件路径作为JDBC的一部分。例如,假如数据库名称为testdb,它的数据库文件位于与确定的运行应用程序命令相同的目录下,下面的代码可以用来连接数据库:Connection c = DriverManager.getConnection(jdbc:hsqldb:file:testdb, sa, );数据库文件的路径格式在Linux主机和Windows主机上都被指定采用前斜线(/)。所以相对路径或者是相对于相同分区下相同目录路径的表达方式是一致的。使用相对路径的时候,这些路径表示的是相对于用于启动JVM的shell命令的执行路径三、Memry-Only数据库Memory-Only数据库不是持久化的而是全部在随机访问的内存中。因为没有任何信息写在磁盘上。这种模式通过mem:协议的方式来指定: Connection c = DriverManager.getConnection(jdbc:hsqldb:mem:dbName, sa, );你也可以在perties中指定相同的URL来运行一个Memory-Only(仅处于内存中)服务器实例。注意事项:当一个服务器实例启动或者建立一个in-process数据库连接的时候,如果指定的路径没有数据库存在,那么就会创建一个新的空的数据库。这个特点的副作用就是让那些新用户产生疑惑。在指定连接已存在的数据库路径的时候,如果出现了什么错误的话,就会建立一个指向新数据库的连接。为了解决这个问题,你可以指定一个连接属性ifexists=true只允许和已存在的数据库建立连接而避免创建新的数据库,如果数据库不存在的话,getConnection()方法将会抛出异常。1.2.3. HSQLDB安装和使用. 安装步骤1)下载hsqldb.jar, 地址:/projects/hsqldb/files/2)下载文件为, 将其解压得到如下的东西然后将lib下的hsqldb.jar放在./lib目录下(注意是./lib,而不是其他的,ant中有相关配置),若要置于其他路径,必须修改相应的ant任务配置项。到此,最基本的hsqldb安装已经结束了(简单吧_),下面是在owk项目使用的hsqldb的说明。3)在项目的根目录下,构建如下的目录结构(这里同样与ant任务配置有关,修改需同时修改ant任务配置项)4)运行ant任务hsqldb.xml中的相应的任务(help,startServer,shutdownServer,broweServer,initScript)具体各种任务的具体作用,可运行help任务来查看,如图所示的帮助:常规使用数据库的步骤:(1). startServer 启动服务器出现下面的文字说明启动服务器成功了(2). initScript 初始化数据库出现下面的文字说明成功初始化数据库中间部分内容省略注:首次初始化后,第二次使用会出现如下图所示的sql error,对此不需要处理不影响效果(3). broweServer 运行数据库管理器(这里可以进行简单的sql语句)出现下面所示的窗口就说明成功了:注:A 菜单栏,一些简单的设置窗口属性和显示内容属性 B 显示数据库拥有的所有表C 在这里可以输入一些的简单的sql语句D 显示结果和错误信息(4). shutdownServer 关闭数据库(注,只有执行这个命令才能关闭数据库,具体解释可见 软件开发人员注意事项03)出现如下的内容,则说明关闭数据库成功5) 配置属性(配置文件WebRoot/webinf/config/perties)(1).为了能在项目使用hsqldb,除了必须修改常规的数据库连接参数配置connection.driver, class,connection.url, connection.username, connection.password外还配置另外两个参数:hibernate.jdbc.batch_size, hibernate.dialect(2). 具体配置值如下:#使用HSQLDB的参数修改connection.driver_class=org.hsqldb.jdbcDriverconnection.url=jdbc:hsqldb:hsql:/localhost/OWKconnection.username=saconnection.password=connection.security=falsehibernate.jdbc.batch_size=0hibernate.dialect=org.hibernate.dialect.HSQLDialect至此,一切皆已结束,你可以安枕无忧的使用hsqlDb。(太方便了,_)1.2.4. HSQLDB使用运行ant任务启动服务器1.2.5. HSLDB的使用注意事项为了能够在以后的单元测试及演示使用等场合使用HSQLDB,开发时应该注意的问题:1) 在写sql语句时,注意不要使用如:select * from owk.mgroup where owk.mgroup.groupId 这种类型的语句,应该使用别名的形式。select * from owk.mgroup role where role.groupId 因为在HSQLDB中不认识owk.mgroup.groupId这种形式2) 在使用别名时,有些字符不能使用:Sql语句的关键字,group等关键字3) 关闭HSQLDB的说明:如果利用ant任务启动数据库后,若此时要停止 Hsqldb ,即点击红色按钮来 Terminate 掉它,实现上,Hsqldb的Java线程还是在后台运行的,它并没有真正结束。这里必须执行ant任务shutdownServer来关闭数据库。(注:关闭时会可能会抛java.sql.SQLException: Connection is broken异常,这不影响效果)4).更改数据库说明:若更换数据库时,注意必须要修改的以下的几个地方:(1). HSQLDB.xml文件中的以下内容:(2). 必须根据这个修改对应的sqlTool.rc文件。(修改的内容跟(1)中的东西对应,sqlTool.rc内容的意义参考sqlTool.rc说明)(3). 若修改了sqlTool.rc 文件中的urlid值,则必须相应的修改HSQLDB.xml文件中注:(2),(3)两步仅仅对运行sql文件时有影响,若无该操作可不进行这两步。2. 数据库单元测试测试流程介绍2.1. 数据库单元测试的原因我猜想有些,可能不完全是大多数的数据库开发过程都是这样的:建立数据库,编写存取数据到数据库的代码,编译并运行,用一个查询语句查验所列的数据是否正确显示。如果能正确显示那就大功告成了。然而,这种靠眼睛来检测的弊端在于:你不经常进行这样的检验,而且这种检验是不完全的。存在这样的可能性,当你对系统进行了修改,过了几个月后,你无意中破坏了系统,从而导致数据的丢失。作为一个编程人员,你可能不会花很多时间来检查数据本身,这就使错误的数据要经过较长的时间才能暴露出来。自动化测试,由于它能经常测试而且测试范围较广,降低了数据丢失的风险。当然,自动化测试还有其他一些好处,他们本身就是代码编写的范例,也可以作为文档,便于你修改别人编写的原始程序,从而减少检测所需的时间。2.2. 测试关注点测试的关注点在于测试逻辑,只要有逻辑就要写测试代码。测试的手段就是验证测试的手段就是验证所有被测试方法的所有产出物,包括:1) 测试方法的返回值 2) 测试方法的执行流程例如: public class DomainService private static TheDAO dao = new TheDAO (); public ReturnObject findByCond(String) return (ReturnObject)dao.getBeanByCondition(select * from ReturnObject where cond=+ paramter, ReturnObject.class); 在对于测试findByCond方法,有两个测试用例: A. 测传递给TheDAO.getBeanByCondition的参数的正确性,如果参数不是”select * from ReturnObject where cond=?”和ReturnObject.class则返回为null。 B. 测返回的对象正确性。 特别是第二点,在商业应用上比较常见的。通常有些方法无明显output,通常是执行写表操作的。对于这样的方法就是测试它的执行流程。当然这些方法本身包含逻辑的。2.3. 测试流程一般而言,使用DbUnit进行单元测试的流程如下:1) 根据业务,做好测试用的准备数据和预想结果数据,通常准备成xml格式文件。(种子文件的准备工作是最困难的一步)2) 在setUp()方法里边备份数据库中的关联表。3) 在setUp()方法里边读入准备数据。4) 对测试类的对应测试方法进行实装:执行对象方法,把数据库的实际执行结果和预想结果进行比较。5) 在tearDown()方法里边,把数据库还原到测试前状态。3. 数据库单元测试最佳实践3.1. 数据库单元测试最佳实践3.1.1. 从“易测试的”应用程序体系结构开始。应用最佳实践: 使应用程序易于测试的最简单方法是使用 DAO。DAO 在消除不必要的相关性时特别有用。根据定义,DAO 具有的唯一相关性是数据访问的相关性,如一个 Connection 或 DataSource 实例,在测试环境中很容易满足这种相关性。此外,DAO 将数据访问相关性从应用程序的其余部分中隐藏起来了,从而使应用程序的其他部分更易于测试。 我们的 DAO 实现 PersonDAOImpl 有一个简单的 setter 方法,它满足与 Connection 实例的相关性: public void setConnection(Connection conn) this.connection = conn;populate 请注意,测试(而不是 DAO)负责获取连接。 更一般的情况是,现实中的 DAO 可能由一个 DataSource 实例而非 Connection 本身填充。在任意一种情况下都不存在对查找机制的相关性。 为了创建易测试的体系结构,建议配置您的应用程序来使用相关性注入 (DI) 容器。DI 容器将鼓励您以消除不必要相关性的方式编写应用程序。 3.1.2. 使用精确的断言。在测试运行时使用精确描述系统处于什么状态的精确断言。只要有一个可能的结果满足测试,数据库测试就是精确的。简单验证 SQL 语句是否成功执行并返回一些结果的数据库查询不是精确的,这是因为即使成功的执行也可能返回错误的结果集。下面是一个不精确测试的示例: public class TestListPersons extends BaseDatabaseTest . /* Simple imprecise test that * checks that results are * returned but does not* count the values returned */public void testListPersons() List results = dao.listPersons(Phil, new Integer(25);assertNotNull(results);assertTrue(results.size() 0); 如代码中的断言所示,该测试仅证明了 DAO 实现返回了一些东西(非空)以及结果大于 0 但我们不知道结果列表是否包含准确的数据。应用最佳实践:精确的数据库测试将指定通过基于查询的 DAO 返回的项的数量和实际值。对更新操作而言,数据库测试还应验证是否已将预期的更改应用到精确定义的表和行的集合上。 代码清单 1: 利用精确断言进行测试 public void testPreciselyListPersons() List results = dao.listPersons(Phil, new Integer(25);assertNotNull(results);assertEquals(2, results.size();for (Iterator iter = results.iterator(); iter.hasNext();) Person person = (Person) iter.next();assertNotNull(person.getId();assertEquals(Phil, person.getFirstName();assertNotNull(person.getAge();assertTrue(person.getAge().intValue() = 25);assertNull(person.getSurName();assertNull(person.getGender();通过验证所有返回的行,人名是否是 Phil,年龄是否是 25 或者更大(由方法参数定义),我们的测试现在验证了该 DAO 实现遵守 listPersons() 方法约定。 3.1.3. 外化断言数据。将断言数据放在外部信息库中,以便于管理和维护您的测试。大多数开发人员都认为编写精确的断言是个不错的主意,但其编写方式可能与我们的精确断言之一 assertEquals(2, results.size() 的编写方式不同,这是因为该断言值是硬编码的。如果您在用成百上千甚至成千上万个测试测试一个大型应用程序,您肯定不想在测试代码中遍布成百上千或者成千上万个硬编码的 String 或 int 值,其原因有两个:首先,如果您的测试数据发生变化,您希望能够轻松地找到需要更改的断言数据。第二,您希望利用在不同测试间共享断言数据的机制。该问题的解决方法是外化断言数据,正如您将在生产代码中外化字符串消息那样。 应用最佳实践: 我们的示例应用程序包含了为外化断言数据问题提供的一个基于属性文件的简单解决方案。清单 2 显示了一个工作中的示例。 代码清单 2: 利用精确断言进行测试 public class TestListAllPersonsExternal extends BaseDatabaseTest private int ALL_RESULT;protected void setUp() throws Exception./get the externalized assertion dataALL_RESULT = getAssertionDataInt(ALL_RESULT);public void testPreciselyListAllPersons()List results = dao.listAllPersons();assertNotNull(results);assertEquals(ALL_RESULT, results.size();.一个断言数据存储在一个实例字段中并通过使用继承的 getAssertionDataInt() 方法在 setUp() 中将其加载。在这个例子中,您将把数据库中所有人的计数值加载到 ALL_RESULT 字段中。您可以看到测试方法断言如何不再依赖于硬编码数据。 我们的小型断言数据加载框架试图按如下方式加载断言值: 1). 它在 TestListAllPersonsEperties 文件中查找一个名为 ALL_RESULT 的属性,该文件与 TestListAllPersonsExternal 类位于同一个程序包中。 2). 它在 perties 文件中查找 ALL_RESULT 属性,该文件也位于 TestListAllPersonsExternal 类的程序包中。 3). 它在类路径的根程序包中的 perties 文件中查找该属性。 这是一种相当方便的搜索方案,这是因为它使您能够将断言数据分组为特定测试类专有的项,它们可以在一个程序包的测试类间共享,也可以在应用程序的所有测试类间共享。 3.1.4. 编写全面的测试。您的测试套件应当是全面的,要覆盖所有的数据库操作,并允许尽可能与实际约束一样多的情景。如果一组测试覆盖了您能够合理预期将在应用程序中实际发生的所有情景,那么它就是全面的。给定一组完整的先决条件而不只是最明显的先决条件,我们的测试需要验证每一个方法是否正确地工作。在我们的 listPersons() 示例中,不难想到一些可能影响方法行为方式的其他执行前状态: 在调用 listPersons() 之前,没有为 DAO 实现提供连接。 调用 listPersons() 时将一个或两个参数设为 null。 使用不同的数据集调用 listPersons()。 在每种情况下,您都要验证是否都返回正确的数据。与精确测试使我们能够验证一种操作在给定先决条件下是否正确工作的方式相同,全面测试确定了所有操作在所有合理预期的先决条件下是否正确工作。 应用最佳实践: 要使您的测试环境全面,首先应想象所有可能的情景。在测试优先的开发传统中,这将在要测试的方法实现期间甚至之前完成。其次,需要主观判断每种情景发生的可能性,以确定是否将其加入到测试套件中。极限编程 (XP) 风格的配对编程非常适用于这两种任务 在想像可能的情景以及判断要实际实施哪些测试时两个臭皮匠顶个诸葛亮。 不要总想着使用 Java 代码覆盖工具的输出作为您数据库测试覆盖完整的证据。对数据库测试而言,代码覆盖是必要非充分的。在一条 SQL 语句中可以包含相当复杂的数据检索逻辑,且每条语句都包含几种可分别测试的情景。 正如您可能已经体验到的,建立全面的测试是编写测试时最困难的技巧之一。对几乎所有项目而言,它都只是一个理想而非现实。开发人员需要在一方面覆盖相关的测试情景与另一方面由于测试的负担而变得无法承受之间找到合适的平衡。 3.1.5. 创建稳定、有意义的测试数据集。花些时间和精力创建一个稳定、有意义的测试数据集,您的测试将以该测试数据集为基础。 有了好的测试数据,编写数据库测试也就容易得多了。开发人员知道他们会得到所需的数据,因此也就可以信心十足地作出精确断言。 创建测试数据是在 TDD 环境中进行数据库开发的一个基础。对数据库操作而言,开发应是测试数据驱动的。在为用例编写测试之前,构建一个数据集。测试数据集并不总是需要很大;它只需足够覆盖与要测试的操作相关的情景。 创建测试数据可能是一项单调乏味的工作,但值得投入时间和脑力创建有意义且结构合理的数据。如果您在项目的早期就创建了高质量的数据,那么您将发现当您向应用程序中添加新特性和测试时可以更容易地更改、扩展或重用您的工作成果。好的测试数据对用户界面测试也极其有用。 应用最佳实践: 您可以通过输入 SQL INSERT 语句或使用工具将数据导入成这种格式来轻松地创建测试数据。然而,为了实现测试自动化,您将需要一种易于从 JUnit 测试案例内部访问的数据插入和删除机制。您可以使用以下任一方式: 从 JUnit 内部运行 SQL*Plus 或 SQL*Loader 脚本。ANT SQL 任务。 定制的测试数据加载程序。这种加载程序从文本文件中读取 SQL 命令并通过 JDBC 执行它们。 使用现有的框架,如 DbUnit,它是专门设计用来简化数据库测试的一个 JUint 扩展。DbUnit 允许将测试数据从 XML 文件导入到数据库中,并且还允许将数据库中的数据导出成 XML 和其他格式。 应用程序的 API 本身。当然,没有该 API 的实际实现,您就无法使用这种机制。 3.1.6. 创建专用的测试库。创建一个专门支持数据库测试的类库可以大大减轻测试的负担。作为编写数据库测试的开发人员,您可能发现自己为支持测试编写了大量的代码。例如,您将需要获取和释放数据库连接。您将需要启动和结束事务。您还经常需要在运行测试之后运行数据库状态查询。有时您将需要在运行数据库测试前执行修改数据库状态的操作,特别是您在一个无法在测试运行之前清理和重新插入数据的环境中工作时。 在所有这些情况下,您都将因专用测试库 包含在测试源文件夹中的一个类库,完全为简化测试而编写,不用于生产代码 的存在而获益。您不需要提前创建测试库,可以到时候再添加。例如,假定您编写了一些修改或检查系统状态的测试代码 只需将这些代码从测试类中取出,并将其转移到测试库中以供将来使用。 应用最佳实践:您可以使用与生产代码所遵循规则迥异的规则来编写要加入测试库的代码: 测试库代码不需要严格检查先决条件(例如空指针),这是因为测试中的失败将捕获这种问题。 测试库代码不需要抛出检查到的异常。任何被捕获的检查到的异常都可以只作为运行时异常再次抛出,并且可以由测试框架捕获。 通常使用静态方法更合适。这突出了创建测试库的目标。生产代码将使用通过接口提供的对象实例来使灵活性和代码重用最大化。另一方面,您的测试实用工具经常需要执行非常具体的任务并与测试环境本身紧密耦合。正确使用静态方法会使您减少测试支持代码的数量,这是因为您甚至不需要担心对象的实例化。 下面代码显示了使用中的示例应用程序测试库: public void testPersonsWithIdLookup() List results = dao.listPersons(Phil, null);assertNotNull(results);assertEquals(2, results.size();for (Iterator iter = results.iterator(); iter.hasNext();) Person person = (Person) iter.next();assertNotNull(person.getId();assertEquals(Phil, PersonUtils.getFirstName(person.getId(),connection);doCommonAsserts(person);第一次使用显示了如何获取数据库连接。您将获取连接的确切机制从测试中隐藏起来了。ConnectionUtils 用于获取连接的机制 DriverManager 的静态方法而不是 JDBC DataSource 或连接池实现 是专门用于测试环境的。第二次使用显示了如何能够在测试库中添加应用程序特有的功能。 PersonUtils.getFirstName() 只是一个使您能够通过最少的击键次数从数据库中检索一条信息的方便方法。测试库中这种类型的功能越多,您对测试环境可以施加的控制就越大。3.1.7. 有效地隔离测试。隔离测试以便一个测试的错误或失败不会影响其他测试或使它们无法成功执行。任何数据库测试的运行都需要预先将系统设置为某个稳定的已知状态。您的测试环境应使您能够自由地以任意顺序运行任意测试集。如果一个测试更改了系统但没有撤销这些更改,那么除非您考虑了这种可能性,否则所有后续测试都将以失败告终。因此,需要隔离测试以消除相互的影响,从而防止这种情况的发生。 应用最佳实践: 有三种简化测试隔离的方法: 撤销在测试执行前或期间所作的所有更改。 在执行任何测试之前清除所有测试状态。 回滚(而非提交)测试期间所作的更改。 前两种方法是关于如何管理测试固件、封装已知系统状态的对象和测试启动时的运行时环境。通过第一种方法,您一般可以使用 JUnit tearDown() 方法来重置系统状态。这种方法存在一些缺点。首先,您需要编写拆卸代码,它本身如果不经过严格测试可能会包含错误。由于这些错误会以不可预测和难以追踪的方式影响后续测试,因此这种错误极难发现。 第二个方法涉及到在测试执行之前清除所有的测试状态。该方法对不与现有数据库交互的新应用程序而言肯定特别合适。以下示例显示了如何将这种方法应用到我们测试类的 setUp() 方法中: protected void setUp() throws Exception super.setUp();SqlPlusRunner.runSQLPlusFile(truncate_data.sql);assertEquals(0, SqlPlusRunner.runSQLPlusFile(persons_data.sql);/complete setup of the fixture by initializing DAO with connection to databasedao = new PersonDAOImpl();dao.setConnection(connection = ConnectionUtils.getConnection();注意截除数据脚本如何在加载测试数据之前运行。这就用非常少的努力保证了测试将在数据库处于可预测状态的前提下开始。除了这个明显的好处之外,它有以下缺点: 各个测试类可以使用它自己的测试数据,这增加了真正的测试数据文件爆炸的可能性。如果测试操作需要处理测试与测试间变化不大的复杂数据集,那么一定程度的测试数据共享会很有好处,即使这意味着偶尔需要使用 tearDown() 方法来重置状态。 有时只是不可能在每次运行之前都重置数据状态。如果您在开发一个使用了由外部 DBA 小组管理的共享旧式数据库实例的应用程序,那么这可能就是这种情况了。 第三种方法涉及到回滚而非提交在测试运行期间所作的更改。这会非常有效,其原因有两个: 现代数据库(如 Oracle 数据库)提供了出色的事务支持,以便您可以仅使用 Connection.rollback(); 轻松撤销当前事务所做的所有工作。 在 Oracle 数据库事务期间所作的所有更改在事务内均可见。换句话说,如果您要继续执行 Cmit() 调用,那么查询要提交的任何更改将非常容易。唯一的限制是您需要使用相同的 Connection 实例来查询在事务期间所作的查询更改。该策略不适于需要在执行期间提交多次的操作。 3.1.8. 分割测试套件。将您的整个测试集分割为一些个别的子套件,以使您的测试环境更加易于管理并共享代码和进程。 如果您在测试一个大型数据库驱动的系统,您可能要进行成百上千或者甚至成千上万个数据库测试。您需要将测试分割为个别的子套件以使测试环境易于管理。您可以从各种方案中进行选择来有效地将测试分组: 将依赖于相同的基础测试数据集的测试分组 为只读的测试与读/写测试创建单独的套件。 为细粒度测试(可能直接利用 SQL 和 PreparedStatements 执行)与在 DAO 实现层执行的粗粒度测试创建单独的套件。 根据应用程序的功能范围分离测试。 通过有效地分割测试,您可以创建一个更加一致和易管理的测试环境。您将有更多的机会共享测试代码、测试数据和常见的设置和拆卸操作。 应用最佳实践: JUnit suite() 方法提供了一种非常方便的分割测试套件的机制。我们的示例应用程序将整个测试套件分割为三个子套件: public class AllTests extends TestCase public static void main(String args)junit.textui.TestRunner.run(suite();public static Test suite() TestSuite suite = new TestSuite();suite.addTest(SqlPlusBasedTests.suite();suite.addTest(DbUnitTests.suite();suite.addTest(TestsOfTests.suite();return suite;基于 SQL*Plus 的测试从使用 DbUn

温馨提示

  • 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
  • 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
  • 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
  • 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
  • 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
  • 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
  • 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。

评论

0/150

提交评论