- 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");
- }
http://www.iteye.com/topic/475172
- 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);
- }
- }
- }