首页 诗词 字典 板报 句子 名言 友答 励志 学校 网站地图
当前位置: 首页 > 教程频道 > 数据库 > 其他数据库 >

spring-mock + dbutil 用以测试数据库操作

2013-12-29 
spring-mock + dbutil 用来测试数据库操作大概流程:1.用dbunit创建初始的测试数据。2.用spring-mock 维护测

spring-mock + dbutil 用来测试数据库操作
大概流程:
1.用dbunit创建初始的测试数据。
2.用spring-mock 维护测试过程中的数据会滚,这样可以保证测试后数据库保持原状态。
3.用junit架构测试。
4.用dbunit销毁初始测试数据。

对Dao进行单元测试,一般有两种思路。一是Mock,对使用的底层API进行Mock,比如Hibernate和JDBC接口,判断接口有没有正确调用,另一种是实际访问数据库,判断数据库有没有正确读写。更多的情况下,我更倾向于后者,因为在使用ORM工具或者jdbcTemplate的情况下,dao一般只有简单的几行代码,没有复杂的逻辑,Mock测试一般没有什么意义,我们更关心的是,Hibernate mapping是否正确,ibatis sql是否正确等,所以实际读写数据库才能真正判断一个dao是否正确,这也是我们关心的测试内容。

package com.test.dbunit.dao;import javax.sql.DataSource;import org.dbunit.Assertion;import org.dbunit.database.DatabaseConnection;import org.dbunit.database.IDatabaseConnection;import org.dbunit.database.QueryDataSet;import org.dbunit.dataset.IDataSet;import org.dbunit.dataset.xml.FlatXmlDataSet;import org.dbunit.operation.DatabaseOperation;import org.junit.Assert;import org.junit.Before;import org.junit.Test;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.core.io.ClassPathResource;import org.springframework.jdbc.datasource.DataSourceUtils;import org.springframework.test.context.ContextConfiguration;import org.springframework.test.context.junit4.AbstractTransactionalJUnit4SpringContextTests;import org.springframework.test.context.transaction.TransactionConfiguration;import com.test.dbunit.entity.User;@ContextConfiguration(locations = { "classpath:testApplicationContext.xml" })@TransactionConfiguration(defaultRollback = true)public class UserDaoTest extends AbstractTransactionalJUnit4SpringContextTests {@Autowiredprivate UserDao userDao;@Autowiredprivate DataSource dataSource;private IDatabaseConnection conn;@Beforepublic void initDbunit() throws Exception {conn = new DatabaseConnection(DataSourceUtils.getConnection(dataSource));}@Testpublic void saveUser() throws Exception {User user = new User();user.setNick("user001");user.setPassword("password001");userDao.save(user);QueryDataSet actual = new QueryDataSet(conn);actual.addTable("user","select * from user where user.nick = 'user001'");IDataSet expected = new FlatXmlDataSet(new ClassPathResource("com/taobao/dbunit/dao/user001.xml").getFile());Assertion.assertEquals(expected, actual);}@Testpublic void updateUser() throws Exception {IDataSet origen = new FlatXmlDataSet(new ClassPathResource("com/taobao/dbunit/dao/user001.xml").getFile());DatabaseOperation.INSERT.execute(conn, origen);User user = new User();user.setNick("user001");user.setPassword("password002");userDao.update(user);QueryDataSet actual = new QueryDataSet(conn);actual.addTable("user","select * from user where user.nick = 'user001'");IDataSet expected = new FlatXmlDataSet(new ClassPathResource("com/taobao/dbunit/dao/user001_updated.xml").getFile());Assertion.assertEquals(expected, actual);}@Testpublic void removeUser() throws Exception {IDataSet origen = new FlatXmlDataSet(new ClassPathResource("com/taobao/dbunit/dao/user001.xml").getFile());DatabaseOperation.INSERT.execute(conn, origen);userDao.remove("user001");QueryDataSet actual = new QueryDataSet(conn);actual.addTable("user", "select * from user where nick = 'user001'");Assert.assertEquals(0, actual.getTable("user").getRowCount());}@Testpublic void findUser() throws Exception {IDataSet data = new FlatXmlDataSet(new ClassPathResource("com/taobao/dbunit/dao/user001.xml").getFile());DatabaseOperation.INSERT.execute(conn, data);User user = userDao.getUserByNick("user001");Assert.assertEquals("password001", user.getPassword());}}


package com.test.dbunit.dao.impl;     import java.sql.ResultSet;   import java.sql.SQLException;     import org.springframework.jdbc.core.RowMapper;     import com.test.dbunit.dao.UserDao;   import com.test.dbunit.entity.User;     public class DefaultUserDao extends BaseDao implements UserDao {         private static String QUERY_BY_NICK = "select * from user where user.nick = ?";              private static String REMOVE_USER = "delete from user where user.nick = ?";              private static String INSERT_USER = "insert into user(nick,password) values(?, ?)";              private static String UPDATE_USER = "update user set user.password = ? where user.nick = ?";       @Override      public User getUserByNick(String nick) {           return (User) getJdbcTemplate().queryForObject(QUERY_BY_NICK,new Object[]{nick}, new RowMapper(){               @Override              public Object mapRow(ResultSet rs, int index) throws SQLException {                   User user = new User();                   user.setNick(rs.getString("nick"));                   user.setPassword(rs.getString("password"));                   return user;               }           });       }         @Override      public void remove(String nick) {           getJdbcTemplate().update(REMOVE_USER, new Object[]{nick});       }         @Override      public void save(User user) {           getJdbcTemplate().update(INSERT_USER, new Object[]{user.getNick(), user.getPassword()});       }         @Override      public void update(User user) {           getJdbcTemplate().update(UPDATE_USER, new Object[]{user.getPassword(), user.getNick()});       }     }  

好的单元测试应该是原子性的,独立的,不应依赖其他测试和上下文,但是要测试数据读写是否正确,就必须涉及初始数据的加载,数据修改的还原等操作。对于初始数据的加载,手动输入很麻烦,一个解决方案就是使用Dbunit,从Xml文件甚至Excel中加载初始数据到数据库,是数据库的值达到一个已知状态。同时还可以使用Dbunit,对数据库的结果状态进行判断,保证和期望的一致。数据修改的还原,可以依赖Spring TransactionalTests,在测试完成后回滚数据库。



Dbunit还可以对数据的现有数据进行备份,还原,清空现有数据,一个好的测试实践是每一个开发人员一个测试数据库,进而对数据库的数据状态有更好的控制,但现实可能会是共享同一个测试库,所以这种情况下,测试的编写必须多做一些考虑。

单元测试:
需要注意的地方就是,DataSourceUtils.getConnection(datasource) , 通过这种方式获得数据库连接初始化Dbunit,能够保证Dbunit使用的数据连接和当前事务的数据库连接相同,保证能够在参与到事务中。Spring的TransactionManager会在开始事务时把当前连接保存到ThreadLocal中,DataSourceUtils.getConnection方法,首先从ThreadLocal中获取连接。
1.<?xml version="1.0" encoding="UTF-8"?>  2.<dataset>  3.    <user nick="user001" password="password001" />  4.</dataset>  

使用dbunit,可以通过xml文件定义数据集

Dbunit的主要构件
IDatabaseConnection
数据库链接。实现类有DatabaseConnection 和DatabaseDataSourceConnection ,执行数据库操作时需要一个连接。

IDataSet
数据集,数据集可以从Xml文件Excel等外部文件获取,也可以从数据库查询,或者编程方式构件,数据集可以作为初始数据插入到数据库,也可以作为断言的依据。另外还有IDatatable等辅助类。


比如在updateUser测试中,使用了QueryDataSet,从数据库中构建一个Dataset,再通过FlatXmlDataSet从Xml文件中构建一个Dataset,断言这两个Dataset相同。

QueryDataSet actual = new QueryDataSet(conn);   actual.addTable("user", "select * from user where user.nick = 'user001'");     IDataSet expected = new FlatXmlDataSet(new ClassPathResource(       "com/taobao/dbunit/dao/user001_updated.xml").getFile());     Assertion.assertEquals(expected, actual);  

DatabaseOperation
通过定义的静态字段可以获取一组代表一个数据操作的子类对象,比如DatabaseOperation .INSERT,返回 InsertOperation,通过执行execute方法把数据集插入到数据库。例如:

@Test  public void updateUser() throws Exception {       setUpDataSet("com/taobao/dbunit/dao/user001.xml");              User user = new User();       user.setNick("user001");       user.setPassword("password002");       userDao.update(user);         verifyDataSet("com/taobao/dbunit/dao/user001_updated.xml");   }  

从Xml文件中构建DataSet,使用Insert插入到数据库,初始化测试数据。

热点排行