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

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

http://www.iteye.com/topic/475172


Java代码 复制代码收藏代码
  1. package com.test.dbunit.dao;  

  2. import javax.sql.DataSource;  

  3. import org.dbunit.Assertion;  

  4. import org.dbunit.database.DatabaseConnection;  

  5. import org.dbunit.database.IDatabaseConnection;  

  6. import org.dbunit.database.QueryDataSet;  

  7. import org.dbunit.dataset.IDataSet;  

  8. import org.dbunit.dataset.xml.FlatXmlDataSet;  

  9. import org.dbunit.operation.DatabaseOperation;  

  10. import org.junit.Assert;  

  11. import org.junit.Before;  

  12. import org.junit.Test;  

  13. import org.springframework.beans.factory.annotation.Autowired;  

  14. import org.springframework.core.io.ClassPathResource;  

  15. import org.springframework.jdbc.datasource.DataSourceUtils;  

  16. import org.springframework.test.context.ContextConfiguration;  

  17. import org.springframework.test.context.junit4.AbstractTransactionalJUnit4SpringContextTests;  

  18. import org.springframework.test.context.transaction.TransactionConfiguration;  

  19. import com.test.dbunit.entity.User;  

  20. @ContextConfiguration(locations = { "classpath:testApplicationContext.xml" })  

  21. @TransactionConfiguration(defaultRollback = true)  

  22. publicclass UserDaoTest extends AbstractTransactionalJUnit4SpringContextTests {  

  23. @Autowired

  24. private UserDao userDao;  

  25. @Autowired

  26. private DataSource dataSource;  

  27. private IDatabaseConnection conn;  

  28. @Before

  29. publicvoid initDbunit() throws Exception {  

  30.        conn = new DatabaseConnection(DataSourceUtils.getConnection(dataSource));  

  31.    }  

  32. @Test

  33. publicvoid saveUser() throws Exception {  

  34.        User user = new User();  

  35.        user.setNick("user001");  

  36.        user.setPassword("password001");  

  37.        userDao.save(user);  

  38.        QueryDataSet actual = new QueryDataSet(conn);  

  39.        actual.addTable("user",  

  40. "select * from user where user.nick = 'user001'");  

  41.        IDataSet expected = new FlatXmlDataSet(new ClassPathResource(  

  42. "com/taobao/dbunit/dao/user001.xml").getFile());  

  43.        Assertion.assertEquals(expected, actual);  

  44.    }  

  45. @Test

  46. publicvoid updateUser() throws Exception {  

  47.        IDataSet origen = new FlatXmlDataSet(new ClassPathResource(  

  48. "com/taobao/dbunit/dao/user001.xml").getFile());  

  49.        DatabaseOperation.INSERT.execute(conn, origen);  

  50.        User user = new User();  

  51.        user.setNick("user001");  

  52.        user.setPassword("password002");  

  53.        userDao.update(user);  

  54.        QueryDataSet actual = new QueryDataSet(conn);  

  55.        actual.addTable("user",  

  56. "select * from user where user.nick = 'user001'");  

  57.        IDataSet expected = new FlatXmlDataSet(new ClassPathResource(  

  58. "com/taobao/dbunit/dao/user001_updated.xml").getFile());  

  59.        Assertion.assertEquals(expected, actual);  

  60.    }  

  61. @Test

  62. publicvoid removeUser() throws Exception {  

  63.        IDataSet origen = new FlatXmlDataSet(new ClassPathResource(  

  64. "com/taobao/dbunit/dao/user001.xml").getFile());  

  65.        DatabaseOperation.INSERT.execute(conn, origen);  

  66.        userDao.remove("user001");  

  67.        QueryDataSet actual = new QueryDataSet(conn);  

  68.        actual.addTable("user", "select * from user where nick = 'user001'");  

  69.        Assert.assertEquals(0, actual.getTable("user").getRowCount());  

  70.    }  

  71. @Test

  72. publicvoid findUser() throws Exception {  

  73.        IDataSet data = new FlatXmlDataSet(new ClassPathResource(  

  74. "com/taobao/dbunit/dao/user001.xml").getFile());  

  75.        DatabaseOperation.INSERT.execute(conn, data);  

  76.        User user = userDao.getUserByNick("user001");  

  77.        Assert.assertEquals("password001", user.getPassword());  

  78.    }  

  79. }  


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


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


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


待测试的类:


Java代码 复制代码收藏代码
  1. package com.test.dbunit.dao.impl;  

  2. import java.sql.ResultSet;  

  3. import java.sql.SQLException;  

  4. import org.springframework.jdbc.core.RowMapper;  

  5. import com.test.dbunit.dao.UserDao;  

  6. import com.test.dbunit.entity.User;  

  7. publicclass DefaultUserDao extends BaseDao implements UserDao {  

  8. privatestatic String QUERY_BY_NICK = "select * from user where user.nick = ?";  

  9. privatestatic String REMOVE_USER = "delete from user where user.nick = ?";  

  10. privatestatic String INSERT_USER = "insert into user(nick,password) values(?, ?)";  

  11. privatestatic String UPDATE_USER = "update user set user.password = ? where user.nick = ?";  

  12. @Override

  13. public User getUserByNick(String nick) {  

  14. return (User) getJdbcTemplate().queryForObject(QUERY_BY_NICK,new Object[]{nick}, new RowMapper(){  

  15. @Override

  16. public Object mapRow(ResultSet rs, int index) throws SQLException {  

  17.                User user = new User();  

  18.                user.setNick(rs.getString("nick"));  

  19.                user.setPassword(rs.getString("password"));  

  20. return user;  

  21.            }  

  22.        });  

  23.    }  

  24. @Override

  25. publicvoid remove(String nick) {  

  26.        getJdbcTemplate().update(REMOVE_USER, new Object[]{nick});  

  27.    }  

  28. @Override

  29. publicvoid save(User user) {  

  30.        getJdbcTemplate().update(INSERT_USER, new Object[]{user.getNick(), user.getPassword()});  

  31.    }  

  32. @Override

  33. publicvoid update(User user) {  

  34.        getJdbcTemplate().update(UPDATE_USER, new Object[]{user.getPassword(), user.getNick()});  

  35.    }  

  36. }  


单元测试:

需要注意的地方就是,DataSourceUtils.getConnection(datasource) 通过这种方式获得数据库连接初始化Dbunit,能够保证Dbunit使用的数据连接和当前事务的数据库连接相同,保证能够在参与到事务中。Spring的TransactionManager会在开始事务时把当前连接保存到ThreadLocal中,DataSourceUtils.getConnection方法,首先从ThreadLocal中获取连接。

user001.xml


Xml代码 复制代码收藏代码
  1. <?xmlversion="1.0"encoding="UTF-8"?>

  2. <dataset>

  3. <usernick="user001"password="password001"/>

  4. </dataset>


使用dbunit,可以通过xml文件定义数据集,也可以使用其他方式定义,比如Excel,编程方式。



Dbunit的主要构件

IDatabaseConnection

数据库链接。实现类有DatabaseConnectionDatabaseDataSourceConnection ,执行数据库操作时需要一个连接。


IDataSet

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


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


Java代码 复制代码收藏代码
  1. QueryDataSet actual = new QueryDataSet(conn);  

  2. actual.addTable("user", "select * from user where user.nick = 'user001'");  

  3. IDataSet expected = new FlatXmlDataSet(new ClassPathResource(  

  4. "com/taobao/dbunit/dao/user001_updated.xml").getFile());  

  5. Assertion.assertEquals(expected, actual);  



DatabaseOperation

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




Java代码 复制代码收藏代码
  1. IDataSet origen = new FlatXmlDataSet(new ClassPathResource(  

  2. "com/taobao/dbunit/dao/user001.xml").getFile());  

  3. 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测试基类


Java代码 复制代码收藏代码
  1. package com.taobao.dbunit.dao;  

  2. import java.sql.SQLException;  

  3. import javax.sql.DataSource;  

  4. import org.dbunit.Assertion;  

  5. import org.dbunit.database.DatabaseConnection;  

  6. import org.dbunit.database.IDatabaseConnection;  

  7. import org.dbunit.dataset.DataSetException;  

  8. import org.dbunit.dataset.DefaultDataSet;  

  9. import org.dbunit.dataset.DefaultTable;  

  10. import org.dbunit.dataset.IDataSet;  

  11. import org.dbunit.dataset.xml.FlatXmlDataSet;  

  12. import org.dbunit.operation.DatabaseOperation;  

  13. import org.junit.Assert;  

  14. import org.junit.Before;  

  15. import org.springframework.beans.factory.annotation.Autowired;  

  16. import org.springframework.core.io.ClassPathResource;  

  17. import org.springframework.jdbc.datasource.DataSourceUtils;  

  18. import org.springframework.test.context.ContextConfiguration;  

  19. import org.springframework.test.context.junit4.AbstractTransactionalJUnit4SpringContextTests;  

  20. import org.springframework.test.context.transaction.TransactionConfiguration;  

  21. @ContextConfiguration(locations = { "classpath:testApplicationContext.xml" })  

  22. @TransactionConfiguration(defaultRollback = true)  

  23. publicclass BaseDaoTest extends AbstractTransactionalJUnit4SpringContextTests {  

  24. @Autowired

  25. private DataSource dataSource;  

  26. private IDatabaseConnection conn;  

  27. @Before

  28. publicvoid initDbunit() throws Exception {  

  29.        conn = new DatabaseConnection(DataSourceUtils.getConnection(dataSource));  

  30.    }  

  31. /**

  32.     * 清空file中包含的表中的数据,并插入file中指定的数据

  33.     *  

  34.     * @param file

  35.     * @throws Exception

  36.     */

  37. protectedvoid setUpDataSet(String file) throws Exception {  

  38.        IDataSet dataset = new FlatXmlDataSet(new ClassPathResource(file)  

  39.                .getFile());  

  40.        DatabaseOperation.CLEAN_INSERT.execute(conn, dataset);  

  41.    }  

  42. /**

  43.     * 验证file中包含的表中的数据和数据库中的相应表的数据是否一致

  44.     *  

  45.     * @param file

  46.     * @throws Exception

  47.     */

  48. protectedvoid verifyDataSet(String file) throws Exception {  

  49.        IDataSet expected = new FlatXmlDataSet(new ClassPathResource(file)  

  50.                .getFile());  

  51.        IDataSet dataset = conn.createDataSet();  

  52. for (String tableName : expected.getTableNames()) {  

  53.            Assertion.assertEquals(expected.getTable(tableName), dataset  

  54.                    .getTable(tableName));  

  55.        }  

  56.    }  

  57. /**

  58.     * 清空指定的表中的数据

  59.     *  

  60.     * @param tableName

  61.     * @throws Exception

  62.     */

  63. protectedvoid clearTable(String tableName) throws Exception {  

  64.        DefaultDataSet dataset = new DefaultDataSet();  

  65.        dataset.addTable(new DefaultTable(tableName));  

  66.        DatabaseOperation.DELETE_ALL.execute(conn, dataset);  

  67.    }  

  68. /**

  69.     * 验证指定的表为空

  70.     *  

  71.     * @param tableName

  72.     * @throws DataSetException

  73.     * @throws SQLException

  74.     */

  75. protectedvoid verifyEmpty(String tableName) throws DataSetException,  

  76.            SQLException {  

  77.        Assert.assertEquals(0, conn.createDataSet().getTable(tableName)  

  78.                .getRowCount());  

  79.    }  

  80. }  


使用:


Java代码 复制代码收藏代码
  1. @Test

  2. publicvoid updateUser() throws Exception {  

  3.    setUpDataSet("com/taobao/dbunit/dao/user001.xml");  

  4.    User user = new User();  

  5.    user.setNick("user001");  

  6.    user.setPassword("password002");  

  7.    userDao.update(user);  

  8.    verifyDataSet("com/taobao/dbunit/dao/user001_updated.xml");  

  9. }  


http://yuan.iteye.com/blog/470418

http://www.iteye.com/topic/21630?page=2#181380

http://www.iteye.com/topic/21630?page=2#181380

http://www.blogjava.net/aoxj/archive/2010/08/26/329975.html


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章