`
方世玉
  • 浏览: 21967 次
  • 性别: Icon_minigender_1
社区版块
存档分类
最新评论

Dao层的测试实践

阅读更多

1. Dao单元测试的问题
Dao层主要工作是数据库访问,是非常重要的模块。为了保证SQL的正确执行,单元测试是必须的。但是一直以来Dao层的单元测试很难进行,主要因为几个问题
1、 单元测试必须是执行隔离的环境代码,而隔离数据库非常困难,不得不放弃这个念头。所以Dao层需要和数据库直接打交道,但是单元测试要求每次重复的动作结果都是一致,但是由于外部数据库环境的问题,测试环境无法稳定。
2、 现阶段的Dao层一般都会利用Spring的容器组装Dao对象,在辅以一些Support对象。这样的结果就是没有Spring容器,无法测试Dao。
3、 每个测试之前,数据库必须处于一个稳定的已知的状态,这就需要数据准备,而单元测试的数据如果要手工插入到数据库中,工作量过大。
4、 测试用例必须有断言,我们需要通过断言来判断数据是否插入数据,每个字段是否相同,这个如果没有辅助工具,而手工去一个个断言,工作量不能接受的

2. 解决方案
为了解决以上问题,我们选择了Unitils来集成Spring、Dbunit等,完成Dao层的单元测试工作,并和Maven工程结合完成配置。
Unitils的使用很简单,以下面的一个例子来说明。
Dao的代码很简单,是查询、更新一个用户的账户信息
public class AccountDao extends JdbcDaoSupport {

    public Account getAccount(String accountId) {
        List<Account> list = null;

        list = getJdbcTemplate().query("select account_id,balance from tb_account where account_id=?",
                new Object[] { accountId }, new RowMapper<Account>() {

                    @Override
                    public Account mapRow(ResultSet rs, int rowNum) throws SQLException {
                        Account acc = new Account();
                        acc.setAccountId(rs.getString("account_id"));
                        acc.setBalance(rs.getInt("balance"));
                        return acc;
                    }
                });
        if (list.size() > 0) {
            return list.get(0);
        } else {
            return null;
        }
    }

    public int updateAccount(String accountId, int balance) {
        int ret = getJdbcTemplate().update("update tb_account set balance = ? where account_id =?",
                new Object[] { balance, accountId });
        return ret;
    }
}


一、Maven的POM文件修改
在Dao工程中的POM文件加入如下
		<dependency>
			<groupId>org.unitils</groupId>
			<artifactId>unitils-dbunit</artifactId>
			<version>${unitils.version}</version>
			<scope>test</scope>
		</dependency>
		<dependency>
			<groupId>org.unitils</groupId>
			<artifactId>unitils-spring</artifactId>
			<version>${unitils.version}</version>
			<scope>test</scope>
		</dependency>

unitils.version目前最新的为3.3版本

二、Unitils的环境配置
Unitils的启动,需要一个配置文件unitils.properties,这个文件默认需要放到classpath下,我们一般为test/resources/unitils.properties文件。文件内容如下
database.driverClassName=com.mysql.jdbc.Driver
database.url=jdbc:mysql://192.168.100.242:3306/test
database.userName=mantis
database.password=mantispw
database.schemaNames=test
database.dialect=mysql
DatabaseModule.Transactional.value.default=rollback


database.driverClassName为测试数据库的Jdbc驱动
database.url为测试数据库的连接串
database.userName为测试数据库用户名
database.schemaNames为测试数据库的schema,mysql可以不需要,Oracle必填。
database.dialect填写为数据库类型,取值有mysql,oracle,derby等
DatabaseModule.Transactional.value.default指的是单元测试对数据库的修改的事务策略,有rollback,disable,commit等选择,我们一般选择回滚 rollback

三、Spring的集成
Unitils提供了Spring的集成功能,可以在单元测试中让Spring组装我们的Dao,自动注入依赖的DataSource等。
针对Spring集成,我们需要些前置条件
1、 将Dao依赖的Spring配置,包括Property解析、DataSource、事务管理等主要是一些基础配置放到Maven工程的test/resources/testapplication/appContext-common.xml中。
<bean
		class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
		<property name="systemPropertiesModeName" value="SYSTEM_PROPERTIES_MODE_OVERRIDE" />
		<property name="ignoreResourceNotFound" value="false" />
		<property name="ignoreUnresolvablePlaceholders" value="true" />
		<property name="locations">
			<list>
				<value>classpath:testapplication/config.properties</value>
			</list>
		</property>
	</bean>

	<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource"
		destroy-method="close">
		<property name="driverClassName">
			<value>${jdbc.driverClassName}</value>
		</property>
		<property name="url">
			<value>${jdbc.url}</value>
		</property>
		<property name="username">
			<value>${jdbc.username}</value>
		</property>
		<property name="password">
			<value>${jdbc.password}</value>
		</property>
		<property name="maxActive">
			<value>${jdbc.maxActive}</value>
		</property>
		<property name="maxIdle">
			<value>${jdbc.maxIdle}</value>
		</property>
		<property name="initialSize">
			<value>${jdbc.maxIdle}</value>
		</property>
		<property name="maxWait">
			<value>18000</value>
		</property>
		<property name="defaultAutoCommit">
			<value>false</value>
		</property>
	</bean>
	<!-- 事务管理器配置,单数据源事务 -->
	<bean id="transactionManager"
		class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
		<property name="dataSource" ref="dataSource" />
	</bean>

之所以单独把单元测试的基础Spring配置抽出来是因为这里注入的数据源和事务管理器,都用最简单的单数据库事务,简化单元测试的环境。避免实际开发中多数据源事务的问题影响结果。
2、 使用Unitils的Spring启动替换功能,将Spring中的正常的DataSource换为Unitils自身的DataSource。这样做的好处是数据准备的操作和业务sql在一个事务中进行,可以方便一起回滚,不对数据库造成影响。替换的DataSource也是一个Spring配置文件,放到test/resources/testapplication/testDataSource.xml中。
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context"
	xsi:schemaLocation="http://www.springframework.org/schema/beans 
		http://www.springframework.org/schema/beans/spring-beans-3.1.xsd
		http://www.springframework.org/schema/context 
		http://www.springframework.org/schema/context/spring-context-3.1.xsd">
	<bean id="dataSource" class="org.unitils.database.UnitilsDataSourceFactoryBean" />
</beans>


三、测试数据的准备
可以根据dbunit的xml格式准备测试数据,通过执行ExportData这个对象来导出现有测试库的数据,在命令行里面输入要导出的表名,即可把当前测试数据库的现有数据导出为xml
测试的xml文件默认放到test/resources下和测试的代码相同的package中,比如test/resources/com/xxx/dao/下。

四、单元测试用例的编写
单元测试用例需要继承UnitilsJUnit3这个基类,顾名思义这个测试套件是依赖Junit3的。Unitils另外也提供了UnitilsJUnit4的基类。下面是我们的一个测试样例代码

public class AccountDaoTest extends UnitilsJUnit3 {

    @SpringApplicationContext({ "classpath:testapplication/appContext-common.xml",
            "classpath:testapplication/testDatasource.xml", "classpath:META-INF/spring/applicationContext-*.xml" })
    protected ApplicationContext applicationContext;

    @SpringBeanByType
    private AccountDao accountDao;

    @DataSet("ACCOUNT.xml")
    public void testGetAccount() {
        Account account = accountDao.getAccount("S31993k");
        System.out.println(JSON.toJSON(account));
        assertEquals(100, account.getBalance());
    }

    @DataSet("ACCOUNT.xml")
    public void testGetAccountNull() {
        Account account = accountDao.getAccount("23");
        assertEquals(null, account);
    }
    @DataSet("ACCOUNT.xml")
    @ExpectedDataSet("ACCOUNT_NEW.xml")
    public void testUpdateAccount() {
        accountDao.updateAccount("S31993k", 35);

    }

}


protected ApplicationContext applicationContext;
是Spring上下文加载后的变量。上面的@ SpringApplicationContext是指明要加载的Spring配置文件到一个变量,可以通过一个String数组和通配符加载多个配置,可以看到这里我们把common和测试数据源的testDatasource.xml都加载了。如果一个工程中,可以抽象出一个公用的测试基类,将Spring的上下文保存在基类中。

private AccountDao accountDao;
是我们要测试的目标对象,这里需要在前面加上@SpringBeanByType的标注,这样Unitils会自动根据类型,将目标对象从Spring上下文取出,注入到测试代码中

public void testUpdateAccount ()
是测试插入的方法,其上的 @DataSet("ACCOUNT.xml")
指的是插入前的预制数据,Unitils和Dbunit会在执行该方法前,将EMPTY_TABLE.xml中的数据导入到数据库中,下面给出的是一个样例数据的xml。执行前,Unitils会清空该表,然后插入指定的测试数据。注:由于事务最后回滚,清空的动作不会提交,所以不用担心数据的损失。
<?xml version='1.0' encoding='UTF-8'?>
<dataset>
  <tb_account account_id="S31993k" balance="100"/>
 </dataset>



@ExpectedDataSet("ACCOUNT_NEW.xml")
为执行测试用例后的期望数据,Unitils会比较实际结果和期望值,看看是否一致。如果不一致则抛出测试失败。ACCOUNT_NEW.xml的内容如下

<?xml version='1.0' encoding='UTF-8'?>
<dataset>
  <tb_account account_id="S31993k" balance="35"/>
 </dataset>

特别注意,dbunit在对比的时候会忽略主键的对比,因为很多主键是自动生成的。所以在xml不需要加上主键的属性值
public void testGetAccount()是查询一个已经存在的帐户。最后通过断言查询是否存在编号为“S31993k”。注:每次执行一个用例方法的时候,DBUnit都会重新初始化相关的数据表,所以不用担心前面的测试用例操作会影响当前的用例结果

public void testGetAccountNull
这是一个尝试查询一个不存在的帐户信息,最后Dao应该返回的是Null,这里用断言做了判断。


3. 经验总结
Unitils通过整合Dbunit和Spring,让我们单元测试的工作量小而效果好。
下面是做Dao单元测试的经验
1、 尽量让单元测试的数据库和其他测试的数据库分离开,避免相互影响。如果每个开发人员拥有自己的单元测试库那是最好的。
2、 Dbunit需要的数据集可以预先从数据库生成,而后面的测试可以重复利用这些数据集。数据集尽量小,保证只包含影响测试的数据。
3、 数据库的外键是单元测试的障碍,建议去掉外键。数据的完整性应该靠应用程序保证。
4、 没有断言就不能称作单元测试,单元测试一定要自动化,而不是靠人眼判断。
5、 如果Dao的单元测试会涉及到多个表(这种情况比较罕见),可以在一个xml中放置多个表的数据,Dbunit会自动识别导入到不同的表中。
分享到:
评论

相关推荐

    对DAO编写单元测试[1]

    编写对DAO编写单元测试[1]软件测试单元测试作为...对于大多数开发人员来讲,编写单元测试已经成为开发过程中必须的流程和最佳实践。 对普通的逻辑组件编写单元测试是一件容易的事情,由于逻辑组件通常只需要内存资源

    单元测试实践小结[5]

    单元测试实践小结[5] 软件测试 7.XML:XMLUnit 8.J2EE:MockRunner 9.GUI:JFCUnit,Marathor 10.Other:JTestCase(采用XML定义测试过程) 分层架构下的单元测试 1Web层的单元测试 主要测试Controller的数据结构化...

    Dropwizard-test:测试项目以试用 dropwizard

    Dropwizard 测试 介绍 测试 Dropwizard 的简单程序基于 用法 mvn clean package && java -jar target/dropwizard-1.0-SNAPSHOT.jar server target/classes/hello-world.yml 网址 通过在上执行 POST 来运行 GC 杂项 ...

    spark streaming实时网站分析项目实战.rar

    操作步骤: 一.数据采集:视频网站访问日志(编辑python...1.数据库访问dao层方法定义 2.hbase操作工具类开发 3.将spark streaming的处理结果写到hbase中 4.映射到hive数据仓库中 四.数据可视化:见数据可视化项目

    基于SSM的篮球系列网上商城设计与实现(源码+部署说明+演示视频+源码介绍).zip

    源码部分包含了完整的项目结构,包括前端页面、后端控制器、实体类、DAO层接口、Service层接口及实现类、Mapper文件等。部署说明部分详细描述了如何将项目部署到Tomcat服务器上,以便进行测试和运行。演示视频展示了...

    计算机专业实习报告.pdf

    中南大学实习报告 实习地点: 湖南省软件测评中心 专业班级:信安 1001 班 姓 名... 软件的分层 – 视图层 (view:如 jsp,html) – 控制层 (controller) – 业务逻辑层 (service) – 数据访问层 (dao) – 数据层 (bean

    精通Spring(清晰书签版

    第3篇介绍DAO层集成技术,主要围绕JDBC、Hibernate和JPA等持久化技术展开论述,针对Spring使能应用的事务管理和集成测试,也进行了相关介绍;第4篇介绍Java EE服务及技术的集成,主要围绕企业应用中使用的各种Java ...

    iBATIS实战

    11.4 创建自己的DAO层 211 11.4.1 从实现中分离出接口 212 11.4.2 创建一个工厂以解耦 212 11.5 小结 214 第12章 扩展iBATIS 215 12.1 理解可插拔组件的设计 215 12.2 使用自定义类型处理器 217 12.2.1 实现自定义...

    基于J2EE框架的个人博客系统项目毕业设计论文(源码和论文)

    我们掌握了数据库及其应用技术、数据库原理、计算机网络技术等课程,对数据库的设计、应用、维护及局域网的组成有了深刻的认识与一定的动手实践能力,考取了信息处理、程序设计、数据库技术等国家IT认证。...

    spring-jpa-wicket-bootstrap:使用 Spring、JPA、Hibernate、Wicket 和 Bootstrap 的 J2EE Web 模板。 在 Tomcat 和 Postgres DB 上测试

    它演示了MvC 、 SoC 、 IoC 、 DAO 、 Service layer和Open Session in View模式以及其他 J2EE 最佳实践。 该模板可以轻松扩展为功能齐全的基于 Wicket 的 Web 应用程序。 用法 该模板使用 maven 并在 Tomcat7 上...

    NHibernate in Action by Christian Bauer

    第一章描述了一个场景,解释什么是持久层以及如何使它适用于商业应用。...本章还介绍了一个通用Helper类:DAO模式。最后,我们还介绍了web应用程序session的管理,展示如何进行长期的商业项目和如何实施分布式应用。

    Yii2中文手册(中文教程完整版)

    Yii 2.0 权威指南 本教程的发布遵循 Yii 文档使用许可. 版权所有 2014 (c) Yii Software LLC. 介绍 已定稿 关于 Yii 已定稿 从 Yii 1.1 升级 ...已定稿 更上一层楼 ...编撰中 数据访问对象(DAO) - 数据库...已定稿 测试

    tech1-temple-java:Java概念证明存储库

    DropWizard —与DAO,服务,资源层的单元测试一对多关系 GraphQL-Spring Boot作为后端API,Vue.js作为前端(SPA) Spring Boot v1 PoC-基于Spring Boot v1。*的PoC Spring Boot v2 PoC-正在开发中... 未完待续...

    MyStudentManagerSSM-1.0.zip

    iBATIS提供的持久层框架包括SQL Maps和Data Access Objects(DAO)MyBatis 消除了几乎所有的JDBC代码和参数的手工设置以及结果集的检索。MyBatis 使用简单的 XML或注解用于配置和原始映射,将接口和 Java 的POJOs...

    Hibernate实战(第2版 中文高清版)

     16.5.3 测试持久层   16.5.4 考虑性能基准   16.6 小结   第17章 JBoss Seam简介   17.1 Java EE 5.0编程模型   17.1.1 JSF详解   17.1.2 EJB 3.0详解   17.1.3 用JSF和EJB 3.0编写Web应用程序   ...

    JAVA程序开发大全---上半部分

    本书内容主要来自作者多年的软件开发和教学、培训经验,通过实例由浅入深地介绍MyEclipse的基本应用,是一本强调实践技能的实用性指导图书。 本书内容丰富、技术全面、案例实用,而且所有的实例都以MyEclipse工程的...

    软件系统设计方案.pdf

    src 源代码⽬录 -main 存放实现类的源代码 --bean model类 --controller 控制器类 --dao 持久层--service 实现业务功能服务 --util ⼯具类 -test 测试类 三、系统运⾏环境 三、系统运⾏环境 操作系统:...

    java三大框架

    ·一个容易使用的框架可以通过一些例子和文档为用户提供最佳实践。 ·采用成功的框架的代码比自己的代码容易测试 J2EE本身提供了一些框架。比如, Enterprise Java-Beans (EJB) container或者 Servlet engine 而...

    chess_db:Spring Boot演示REST应用程序,允许加载和搜索PGN国际象棋游戏数据库以及进行实时游戏

    使用广泛概念和模式来构建REST API的示例Spring Boot项目,以作为参考和实践。 涵盖的概念 域定义(实体,DAO,架构) 基于JWT的身份验证和授权 上传文件 批处理作业 REST端点 表示层 验证方式 对应关系 错误处理 ...

    网络架构师148讲视频课程

    │ 第128节:应用建议及最佳实践.avi │ 第129节:MongoDB结合应用开发一.avi │ 第130节:MongoDB结合应用开发二.avi │ 第131节:应用MongoDB后体系结构.avi │ 第132节:MogileFS简介和入门.avi │ 第133节:...

Global site tag (gtag.js) - Google Analytics