精华帖 (1) :: 良好帖 (11) :: 新手帖 (0) :: 隐藏帖 (1)
|
|
---|---|
作者 | 正文 |
发表时间:2009-09-23
最后修改:2009-11-22
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 { @Autowired private UserDao userDao; @Autowired private DataSource dataSource; private IDatabaseConnection conn; @Before public void initDbunit() throws Exception { conn = new DatabaseConnection(DataSourceUtils.getConnection(dataSource)); } @Test public 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); } @Test public 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); } @Test public 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()); } @Test public 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()); } } 对Dao进行单元测试,一般有两种思路。一是Mock,对使用的底层API进行Mock,比如Hibernate和JDBC接口,判断接口有没有正确调用,另一种是实际访问数据库,判断数据库有没有正确读写。更多的情况下,我更倾向于后者,因为在使用ORM工具或者jdbcTemplate的情况下,dao一般只有简单的几行代码,没有复杂的逻辑,Mock测试一般没有什么意义,我们更关心的是,Hibernate mapping是否正确,ibatis sql是否正确等,所以实际读写数据库才能真正判断一个dao是否正确,这也是我们关心的测试内容。
好的单元测试应该是原子性的,独立的,不应依赖其他测试和上下文,但是要测试数据读写是否正确,就必须涉及初始数据的加载,数据修改的还原等操作。对于初始数据的加载,手动输入很麻烦,一个解决方案就是使用Dbunit,从Xml文件甚至Excel中加载初始数据到数据库,是数据库的值达到一个已知状态。同时还可以使用Dbunit,对数据库的结果状态进行判断,保证和期望的一致。数据修改的还原,可以依赖Spring TransactionalTests,在测试完成后回滚数据库。
Dbunit还可以对数据的现有数据进行备份,还原,清空现有数据,一个好的测试实践是每一个开发人员一个测试数据库,进而对数据库的数据状态有更好的控制,但现实可能会是共享同一个测试库,所以这种情况下,测试的编写必须多做一些考虑。
待测试的类: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()}); } } 单元测试:需要注意的地方就是,DataSourceUtils.getConnection(datasource) , 通过这种方式获得数据库连接初始化Dbunit,能够保证Dbunit使用的数据连接和当前事务的数据库连接相同,保证能够在参与到事务中。Spring的TransactionManager会在开始事务时把当前连接保存到ThreadLocal中,DataSourceUtils.getConnection方法,首先从ThreadLocal中获取连接。 user001.xml<?xml version="1.0" encoding="UTF-8"?> <dataset> <user nick="user001" password="password001" /> </dataset> 使用dbunit,可以通过xml文件定义数据集,也可以使用其他方式定义,比如Excel,编程方式。
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方法把数据集插入到数据库。例如:
IDataSet origen = new FlatXmlDataSet(new ClassPathResource( "com/taobao/dbunit/dao/user001.xml").getFile()); DatabaseOperation.INSERT.execute(conn, origen); 从Xml文件中构建DataSet,使用Insert插入到数据库,初始化测试数据。 Assertion唯一的方法,assertEqual,断言两个数据集或数据表相同。
更多关于Dbunit的组件的介绍:http://www.dbunit.org/components.html
PS:使用Oracle的时候,初始化DatabaseConnection需要传入scheme。new DatabaseConnection(conn,SCHEMA_NAME ) ,SCHMEA_NAME需要大写。 附件提供所有代码下载
一个DAO测试基类package com.taobao.dbunit.dao; import java.sql.SQLException; import javax.sql.DataSource; import org.dbunit.Assertion; import org.dbunit.database.DatabaseConnection; import org.dbunit.database.IDatabaseConnection; import org.dbunit.dataset.DataSetException; import org.dbunit.dataset.DefaultDataSet; import org.dbunit.dataset.DefaultTable; 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.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; @ContextConfiguration(locations = { "classpath:testApplicationContext.xml" }) @TransactionConfiguration(defaultRollback = true) public class BaseDaoTest extends AbstractTransactionalJUnit4SpringContextTests { @Autowired private DataSource dataSource; private IDatabaseConnection conn; @Before public void initDbunit() throws Exception { conn = new DatabaseConnection(DataSourceUtils.getConnection(dataSource)); } /** * 清空file中包含的表中的数据,并插入file中指定的数据 * * @param file * @throws Exception */ protected void setUpDataSet(String file) throws Exception { IDataSet dataset = new FlatXmlDataSet(new ClassPathResource(file) .getFile()); DatabaseOperation.CLEAN_INSERT.execute(conn, dataset); } /** * 验证file中包含的表中的数据和数据库中的相应表的数据是否一致 * * @param file * @throws Exception */ protected void verifyDataSet(String file) throws Exception { IDataSet expected = new FlatXmlDataSet(new ClassPathResource(file) .getFile()); IDataSet dataset = conn.createDataSet(); for (String tableName : expected.getTableNames()) { Assertion.assertEquals(expected.getTable(tableName), dataset .getTable(tableName)); } } /** * 清空指定的表中的数据 * * @param tableName * @throws Exception */ protected void clearTable(String tableName) throws Exception { DefaultDataSet dataset = new DefaultDataSet(); dataset.addTable(new DefaultTable(tableName)); DatabaseOperation.DELETE_ALL.execute(conn, dataset); } /** * 验证指定的表为空 * * @param tableName * @throws DataSetException * @throws SQLException */ protected void verifyEmpty(String tableName) throws DataSetException, SQLException { Assert.assertEquals(0, conn.createDataSet().getTable(tableName) .getRowCount()); } } 使用: @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"); }
声明:ITeye文章版权属于作者,受法律保护。没有作者书面许可不得转载。
推荐链接
|
|
返回顶楼 | |
发表时间:2009-09-23
还有很多想写的,今天没时间了,下次补上
|
|
返回顶楼 | |
发表时间:2009-11-04
能不能说说你们解决数据准备和数据维护的方案啊,在复杂的系统 你要准备相应的测试数据是很麻烦的不觉的么?而已一旦测试数据多起来,数据库发生改变 这部分数据怎么维护啊?还能回归么?
|
|
返回顶楼 | |
发表时间:2010-03-29
我用的2.4.7,有部分代码deprecated了.用了api指定的替代方案测试出错.以下是能通过的代码.
import java.io.File; import java.io.FileWriter; import java.io.Writer; import javax.sql.DataSource; import org.apache.commons.io.FileUtils; 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.XmlDataSet; import org.dbunit.dataset.xml.XmlDataSetWriter; import org.dbunit.operation.DatabaseOperation; 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; public class DbUnitUtils extends SpringTxTestCase { private IDatabaseConnection conn; public static final String SCHEMA_NAME = "test1"; @Autowired private DataSource dataSource; @Before public void initDbunit() throws Exception { conn = new DatabaseConnection(DataSourceUtils.getConnection(dataSource), SCHEMA_NAME); } // @Test public void exportData() { exportTable("/home/test1/temp/depart.xml", "depart"); } @Test public void importData() { String insertFile = "dbunit/depart/insert.xml"; importTable(insertFile); verifyDataSet(insertFile); } /** * 验证file中包含的表中的数据和数据库中的相应表的数据是否一致 * * @param xmlFileClassPath * @throws Exception */ protected void verifyDataSet(String xmlFileClassPath) { try { IDataSet expected = new XmlDataSet(new ClassPathResource(xmlFileClassPath).getInputStream()); IDataSet dataset = conn.createDataSet(); for (String tableName : expected.getTableNames()) { Assertion.assertEquals(expected.getTable(tableName), dataset.getTable(tableName)); } } catch (Exception e) { e.printStackTrace(); throw new RuntimeException(e); } } /** * 导出数据到指定文件 * * @param file * @param connection * @throws Exception */ private void exportTable(String file, String tableName) { QueryDataSet dataSet = new QueryDataSet(conn); try { File outputFile = new File(file); FileUtils.touch(outputFile); dataSet.addTable(tableName); Writer writer = new FileWriter(file); XmlDataSetWriter w = new XmlDataSetWriter(writer); w.setIncludeColumnComments(true); w.write(dataSet); writer.flush(); writer.close(); } catch (Exception e) { e.printStackTrace(); throw new RuntimeException(e); } } /** * 导入数据到表 * * @param xmlFileClassPath * @param tableName */ public void importTable(String xmlFileClassPath) { try { IDataSet dataSet = new XmlDataSet(new ClassPathResource(xmlFileClassPath).getInputStream()); DatabaseOperation.INSERT.execute(conn, dataSet); flush(); } catch (Exception e) { e.printStackTrace(); throw new RuntimeException(e); } } } |
|
返回顶楼 | |
发表时间:2010-04-01
其实有时间可以去了解一下H2数据库
是一个内存数据库,用它来开发,不用引入DBUnit就可以测试DAO了; 希望会对你有帮助; |
|
返回顶楼 | |
发表时间:2010-04-09
congliibm 写道 其实有时间可以去了解一下H2数据库
是一个内存数据库,用它来开发,不用引入DBUnit就可以测试DAO了; 希望会对你有帮助; 使用H2或者HSQL有个问题就是,sql语法的差异问题。用Hibernate还好,可以我们更多的使用ibatis,这个Sql差异问题就麻烦了,尤其是用Oracle的时候。还得为HSQL单独弄一份DDL。实际用起来很多问题啊。Hibernate的话,推荐用这个方法。 |
|
返回顶楼 | |
发表时间:2010-04-09
另外,就算用HSQL,还是有一个测试数据构造的问题。Dbunit还是很有用处的。
|
|
返回顶楼 | |
发表时间:2010-04-09
josengg 写道 能不能说说你们解决数据准备和数据维护的方案啊,在复杂的系统 你要准备相应的测试数据是很麻烦的不觉的么?而已一旦测试数据多起来,数据库发生改变 这部分数据怎么维护啊?还能回归么?
这个确实是一个问题。测试数据构造还是很麻烦的。批量数据生成的时候,我会考虑用数据生成工具,比如EMS Data Generator,然后写个脚本导入到Xml或Excel供dbunit使用。数据维护上,只能依靠团队内部的规范了。你们有没有什么好的方案? |
|
返回顶楼 | |
发表时间:2010-04-09
最后修改:2010-04-09
mshijie 写道 josengg 写道 能不能说说你们解决数据准备和数据维护的方案啊,在复杂的系统 你要准备相应的测试数据是很麻烦的不觉的么?而已一旦测试数据多起来,数据库发生改变 这部分数据怎么维护啊?还能回归么?
这个确实是一个问题。测试数据构造还是很麻烦的。批量数据生成的时候,我会考虑用数据生成工具,比如EMS Data Generator,然后写个脚本导入到Xml或Excel供dbunit使用。数据维护上,只能依靠团队内部的规范了。你们有没有什么好的方案? 把表关联都断掉。 用到什么数据插什么数据。 硬sql insert进去 最主要的是不测试SERVICE 只测试DAO代码量就会少很多 |
|
返回顶楼 | |
浏览 11011 次