Java代码
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());
}
}
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还可以对数据的现有数据进行备份,还原,清空现有数据,一个好的测试实践是每一个开发人员一个测试数据库,进而对数据库的数据状态有更好的控制,但现实可能会是共享同一个测试库,所以这种情况下,测试的编写必须多做一些考虑。
待测试的类:
Java代码
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()});
}
}
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代码
<?xml version="1.0" encoding="UTF-8"?>
<dataset>
<user nick="user001" password="password001" />
</dataset>
<?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相同。
Java代码
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);
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方法把数据集插入到数据库。例如:
Java代码
IDataSet origen = new FlatXmlDataSet(new ClassPathResource(
"com/taobao/dbunit/dao/user001.xml").getFile());
DatabaseOperation.INSERT.execute(conn, origen);
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测试基类
Java代码
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());
}
}
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());
}
}
使用:
Java代码
@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");
}
@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");
}
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());
}
}
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还可以对数据的现有数据进行备份,还原,清空现有数据,一个好的测试实践是每一个开发人员一个测试数据库,进而对数据库的数据状态有更好的控制,但现实可能会是共享同一个测试库,所以这种情况下,测试的编写必须多做一些考虑。
待测试的类:
Java代码
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()});
}
}
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代码
<?xml version="1.0" encoding="UTF-8"?>
<dataset>
<user nick="user001" password="password001" />
</dataset>
<?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相同。
Java代码
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);
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方法把数据集插入到数据库。例如:
Java代码
IDataSet origen = new FlatXmlDataSet(new ClassPathResource(
"com/taobao/dbunit/dao/user001.xml").getFile());
DatabaseOperation.INSERT.execute(conn, origen);
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测试基类
Java代码
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());
}
}
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());
}
}
使用:
Java代码
@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");
}
@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");
}
相关推荐
通过以上步骤,我们可以有效地在Spring中结合Dbunit对Dao进行集成单元测试,确保Dao在不同场景下的正确性。这种方法能够提高代码质量,减少因为数据库操作错误导致的问题,同时也方便了持续集成和持续部署过程中的...
集成Spring和DBUnit,主要目的是为了在测试环境中对数据库操作进行控制,确保测试的隔离性和准确性。以下是一些关键步骤: 1. **配置Spring测试环境**:使用Spring Test模块,创建一个继承自`...
本文将深入探讨Spring3中的单元测试和集成测试,并结合DBunit这一数据库测试工具进行说明。 首先,单元测试是针对软件的最小可测试单元进行验证,通常是一个方法。在Spring3中,我们可以使用JUnit作为基础测试框架...
在本篇“Spring3学习笔记(2)-集成ibatis3进行单元测试”中,我们将深入探讨如何在Spring3框架中整合Ibatis3,并利用它来进行单元测试。这个主题对于理解如何在实际项目中实现数据访问层的测试具有重要意义。在现代...
分析DBUnitUtils类的源码可以帮助我们理解作者是如何结合Unitils与DBUnit进行DAO测试的。这可能包括对特定数据库操作的模拟,如事务管理、异常处理以及数据验证逻辑。 6. **工具的使用和集成**: Unitils可以与...
DBUnit 是一个用于数据库测试的Java工具,它与JUnit结合使用,可以帮助开发者在软件开发过程中进行数据驱动的测试。在本文中,我们将深入探讨DBUnit测试规范及其在软件开发中的应用。 首先,按照DBUnit测试规范,...
2.3.4 在Spring中装配DAO 2.4 业务层 2.4.1 UserService 2.4.2 在Spring中装配Service 2.4.3 单元测试 2.5 展现层 2.5.1 配置Spring MVC框架 2.5.2 处理登录请求 2.5.3 JSP视图页面 2.6 运行Web应用 2.7 小结 第2篇...
同时,测试时可以利用Spring的Mockito或DBUnit进行单元测试,确保持久层的正确性。 5. 测试驱动开发(TDD) 在文章中提到的示例中,使用了DbUnit进行测试驱动开发。TDD是一种软件开发方法,强调先写测试用例,再...
2.3.4 在Spring中装配DAO 2.4 业务层 2.4.1 UserService 2.4.2 在Spring中装配Service 2.4.3 单元测试 2.5 展现层 2.5.1 配置Spring MVC框架 2.5.2 处理登录请求 2.5.3 JSP视图页面 2.6 运行Web应用 2.7 小结 第2篇...
使用JUnit和Mockito进行单元测试,通过Spring Test和DBUnit进行集成测试,确保代码的质量和稳定性。 六、代码质量管理与重构 SpringSide 4 遵循良好的编码规范,如SOLID原则,以及代码重构的最佳实践。通过学习...
为了确保代码的质量,本书还强调了单元测试的重要性,并介绍了如何使用JUnit和DBUnit进行单元测试,以及如何将测试集成到持续集成流程中。单元测试能够帮助开发者及时发现并修复代码中的错误,提高软件的稳定性和...
Unitils是一个流行的Java开发框架,专注于自动化单元测试和集成测试。这个压缩包包含了运行和使用Unitils所需的所有组件,使得开发者无需单独下载和管理各个依赖。 Unitils框架的核心特点包括: 1. **数据库支持**...
- 使用SpringTest和DBunit简化DAO层测试,EasyMock进行Service层逻辑测试。 - 采用EHcache和Redis的缓存预热机制提升系统性能。 - 使用Redis的SortedSet实现热门商品和人气商品的排序展示。 - 集成了FastDFS作为...
Spring Mybatis 是一个将 Spring 框架与 Mybatis 数据持久层框架集成的项目,它使得在 Spring 应用中使用 Mybatis 变得更加简便。这个项目通常包含配置、映射器接口与 XML 映射文件、DAO 接口与实现、Service 以及 ...
测试方面,可以使用 Spring Boot Test 或 JUnit 对 Controller 和 Service 层进行单元测试,使用 Hibernate Test 或 DBUnit 对数据访问进行集成测试。 通过这个 "Spring-Hibernate" 项目,你可以深入理解这两个框架...
- `dbunit-2.4.8.jar`: DBUnit 是一个JUnit扩展,用于数据库的单元测试,可以导入/导出数据,执行数据验证。 - `commons-collections-3.2.1.jar`: Apache Commons Collections 包含了许多实用的集合操作类,扩展了...
8. **测试**:为了确保功能的正确性,还需要编写单元测试和集成测试。JUnit可以用于测试Action和Service层,而数据库相关的测试可以借助Hibernate的Session模拟,或者使用数据库测试工具如DBUnit。 以上是SSH框架与...
#### 十、使用JUnit和DBUnit进行单元测试(Unit testing with JUnit and DBUnit) 第十章专注于如何为Hibernate应用程序编写单元测试。这部分内容对于确保代码质量和可维护性至关重要。它首先介绍了如何使用JUnit...
9. **单元测试和集成测试**:为了保证代码质量,项目可能包含了JUnit或TestNG等单元测试框架的测试用例,以及可能的Mockito等工具进行模拟测试。此外,对于数据库相关的操作,可能使用了数据库测试框架如DBUnit进行...