`
方世玉
  • 浏览: 22213 次
  • 性别: 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会自动识别导入到不同的表中。
分享到:
评论

相关推荐

    SpringBoot 多模块Dao层单元测试

    3. **编写Dao层测试** 在Dao层模块下创建对应的测试类,例如`UserDaoTest.java`。这里我们可以使用Mockito来创建Dao层所依赖的数据源的模拟对象,避免在测试中实际访问数据库。测试类示例: ```java import org....

    使用junit测试ssh中的dao

    “junit使用指南及作业规范.pdf”这份文档可能详细介绍了JUnit的用法,包括更复杂的特性如参数化测试、超时测试、假设测试等,以及如何编写良好的测试实践。遵循这些规范可以提高测试的覆盖率和质量。 在实际开发中...

    junit测试spring,hibernate的dao层代码

    在软件开发过程中,单元测试是确保代码质量的重要环节。`JUnit` 是Java编程语言中最流行的单元测试框架,它允许开发者...通过这样的测试实践,我们可以更好地维护代码,减少因数据层错误导致的问题,提高整体项目质量。

    DAO与三层结构

    DAO(Data Access Object)模式与三层结构是软件开发中常见的设计模式和架构方式,主要用于处理...总结来说,DAO模式和三层结构是构建企业级应用的重要技术,通过学习和实践,开发者可以更好地组织代码,提高软件质量。

    dbutils +dbcp 连接池构建dao层

    "dbutils + dbcp 连接池构建DAO层"是一个常见的Java开发实践,它涉及到数据库访问对象(DAO)的设计模式以及数据库连接池的使用。下面将详细阐述这两个知识点。 首先,DAO(Data Access Object)设计模式是软件工程...

    Hibernate封装dao层

    对DAO层进行充分的单元测试,验证其功能的正确性,可以使用JUnit和Mockito等工具。 9. **最佳实践**: - 避免在DAO层进行复杂的业务逻辑,保持DAO层的简洁性。 - 使用连接池管理数据库连接,如C3P0、HikariCP等...

    省略dao层,采用全注解完成登录功能

    在本项目中,"省略DAO层,采用全注解完成登录功能"是一个创新的实践,旨在简化传统的Java Web开发中的数据访问层(DAO层)结构。DAO层通常用于处理数据库交互,但在本示例中,我们将看到如何利用Java的注解技术来减少...

    数据库持久层的UT测试

    总的来说,"数据库持久层的UT测试"涵盖了如何有效地对MyBatis DAO层进行单元测试,涉及到的工具和概念包括:MyBatis、DAO设计模式、JDK 1.6、Maven、JUnit、Mockito、数据库配置切换、测试数据初始化和回滚。...

    使用Unitils测试DAO

    综上所述,"使用Unitils测试DAO"的主题涵盖了如何利用Unitils库和DBUnit组件来创建可靠的DAO层测试,包括数据库的初始化、数据集的管理、断言的使用,以及自定义工具类的创建和源码分析。这种测试方法有助于提升代码...

    使用代理实现Hibernate Dao层自动事务

    在IT行业中,数据库操作是应用程序的核心部分,而Hibernate作为一款流行的Java ORM(对象关系映射)框架,极大地...在大型项目中,使用代理实现的自动事务管理是最佳实践之一,因为它提高了代码的可测试性和可扩展性。

    dao层增删改查分页查找

    本话题将深入探讨如何在DAO层利用J2EE技术进行这些基本操作,并引入分页查找功能。 首先,让我们了解DAO设计模式。DAO模式是软件设计模式之一,它的主要作用是为业务逻辑层提供一个接口,用来访问和操作数据源,...

    JSP的DAO模式留言板

    DAO层对应的方法会查询数据库,如果找到匹配的记录,则验证通过。否则,返回错误信息。 **五、增删改功能** 1. **添加留言**:用户填写表单后,JSP页面将数据提交到Servlet或Controller,处理后调用DAO的`add...

    对dbunit进行mybatis DAO层Excel单元测试(必看篇)

    一、Mybatis DAO层测试难点 在mybatis DAO层中,对数据库的单元测试存在一些难点: 1. 可重复性:每次运行单元测试,得到的数据是重复的独立的。 2. 独立性:测试数据与实际数据相互独立。 3. 数据库中脏数据...

    Delphi 三层 界面和逻辑的分离 抽取DAO层

    在IT行业中,三层架构是一种常见的软件设计...通过学习和实践这个Delphi三层架构的例子,开发者能够掌握如何有效地分离关注点,提高代码的可复用性和可测试性。这不仅对个人技能提升有益,也有助于团队协作和项目维护。

    PHP_DAO_MYSQL php写的dao看起来还好

    1. **DAO模式**:理解DAO模式的基本概念,它如何隔离业务逻辑和数据访问层,以及其优点,如提高代码可测试性、可复用性和可维护性。 2. **PHP面向对象编程**:了解如何定义类、接口,以及类的继承、封装和多态性,...

    单元测试与TDD实践

    1. **DAO(数据访问对象)层测试**:主要关注数据的读写操作是否正确,以及异常处理机制是否有效。使用EasyMock创建mock对象来模拟数据库行为,可以有效地进行测试。 2. **Service层测试**:这一层通常处理业务逻辑...

    小型web程序实例DAO

    你可以创建模拟的DAO(Mock DAO)来测试Service层,而无需实际连接数据库,这提高了测试的效率和覆盖率。 7. **ORM框架集成**: 当今许多Web应用程序选择使用ORM(Object-Relational Mapping)框架,如Hibernate和...

    基于DAO设计模式的新闻发布系统

    7. 测试:DAO层的独立性使得单元测试变得容易,可以使用Mock对象模拟数据库交互,进行无状态的测试,确保每个操作的正确性。 8. 安全性:在处理新闻发布时,系统需要考虑权限控制,确保只有授权的用户才能发布、...

    myeclipse 自动生成DAO层,实体类,mybatis 实体映射文件

    MyEclipse作为一款强大的Java集成开发环境,提供了丰富的功能,其中包括自动生成DAO层、实体类以及MyBatis的实体映射文件。这样的自动化工具可以帮助开发者节省大量手动编写代码的时间,减少错误,并保持代码的一致...

    Don’t repeat the DAO!

    - **可测试性**:DAO层可以独立单元测试,便于调试和维护。 - **可重用性**:DAO接口可以被多个业务组件复用。 - **灵活性**:更换数据库只需修改DAO实现,不涉及业务代码。 5. **优化与扩展**: - **继承与...

Global site tag (gtag.js) - Google Analytics