使用Unitils测试DAO层
Spring 的测试框架为我们提供一个强大的测试环境,解决日常单元测试中遇到的大部分测试难题:如运行多个测试用例和测试方法时,Spring上下文只需创建一次;数据库现场不受破坏;方便手工指定Spring配置文件、手工设定Spring容器是否需要重新加载等。但也存在不足的地方,基本上所有的Java应用都涉及数据库,带数据库应用系统的测试难点在于数据库测试数据的准备、维护、验证及清理。Spring 测试框架并不能很好地解决所有问题。要解决这些问题,必须整合多方资源,如DbUnit、Unitils、Mokito等。其中Unitils正是这样的一个测试框架。
16.5.1 数据库测试的难点
按照Kent Back的观点,单元测试最重要的特性之一应该是可重复性。不可重复的单元测试是没有价值的。因此好的单元测试应该具备独立性和可重复性,对于业务逻辑层,可以通过Mockito底层对象和上层对象来获得这种独立性和可重复性。而DAO层因为是和数据库打交道的层,其单元测试依赖于数据库中的数据。要实现DAO层单元测试的可重复性就需要对每次因单元测试引起数据库中的数据变化进行还原,也就是保护单元测试数据库的数据现场。
16.5.2 扩展Dbunit用Excel准备数据(1)
在测试数据访问层(DAO)时,通常需要经过测试数据的准备、维护、验证及清理的过程。这个过程不仅烦锁,而且容易出错,如数据库现场容易遭受破坏、如何对数据操作正确性进行检查等。虽然Spring测试框架在这一方面为我们减轻了很多工作,如通过事务回滚机制来保存数据库现场等,但对测试数据及验证数据准备方面还没有一种很好的处理方式。Unitils框架出现,改变了难测试DAO的局面,它将SpringModule、DatabaseModule、DbUnitModule等整合在一起,使得DAO的单元测试变得非常容易。基于Unitils框架的DAO测试过程如图16-6所示。
图16-6 基于Unitils框架DAO测试流程 |
以JUnit作为整个测试的基础框架,并采用DbUnit作为自动管理数据库的工具,以XML、Excel作为测试数据及验证数据准备,最后通过Unitils的数据集注解从Excel、XML文件中加载测试数据。使用一个注解标签就可以完成加载、删除数据操作。由于XML作为数据集易用性不如Excel,在这里就不对XML数据集进行讲解。下面我们主要讲解如何应用Excel作为准备及验证数据的载体,减化DAO单元测试。由于Unitils没有提供访问Excel的数据集工厂,因此需要编写插件支持Excel格式数据源。Unitils提供一个访问XML的数据集工厂MultiSchemaXmlDataSetFactory,其继承自DbUnit提供的数据集工厂接口DataSetFactory。我们可以参考这个XML数据集工厂类,编写一个访问Excel的数据集工厂MultiSchemaXlsDataSetFactory及Excel数据集读取器MultiSchemaXlsDataSetReader,然后在数据集读取器中调用Apache POI类库来读写Excel文件,如代码清单16-20所示。
代码清单16 20 MultiSchemaXlsDataSetFactory.java EXCEL数据集工厂
- import org.unitils.core.UnitilsException;
- import org.unitils.DbUnit.datasetfactory.DataSetFactory;
- import org.unitils.DbUnit.util.MultiSchemaDataSet;
- …
- public class MultiSchemaXlsDataSetFactory implements DataSetFactory {
- protected String defaultSchemaName;
- //① 初始化数据集工厂
- public void init(Properties configuration, String defaultSchemaName) {
- this.defaultSchemaName = defaultSchemaName;
- }
- //② 从Excel文件创建数据集
- public MultiSchemaDataSet createDataSet(File... dataSetFiles) {
- try {
- MultiSchemaXlsDataSetReader xlsDataSetReader =
- new MultiSchemaXlsDataSetReader(defaultSchemaName);
- return xlsDataSetReader.readDataSetXls(dataSetFiles);
- } catch (Exception e) {
- throw new UnitilsException("创建数据集失败: "
- + Arrays.toString(dataSetFiles), e);
- }
- }
- //③ 获取数据集文件的扩展名
- public String getDataSetFileExtension() {
- return "xls";
- }
- }
- …
与XML数据集工厂MultiSchemaXmlDataSetFactory一样,Excel的数据集工厂也需要实现数据集工厂接口DataSetFactory的三个方法:init(…)、createDataSet(File... dataSetFiles)、getDataSetFileExtension()。在①处,初始化数据集工厂,需要设置一个默认的数据库表模式名称defaultSchemaName。在②处,执行创建多数据集,具体读取构建数据集的过程封装在Excel读取器MultiSchemaXlsDataSetReader中。在③处,获取数据集文件的扩展名,对Excel文件而言就是"xls"。下面来看一下这个数据集读取器的实现代码。
代码清单16 21 MultiSchemaXlsDataSetReader.java EXCEL数据集读取器
- import org.unitils.core.UnitilsException;
- import org.unitils.DbUnit.datasetfactory.DataSetFactory;
- import org.unitils.DbUnit.util.MultiSchemaDataSet;
- …
- // Excel数据集读取器
- public class MultiSchemaXlsDataSetReader {
- private String defaultSchemaName;
- public MultiSchemaXlsDataSetReader(String defaultSchemaName) {
- this.defaultSchemaName = defaultSchemaName;
- }
- // Excel数据集读取器
- public MultiSchemaDataSet readDataSetXls(File... dataSetFiles) {
- try {
- Map<String, List<ITable>> tableMap = getTables(dataSetFiles);
- MultiSchemaDataSet dataSets = new MultiSchemaDataSet();
- for (Entry<String, List<ITable>> entry : tableMap.entrySet()) {
- List<ITable> tables = entry.getValue();
- try {
- DefaultDataSet ds = new DefaultDataSet(tables
- .toArray(new ITable[] {}));
- dataSets.setDataSetForSchema(entry.getKey(), ds);
- } catch (AmbiguousTableNameException e) {
- throw new UnitilsException("构造DataSet失败!", e);
- }
- }
- return dataSets;
- } catch (Exception e) {
- throw new UnitilsException("解析EXCEL文件出错:", e);
- }
- }
- …
- }
- …
根据传入的多个Excel文件,构造一个多数据集。 其中一个数据集对应一个Excel文件,一个Excel的Sheet表对应一个数据库Table。通过DbUnit提供Excel数据集构造类XlsDataSet,可以很容易将一个Excel文件转换为一个数据集:XlsDataSet(new FileInputStream(xlsFile))。最后将得到的多个DataSet用MultiSchemaDataSet进行封装。
下面就以一个用户DAO的实现类WithoutSpringUserDaoImpl为例,介绍如何使用我们实现的Excel数据集工厂。为了让Unitils使用自定义的数据集工厂,需要在unitils.properties配置文件中指定自定义的数据集工厂。
代码清单16 22 unitils.properties配置文件
- …
- DbUnitModule.DataSet.factory.default=sample.unitils.
dataset.excel.MultiSchemaXlsDataSetFactory - DbUnitModule.ExpectedDataSet.factory.default=sample.
unitils.dataset.excel.MultiSchemaXlsDataSetFactory - …
其中DbUnitModule.DataSet.factory.default是配置数据集工厂类,在测试方法中可以使用@DataSet注解加载指定的准备数据。默认是XML数据集工厂,这里指定自定义数据集工厂类全限定名为sample.unitils.dataset.excel.MultiSchemaXlsDataSetFactory。
其中DbUnitModule. ExpectedDataSet.factory.default是配置验证数据集工厂类,也是指定自定义数据集工厂类,使用@ ExpectedDataSet注解加载验证数据。
代码清单16 23 UserDaoTest.java 用户DAO测试
- import org.unitils.core.UnitilsException;
- import org.unitils.DbUnit.datasetfactory.DataSetFactory;
- import org.unitils.DbUnit.util.MultiSchemaDataSet;
- …
- public class UserDaoTest extends UnitilsJUnit4 {
- @Test
- @DataSet //① 准备测试数据
- public void getUser() {
- …
- }
- @Test
- @DataSet("BaobaoTao.SaveUser.xls") //② 准备测试数据 -
- @ExpectedDataSet //③ 准备验证数据
- public void saveUser()throws Exception {
- …
- }
- }
- …
@DateSet 注解表示了测试时需要寻找DbUnit的数据集文件进行加载,如果没有指明数据集的文件名,则Unitils自动在当前测试用例所在类路径下加载文件名为测试用例类名的数据集文件,实例中①处,将到UserDaoTest.class所在目录加载WithExcelUserDaoTest.xls 数据集文件。
@ExpectedDataSet注解用于加载验证数据集文件,如果没有指明数据集的文件名,则会在当前测试用例所在类路径下加载文件名为testClassName.methodName-result.xls的数据集文件。实例中③处将加载UserDaoTest. saveUser.result.xls数据集文件。
16.5.3 测试实战(1)
《Spring 3.x 企业应用开发实战》第16章实战型单元测试,本章有别于一般书籍的单元测试内容,本书以当前最具实战的JUnit4+Unitils+ Mockito复合测试框架对如何测试数据库、Web的应用进行了深入的讲解。本节为大家介绍测试实战。
- 作者:陈雄华/林开雄来源:电子工业出版社|2012-03-01 15:44
Tech Neo技术沙龙 | 11月25号,九州云/ZStack与您一起探讨云时代网络边界管理实践
16.5.3 测试实战(1)
使用JUnit作为基础测试框架,结合Unitils、DbUnit管理测试数据,并使用我们编写的Excel数据集工厂(见代码清单16 20)。从Excel数据集文件中获取准备数据及验证数据,并使用HSQLDB作为测试数据库。下面详细介绍如何应用Excel准备数据集及验证数据集来测试DAO。
在进行DAO层的测试之前,我们先来认识一下需要测试的UserDaoImpl用户数据访问类。UserDaoImpl用户数据访问类中拥有一个获取用户信息和保存注册用户信息的方法,其代码如下所示。
代码清单16 24 UserDaoImpl
- import java.util.List;
- import org.hibernate.Session;
- import org.hibernate.SessionFactory;
- import org.springframework.orm.hibernate3.HibernateTemplate;
- import com.baobaotao.dao.UserDao;
- import com.baobaotao.domain.User;
- public class UserDaoImpl implements UserDao {
- //通过用户名获取用户信息
- public User findUserByUserName(String userName) {
- String hql = " from User u where u.userName=?";
- List<User> users = getHibernateTemplate().find(hql, userName);
- if (users != null && users.size() > 0)
- return users.get(0);
- else
- return null;
- }
- //保存用户信息
- public void save(User user) {
- getHibernateTemplate().saveOrUpdate(user);
- }
- …
- }
我们认识了需要测试的UserDaoImpl用户数据访问类之后,还需要认识一下用于表示用户领域的对象User,在演示测试保存用户信息及获取用户信息时需要用到此领域对象,其代码如下所示。
代码清单16 25 User
- import javax.persistence.Column;
- import javax.persistence.Entity;
- …
- @Entity
- @GeneratedValue(strategy = GenerationType.IDENTITY)
- @Table(name = "t_user")
- public class User implements Serializable{
- @Id
- @Column(name = "user_id")
- protected int userId;
- @Column(name = "user_name")
- protected String userName;
- protected String password;
- @Column(name = "last_visit")
- protected Date lastVisit;
- @Column(name = "last_ip")
- protected String lastIp;
- @Column(name = "credits")
- private int credits;
- …
- }
16.5.3 测试实战(2)
用户登录日志领域对象LoginLog与用户领域对象Hibernate注解配置一致,这里就不再列出,读者可以参考本书附带光盘中的实例代码。在实例测试中,我们直接使用Hibernate进行持久化操作,所以还需要对Hibernate进行相应配置,详细的配置清单如下所示。
代码清单16 26 hibernate.cfg.xml
- <?xml version="1.0" encoding="UTF-8"?>
- <!DOCTYPE hibernate-configuration PUBLIC
- "-//Hibernate/Hibernate Configuration DTD 3.0//EN"
- "http://hibernate.sourceforge.net/hibernate-configuration-3.0.dtd">
- <hibernate-configuration>
- <session-factory>
- <!--① SQL方言,这边设定的是HSQL -->
- <property name="dialect">org.hibernate.dialect.HSQLDialect</property>
- <!--② 数据库连接配置 -->
- <property name="hibernate.connection.driver_class">org.hsqldb.jdbcDriver</property>
- <property name="hibernate.connection.url">
- jdbc:hsqldb:data/sampledb
- </property>
- <!--设置连接数据库的用户名-->
- <property name="hibernate.connection.username">sa</property>
- <!--设置连接数据库的密码-->
- <property name="hibernate.connection.password"></property>
- <!--③ 设置显示sql语句方便调试-->
- <property name="hibernate.show_sql">true</property>
- <!--④ 配置映射 -->
- <property name="configurationClass">
- org.hibernate.cfg.AnnotationConfiguration
- </property>
- <mapping class="com.baobaotao.domain.User" />
- <mapping class="com.baobaotao.domain.LoginLog" />
- </session-factory>
- </hibernate-configuration>
选用HSQLDB作为测试数据库,在①处,配置HSQLDB的SQL方言HSQLDialect。在②处,对连接数据库驱动及数据库连接进行相应的配置。为了方便测试调试,在③处设置显示Hibernate生成的SQL语句。在④处启用Hibernate的注解功能,并配置相应的领域对象,如实例中的User、LoginLog。将配置好的hibernate.cfg.xml放在src目录下。
配置Unitils测试环境
要在单元测试中更好地使用Unitils ,首先需要在测试源码的根目录中创建一个项目级unitils.properties 配置文件,实例中unitils.properties详细配置清单如下所示。
代码清单16 27 unitils.properties
- #① 启用unitils所需模块
- unitils.modules=database,dbunit,hibernate,spring
- #自定义扩展模块,详见实例源码
- unitils.module.dbunit.className=sample.unitils.module.CustomExtModule
- #② 配置数据库连接
- database.driverClassName=org.hsqldb.jdbcDriver
- database.url=jdbc:hsqldb:data/sampledb;shutdown=true
- database.userName=sa
- databasedatabase.password=
- database.schemaNames=public
- database.dialect = hsqldb
- #③ 配置数据库维护策略.
- updateDataBaseSchema.enabled=true
- #④ 配置数据库表创建策略
- dbMaintainer.autoCreateExecutedScriptsTable=true
- dbMaintainer.script.locations=D:/masterSpring/chapter16/resources/dbscripts
- #⑤ 数据集加载策略
- #DbUnitModule.DataSet.loadStrategy.default=org.unitils.dbunit.datasetloadstrategy.InsertLoadStrategy
- #⑥ 配置数据集工厂
- DbUnitModule.DataSet.factory.default=sample.unitils.dataset.excel.MultiSchemaXlsDataSetFactory
- DbUnitModule.ExpectedDataSet.factory.default=sample.unitils.dataset.excel.MultiSchemaXlsDataSetFactory
- #⑦ 配置事务策略
- DatabaseModule.Transactional.value.default=commit
- #⑧ 配置数据集结构模式XSD生成路径
- dataSetStructureGenerator.xsd.dirName=resources/xsd
16.5.3 测试实战(3)
我们知道unitils.properties中配置的属性是整个项目级别的,整个项目都可以使用这些全局的属性配置。特定用户使用的属性可以设置在unitils-local.properties 文件中,比如user、password和schema,这样每个开发者就使用自定义的测试数据库的schema,而且彼此之间也不会产生影响,实例的详细配置清单如下所示。
代码清单16 28 unitils-local.properties
- …
- database.userName=sa
- databasedatabase.password=
- database.schemaNames=public
- …
如果用户分别在unitils.properties文件及unitils -local.properties文件中对相同属性配置不同值时,将会以unitils-local.properties 配置内容为主。如在unitils.properties配置文件中,也配置了database.schemaNames=xxx,测试时启用的是用户自定义配置中的值database.schemaNames=public。
配置数据集加载策略
默认的数据集加载机制采用先清理后插入的策略,也就是数据在被写入数据库的时候是会先删除数据集中有对应表的数据,然后将数据集中的数据写入数据库。这个加载策略是可配置的,我们可以通过修改DbUnitModule.DataSet.loadStrategy.default的属性值来改变加载策略。如实例代码清单16 27中⑤配置策略,这时加载策略就由先清理后插入变成了插入,数据已经存在表中将不会被删除,测试数据只是进行插入操作。可选的加载策略列表如下所示。
CleanInsertLoadStrategy:先删除dateSet中有关表的数据,然后再插入数据。
InsertLoadStrategy:只插入数据。
RefreshLoadStrategy:有同样key的数据更新,没有的插入。
UpdateLoadStrategy: 有同样key的数据更新,没有的不做任何操作。
配置事务策略
在测试DAO的时候都会填写一些测试数据,每个测试运行都会修改或者更新了数据,当下一个测试运行的时候,都需要将数据恢复到原有状态。如果使用的是Hibernate或者JPA,需要每个测试都运行在事务中,保证系统的正常工作。默认情况下,事务管理是disabled的,我们可以通过修改DatabaseModule.Transactional.value.default配置选项,如实例代码清单16 27中⑧配置策略,这时每个测试都将执行commit,其他可选的配置属性值有rollback和disabled。
准备测试数据库及测试数据
配置好了Unitils基本配置、加载模块、数据集创建策略、事务策略之后,我们就着手开始测试数据库及测试数据准备工作,首先我们创建测试数据库。
创建测试数据库
在源码包根目录下创建一个dbscripts文件夹(文件夹目录结构如图16-7所示),且这个文件夹必须与在unitils.properties 文件中dbMaintainer.script.locations配置项指定的位置一致,如代码清单16 27中④ 所示。
图16-7 数据库脚本文件夹 |
在这个文件夹中创建一个数据库创建脚本文件001_create_sampledb.sql,里面包含创建用户表t_user 及登录日志表t_login_log,详细的脚本如下所示。
代码清单16 29 001_create_sampledb.sql
- CREATE TABLE t_user (
- user_id INT generated by default as identity (start with 100),
- user_name VARCHAR(30),credits INT,
- password VARCHAR(32),last_visit timestamp,
- last_ip VARCHAR(23), primary key (user_id));
- CREATE TABLE t_login_log (
- login_log_id INT generated by default as identity (start with 1),
- user_id INT,
- ip VARCHAR(23),
- login_datetime timestamp,
- primary key (login_log_id));
16.5.3 测试实战(4)
细心的读者可能会发现这个数据库创建脚本文件名好像存在一定的规则,是的,这个脚本文件命名需要按以下规则命名:版本号 + "_" + "自定义名称" + " .sql" 。
连接到测试数据库
测试DAO时,读者要有个疑问,测试数据库用到的数据源来自哪里,怎么让我们测试的DAO类来使用我们的数据源。执行测试实例的时候,Unitils 会根据我们定义的数据库连接属性来创建一个数据源实例连接到测试数据库。随后的DAO测试会重用相同的数据源实例。建立连接的细节定义在unitils.properties配置文件中,如代码清单16 27中的② 配置部分所示。
用Excel准备测试数据
准备好测试数据库之后,剩下的工作就是用Excel来准备测试数据及验证数据,回顾一下我们要测试的UserDaoImpl 类(代码清单16 24),需要对其中的获取用户信息方法findUserByUserName()及保存用户信息方法saveUser()进行测试,所以我们至少需要准备三个Excel数据集文件 ,分别是供查询用户用的数据集BaobaoTao.Users.xls、供保存用户信息用的数据集BaobaoTao.SaveUser.xls及供保存用户信息用的验证数据集BaobaoTao. ExpectedSaveUser.xls。下面以用户数据集BaobaoTao.Users.xls实例进行说明,如图16-8所示。
图16-8 BaobaoTao.Users.xls查询用户数据集 |
在①处t_user表示数据库对应的表名称。在②处表示数据库中t_user表对应的字段名称。在③处表示准备测试的模拟数据。一个数据集文件可以对应多张表,一个Sheet对就一张表。把创建好的数据集文件放到与测试类相同的目录中,如实例中的UserDaoTest类位于com.baobaotao.dao包中,则数据集文件需要放到当前包中。其他两个数据集文件数据结构如图16-9和16-10所示。
图16-9 BaobaoTao.SaveUser.xls准备保存数据集 |
图16-10 BaobaoTao.ExpectedSaveUser准备验证数据集 |
编写UserDaoImpl的测试用例
完成了Unitils环境配置、准备测试数据库及测试数据之后,就可以开始编写用户DAO单元测试类,下面我们为用户数据访问UserDaoImpl编写测试用例类。
代码清单16 30 UserDaoTest用户DAO测试
- import org.unitils.core.UnitilsException;
- import org.unitils.DbUnit.datasetfactory.DataSetFactory;
- import org.unitils.DbUnit.util.MultiSchemaDataSet;
- …
- @SpringApplicationContext( {"baobaotao-dao.xml" }) //① 初始化Spring容器
- public class UserDaoTest extends UnitilsJUnit4 {
- @SpringBean("jdbcUserDao") //② 从Spring容器中加载DAO
- private UserDao userDao;
- @Before
- public void init() {
- }
- …
- }
在①处,通过Unitils提供@ SpringApplicationContext注解加载Spring配置文件,并初始化Spring容器。在②处,通过@SpringBean注解从Spring容器加载一个用户DAO实例。编写UserDaoTest测试基础模型之后,接下来就编写查询用户信息findUserByUserName()的测试方法。代码清单16 31 UserDaoTest.findUserByUserName()测试
- import org.unitils.core.UnitilsException;
- import org.unitils.DbUnit.datasetfactory.DataSetFactory;
- import org.unitils.DbUnit.util.MultiSchemaDataSet;
- …
- public class UserDaoTest extends UnitilsJUnit4 {
- …
- @Test //① 标志为测试方法
- @DataSet("BaobaoTao.Users.xls") //② 加载准备用户测试数据
- public void findUserByUserName() {
- User user = userDao.findUserByUserName("tony"); //③ 从数据库中加载tony用户
- assertNull("不存在用户名为tony的用户!", user);
- user = userDao.findUserByUserName("jan"); //④ 从数据库中加载jan用户
- assertNotNull("jan用户存在!", user);
- assertEquals("jan", user.getUserName());
- assertEquals("123456",user.getPassword());
- assertEquals(10,user.getCredits());
- }
- …
- }
16.5.3 测试实战(5)
在①处,通过JUnit提供@Test注解,把当前方法标志为可测试方法。在②处,通过Unitils提供的@DataSet注解从当前测试类UserDaoTest.class所在的目录寻找支持DbUnit的数据集文件并进行加载。执行测试逻辑之前,会把加载的数据集先持久化到测试数据库中,具体加载数据集的策略详见上文"配置数据集加载策略"部分。实例中采用的默认加载策略,即先删除测试数据库对应表的数据再插入数据集中的测试数据。这种策略可以避免不同测试方法加载数据集相互干扰。在③处执行查询用户方法时,测试数据库中t_user表数据已经是如图16-8 BaobaoTao.Users.xls所示的数据,因此查询不到"tony"用户信息。在④处,执行查询"jan"用户信息,从测试数据集可以看出,可以加载到"jan"的详细信息。最后在IDE中执行UserDaoTest. findUserByUserName()测试方法,按我们预期通过测试,测试结果如图16-11所示。
图16-11 UserDaoTest. findUserByUserName()测试结果 完成了查询用户的测试之后,我们开始着手编写保存用户信息的测试方法,详细的实现代码如下所示。
代码清单16 32 UserDaoTest.saveUser()测试
- import org.unitils.core.UnitilsException;
- import org.unitils.DbUnit.datasetfactory.DataSetFactory;
- import org.unitils.DbUnit.util.MultiSchemaDataSet;
- …
- public class UserDaoTest extends UnitilsJUnit4 {
- …
- @Test //① 标志为测试方法
- @ExpectedDataSet("BaobaoTao.ExpectedSaveUser.xls") //准备验证数据
- public void saveUser()throws Exception {
- User u = new User();
- u.setUserId(1);
- u.setUserName("tom");
- u.setPassword("123456");
- u.setLastVisit(getDate("2011-06-06 08:00:00","yyyy-MM-dd HH:mm:ss"));
- u.setCredits(30);
- u.setLastIp("127.0.0.1");
- userDao.save(u); //执行用户信息更新操作
- }
- …
- }
在①处,通过JUnit提供@Test注解,把当前方法标志为可测试方法。在②处,通过Unitils提供的@ExpectedDataSet注解从当前测试类UserDaoTest.class所在的目录寻找支持DbUnit的验证数据集文件并进行加载,之后验证数据集里的数据和数据库中的数据是否一致。在UserDaoTest.saveUser()测试方法中创建一个User实例,并设置与图16-10 验证数据集中相同的数据,然后执行保存用户操作。最后在IDE中执行UserDaoTest.saveUser()测试方法,执行结果如图16-12所示。
图16-12 UserDaoTest. saveUser()测试结果 虽然已经成功完成了保存用户信息UserDaoTest.saveUser() 方法测试,但还是存在不足的地方,我们测试数据通过硬编码方式直接设置在User实例中。如果需要更改测试数据,只能更改测试代码。大大削减了测试的灵活性。如果能直接从Excel数据集获取测试数据,并自动绑定到目标对象,那我们的测试用例就更加完美。为此笔者编写了一个获取Excel数据集Bean工厂XlsDataSetBeanFactory,用于自动绑定数据集到测试对象。我们对上面的测试方法进行整改,实现代码如代码清单16-33所示。
代码清单16 33 UserDaoTest.java
- import org.unitils.core.UnitilsException;
- import org.unitils.DbUnit.datasetfactory.DataSetFactory;
- import org.unitils.DbUnit.util.MultiSchemaDataSet;
- import sample.unitils.dataset.util.XlsDataSetBeanFactory;
- …
- public class UserDaoTest extends UnitilsJUnit4 {
- …
- @Test //① 标志为测试方法
- @ExpectedDataSet("BaobaoTao.ExpectedSaveUser.xls") //准备验证数据
- public void saveUser()throws Exception {
- //② 从保存数据集中创建Bean
- User u = XlsDataSetBeanFactory.createBean("BaobaoTao.SaveUser.xls”
- ,"t_user", User.class);
- userDao.save(u); //③ 执行用户信息更新操作
- }
- …
- }
16.5.3 测试实战(6)
在②处,通过XlsDataSetBeanFactory.createBean()方法,从当前测试类所在目录加载BaobaoTao.SaveUser.xls数据集文件,其数据结构如图16-9所示。把BaobaoTao.SaveUser.xls中名称为t_user 的Sheet页中的数据绑定到User对象,如果当前Sheet页有多条记录,可以通过XlsDataSetBeanFactory.createBeans()获取用户列表List<User>。最后在IDE中重新执行UserDaoTest.saveUser()测试方法,执行结果如图16-13所示。
图16-13 UserDaoTest. saveUser()测试结果 从测试结果可以看出,执行UserDaoTest.saveUser()测试失败。从右边的失败报告信息我们可以看出,是由于模拟用户的积分与我们期望数据不一致造成,期望用户积分是30,而我们保存用户的积分是10。重新对比一下图16-9 BaobaoTao.SaveUser.xls数据集数据与图16-10 BaobaoTao.ExpectedSaveUser.xls数据集的数据,确实我们准备保存数据集的数据与验证结果的数据不一致。把BaobaoTao.SaveUser.xls数据集中的用户积分更改为30,最后在IDE中重新执行UserDaoTest.saveUser()测试方法,执行结果如图16-14所示。
从测试结果可以看出,保存用户通过测试。从上述的测试实战,我们已经体验到用Excel准备测试数据与验证数据带来的便捷性。到此,我们完成了DAO测试的整个过程,对于XlsDataSetBeanFactory具体实现,读者可以查看本章的实例源码,这里就不做详细分析。下面是实现基本骨架。
图16-14 UserDaoTest. saveUser()测试结果 代码清单16 34 XlsDataSetBeanFactory
- import org.dbunit.dataset.Column;
- import org.dbunit.dataset.DataSetException;
- import org.dbunit.dataset.IDataSet;
- import org.dbunit.dataset.ITable;
- import org.dbunit.dataset.excel.XlsDataSet;
- …
- public class XlsDataSetBeanFactory {
- //从Excel数据集文件创建多个Bean
- public static <T> List<T> createBeans(String file, String tableName,
- Class<T> clazz) throws Exception {
- BeanUtilsBean beanUtils = createBeanUtils();
- List<Map<String, Object>> propsList = createProps(file, tableName);
- List<T> beans = new ArrayList<T>();
- for (Map<String, Object> props : propsList) {
- T bean = clazz.newInstance();
- beanUtils.populate(bean, props);
- beans.add(bean);
- }
- return beans;
- }
- //从Excel数据集文件创建多个Bean
- public static <T> T createBean(String file, String tableName, Class<T> clazz)
- throws Exception {
- BeanUtilsBean beanUtils = createBeanUtils();
- List<Map<String, Object>> propsList = createProps(file, tableName);
- T bean = clazz.newInstance();
- beanUtils.populate(bean, propsList.get(0));
- return bean;
- }
- …
- }
16.5.3 测试实战(4)
细心的读者可能会发现这个数据库创建脚本文件名好像存在一定的规则,是的,这个脚本文件命名需要按以下规则命名:版本号 + "_" + "自定义名称" + " .sql" 。
连接到测试数据库
测试DAO时,读者要有个疑问,测试数据库用到的数据源来自哪里,怎么让我们测试的DAO类来使用我们的数据源。执行测试实例的时候,Unitils 会根据我们定义的数据库连接属性来创建一个数据源实例连接到测试数据库。随后的DAO测试会重用相同的数据源实例。建立连接的细节定义在unitils.properties配置文件中,如代码清单16 27中的② 配置部分所示。
用Excel准备测试数据
准备好测试数据库之后,剩下的工作就是用Excel来准备测试数据及验证数据,回顾一下我们要测试的UserDaoImpl 类(代码清单16 24),需要对其中的获取用户信息方法findUserByUserName()及保存用户信息方法saveUser()进行测试,所以我们至少需要准备三个Excel数据集文件 ,分别是供查询用户用的数据集BaobaoTao.Users.xls、供保存用户信息用的数据集BaobaoTao.SaveUser.xls及供保存用户信息用的验证数据集BaobaoTao. ExpectedSaveUser.xls。下面以用户数据集BaobaoTao.Users.xls实例进行说明,如图16-8所示。
图16-8 BaobaoTao.Users.xls查询用户数据集 |
在①处t_user表示数据库对应的表名称。在②处表示数据库中t_user表对应的字段名称。在③处表示准备测试的模拟数据。一个数据集文件可以对应多张表,一个Sheet对就一张表。把创建好的数据集文件放到与测试类相同的目录中,如实例中的UserDaoTest类位于com.baobaotao.dao包中,则数据集文件需要放到当前包中。其他两个数据集文件数据结构如图16-9和16-10所示。
图16-9 BaobaoTao.SaveUser.xls准备保存数据集 |
图16-10 BaobaoTao.ExpectedSaveUser准备验证数据集 |
编写UserDaoImpl的测试用例
完成了Unitils环境配置、准备测试数据库及测试数据之后,就可以开始编写用户DAO单元测试类,下面我们为用户数据访问UserDaoImpl编写测试用例类。
代码清单16 30 UserDaoTest用户DAO测试
- import org.unitils.core.UnitilsException;
- import org.unitils.DbUnit.datasetfactory.DataSetFactory;
- import org.unitils.DbUnit.util.MultiSchemaDataSet;
- …
- @SpringApplicationContext( {"baobaotao-dao.xml" }) //① 初始化Spring容器
- public class UserDaoTest extends UnitilsJUnit4 {
- @SpringBean("jdbcUserDao") //② 从Spring容器中加载DAO
- private UserDao userDao;
- @Before
- public void init() {
- }
- …
- }
在①处,通过Unitils提供@ SpringApplicationContext注解加载Spring配置文件,并初始化Spring容器。在②处,通过@SpringBean注解从Spring容器加载一个用户DAO实例。编写UserDaoTest测试基础模型之后,接下来就编写查询用户信息findUserByUserName()的测试方法。代码清单16 31 UserDaoTest.findUserByUserName()测试
- import org.unitils.core.UnitilsException;
- import org.unitils.DbUnit.datasetfactory.DataSetFactory;
- import org.unitils.DbUnit.util.MultiSchemaDataSet;
- …
- public class UserDaoTest extends UnitilsJUnit4 {
- …
- @Test //① 标志为测试方法
- @DataSet("BaobaoTao.Users.xls") //② 加载准备用户测试数据
- public void findUserByUserName() {
- User user = userDao.findUserByUserName("tony"); //③ 从数据库中加载tony用户
- assertNull("不存在用户名为tony的用户!", user);
- user = userDao.findUserByUserName("jan"); //④ 从数据库中加载jan用户
- assertNotNull("jan用户存在!", user);
- assertEquals("jan", user.getUserName());
- assertEquals("123456",user.getPassword());
- assertEquals(10,user.getCredits());
- }
- …
- }
使用Unitils测试DAO层的更多相关文章
- 我们应该测试 DAO 层吗?
应该测试 DAO 层吗? 网上有很多人讨论单元测试是否应该包含 DAO 层的测试.笔者觉得,对于一些主要是crud的业务来说,service层和controller层都会非常薄,而主要的逻辑都落在ma ...
- 使用 Spring 2.5 TestContext 测试DAO层
资源准备: mysql5.0 spring-2.5 hibernate-3.2 junit-4.jar 创建表 DROP TABLE IF EXISTS `myproject`.`boys`; ...
- dbunit进行DAO层Excel单元测试
DAO层测试难点 可重复性,每次运行单元测试,得到的数据是重复的 独立性,测试数据与实际数据相互独立 数据库中脏数据预处理 不能给数据库中数据带来变化 DAO层测试方法 使用内存数据库,如H2.优点: ...
- 基于dbunit进行mybatis DAO层Excel单元测试
DAO层测试难点 可重复性,每次运行单元测试,得到的数据是重复的 独立性,测试数据与实际数据相互独立 数据库中脏数据预处理 不能给数据库中数据带来变化 DAO层测试方法 使用内存数据库,如H2.优点: ...
- DAO层设计Junit测试
DAO层的设计: 在实际的开发中有一种项目的程序组织架构方案叫做MVC模式. MVC模式就是按照程序的功能将它们分成三层,分别是Modle层 (模型层).View(显示层).Controller(控制 ...
- 使用springboot实现一个简单的restful crud——02、dao层单元测试,测试从数据库取数据
接着上一篇,上一篇我们创建了项目.创建了实体类,以及创建了数据库数据.这一篇就写一下Dao层,以及对Dao层进行单元测试,看下能否成功操作数据库数据. Dao EmpDao package com.j ...
- mybatis实战教程(mybatis in action)之十:mybatis SqlSessionSupport 的使用,构件DAO 层的应用
前面的系列mybatis 文章,已经基本讲到了mybatis的操作,但都是基于mapper隐射操作的,在mybatis 3中这个mapper 接口貌似充当了以前在ibatis 2中的 DAO 层的作用 ...
- 用Unitils测试BaseDao遇到的问题总结
<Spring 3.0就这么简单>.(陈雄华,林开雄)第8章,对如何用Unitils进行测试简单介绍,下面是我用Unitils进行单元测试过程中遇到的问题的总结. 1.设置好pom.xml ...
- mapper.xml是怎样实现Dao层接口
上午写了一个简单的 从xml读取信息实例化一个Bean对象.下午就开始想mybatis是怎么通过xml文件来实现dao层接口的,一开始想直接用Class.forName(String name)然后调 ...
随机推荐
- SQL Server2012使用导入和导出向导时,用sql语句作为数据源,出现数据源类型会变成202或者203
用MS SqlServer2012进行数据导出时,使用的查询语句导出,但是出现了错误: “发现 xx个未知的列类型转换您只能保存此包“ 点击列查看详细错误信息时,可以看到: [源信息]源位置: 192 ...
- GitLab-CI环境搭建与操作手册
第一章 系统安装简介 1.1. 系统结构 GitLab-CI持续集成服务主要包括gitlab.runner 2个模块.Gitlab主要负责代码文件的管理:runner则负责版本编译.存储.推送等任务. ...
- mvc与mvp与mvvm
==MVC,MVP和MVVM都是常见的软件架构设计模式,它通过分离关注点来改进代码的组织方式== MVC.MVP和MVVM的相同点和不同点 不同部分是C(Controller).P(Presenter ...
- redis应用场景及实例
Redis在很多方面与其他数据库解决方案不同:它使用内存提供主存储支持,而仅使用硬盘做持久性的存储;它的数据模型非常独特,用的是单线程.另一个大区别在于,你可以在开发环境中使用Redis的功能,但却不 ...
- 【bzoj3007】拯救小云公主 二分+对偶图+并查集
题目描述 英雄又即将踏上拯救公主的道路…… 这次的拯救目标是——爱和正义的小云公主. 英雄来到boss的洞穴门口,他一下子就懵了,因为面前不只是一只boss,而是上千只boss.当英雄意识到自己还是等 ...
- linux下编译libmysqlclient, 安装mysql-server mysql-client
cmake . -DCMAKE_INSTALL_PREFIX=/home/zhangyawei/server/depends make make install 安装 mysql-server mys ...
- ZOJ 3874 Permutation Graph ——分治 NTT
发现每一块一定是按照一定的顺序的. 然后与标号无关,并且相同大小的对答案的影响相同. 然后列出递推式,上NTT+分治就可以了. 然后就可以与输入同阶处理答案了. #include <map> ...
- POJ3207 Ikki's Story IV - Panda's Trick 【2-sat】
题目 liympanda, one of Ikki's friend, likes playing games with Ikki. Today after minesweeping with Ikk ...
- pdo防sql注入
http://blog.csdn.net/qq635785620/article/details/11284591
- SQLalchemy 使用记录
1.models.py中添加该方法,可通过该方法转dict #驼峰 def to_hump_dict(self): return {commonUtils.str2Hump(c.name): geta ...