在Spring中結合Dbunit對Dao進行集成單元測試

Java代碼  收藏代碼
  1. package com.test.dbunit.dao;  
  2.   
  3. import javax.sql.DataSource;  
  4.   
  5. import org.dbunit.Assertion;  
  6. import org.dbunit.database.DatabaseConnection;  
  7. import org.dbunit.database.IDatabaseConnection;  
  8. import org.dbunit.database.QueryDataSet;  
  9. import org.dbunit.dataset.IDataSet;  
  10. import org.dbunit.dataset.xml.FlatXmlDataSet;  
  11. import org.dbunit.operation.DatabaseOperation;  
  12. import org.junit.Assert;  
  13. import org.junit.Before;  
  14. import org.junit.Test;  
  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.   
  22. import com.test.dbunit.entity.User;  
  23.   
  24.   
  25. @ContextConfiguration(locations = { "classpath:testApplicationContext.xml" })  
  26. @TransactionConfiguration(defaultRollback = true)  
  27. public class UserDaoTest extends AbstractTransactionalJUnit4SpringContextTests {  
  28.     @Autowired  
  29.     private UserDao userDao;  
  30.   
  31.     @Autowired  
  32.     private DataSource dataSource;  
  33.   
  34.     private IDatabaseConnection conn;  
  35.   
  36.     @Before  
  37.     public void initDbunit() throws Exception {  
  38.         conn = new DatabaseConnection(DataSourceUtils.getConnection(dataSource));  
  39.     }  
  40.   
  41.     @Test  
  42.     public void saveUser() throws Exception {  
  43.         User user = new User();  
  44.         user.setNick("user001");  
  45.         user.setPassword("password001");  
  46.         userDao.save(user);  
  47.   
  48.         QueryDataSet actual = new QueryDataSet(conn);  
  49.         actual.addTable("user",  
  50.                 "select * from user where user.nick = 'user001'");  
  51.   
  52.         IDataSet expected = new FlatXmlDataSet(new ClassPathResource(  
  53.                 "com/taobao/dbunit/dao/user001.xml").getFile());  
  54.   
  55.         Assertion.assertEquals(expected, actual);  
  56.     }  
  57.   
  58.     @Test  
  59.     public void updateUser() throws Exception {  
  60.   
  61.         IDataSet origen = new FlatXmlDataSet(new ClassPathResource(  
  62.                 "com/taobao/dbunit/dao/user001.xml").getFile());  
  63.   
  64.         DatabaseOperation.INSERT.execute(conn, origen);  
  65.   
  66.         User user = new User();  
  67.         user.setNick("user001");  
  68.         user.setPassword("password002");  
  69.         userDao.update(user);  
  70.   
  71.         QueryDataSet actual = new QueryDataSet(conn);  
  72.         actual.addTable("user",  
  73.                 "select * from user where user.nick = 'user001'");  
  74.   
  75.         IDataSet expected = new FlatXmlDataSet(new ClassPathResource(  
  76.                 "com/taobao/dbunit/dao/user001_updated.xml").getFile());  
  77.   
  78.         Assertion.assertEquals(expected, actual);  
  79.     }  
  80.   
  81.     @Test  
  82.     public void removeUser() throws Exception {  
  83.         IDataSet origen = new FlatXmlDataSet(new ClassPathResource(  
  84.                 "com/taobao/dbunit/dao/user001.xml").getFile());  
  85.         DatabaseOperation.INSERT.execute(conn, origen);  
  86.   
  87.         userDao.remove("user001");  
  88.   
  89.         QueryDataSet actual = new QueryDataSet(conn);  
  90.         actual.addTable("user""select * from user where nick = 'user001'");  
  91.   
  92.         Assert.assertEquals(0, actual.getTable("user").getRowCount());  
  93.           
  94.     }  
  95.       
  96.     @Test  
  97.     public void findUser() throws Exception {  
  98.         IDataSet data = new FlatXmlDataSet(new ClassPathResource(  
  99.                 "com/taobao/dbunit/dao/user001.xml").getFile());  
  100.         DatabaseOperation.INSERT.execute(conn, data);  
  101.   
  102.           
  103.         User user = userDao.getUserByNick("user001");  
  104.   
  105.         Assert.assertEquals("password001", user.getPassword());  
  106.     }  
  107.   
  108. }  

 對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.   
  3. import java.sql.ResultSet;  
  4. import java.sql.SQLException;  
  5.   
  6. import org.springframework.jdbc.core.RowMapper;  
  7.   
  8. import com.test.dbunit.dao.UserDao;  
  9. import com.test.dbunit.entity.User;  
  10.   
  11. public class DefaultUserDao extends BaseDao implements UserDao {  
  12.   
  13.     private static String QUERY_BY_NICK = "select * from user where user.nick = ?";  
  14.       
  15.     private static String REMOVE_USER = "delete from user where user.nick = ?";  
  16.       
  17.     private static String INSERT_USER = "insert into user(nick,password) values(?, ?)";  
  18.       
  19.     private static String UPDATE_USER = "update user set user.password = ? where user.nick = ?";  
  20.     @Override  
  21.     public User getUserByNick(String nick) {  
  22.         return (User) getJdbcTemplate().queryForObject(QUERY_BY_NICK,new Object[]{nick}, new RowMapper(){  
  23.             @Override  
  24.             public Object mapRow(ResultSet rs, int index) throws SQLException {  
  25.                 User user = new User();  
  26.                 user.setNick(rs.getString("nick"));  
  27.                 user.setPassword(rs.getString("password"));  
  28.                 return user;  
  29.             }  
  30.         });  
  31.     }  
  32.   
  33.     @Override  
  34.     public void remove(String nick) {  
  35.         getJdbcTemplate().update(REMOVE_USER, new Object[]{nick});  
  36.     }  
  37.   
  38.     @Override  
  39.     public void save(User user) {  
  40.         getJdbcTemplate().update(INSERT_USER, new Object[]{user.getNick(), user.getPassword()});  
  41.     }  
  42.   
  43.     @Override  
  44.     public void update(User user) {  
  45.         getJdbcTemplate().update(UPDATE_USER, new Object[]{user.getPassword(), user.getNick()});  
  46.     }  
  47.   
  48. }  

 單元測試:

需要注意的地方就是,DataSourceUtils.getConnection(datasource)  通過這種方式獲得數據庫連接初始化Dbunit,能夠保證Dbunit使用的數據連接和當前事務的數據庫連接相同,保證能夠在參與到事務中。Spring的TransactionManager會在開始事務時把當前連接保存到ThreadLocal中,DataSourceUtils.getConnection方法,首先從ThreadLocal中獲取連接。 

user001.xml

Xml代碼  收藏代碼
  1. <?xml version="1.0" encoding="UTF-8"?>  
  2. <dataset>  
  3.     <user nick="user001" password="password001" />  
  4. </dataset>  

 使用dbunit,可以通過xml文件定義數據集,也可以使用其他方式定義,比如Excel,編程方式。

 

 

Dbunit的主要構件

IDatabaseConnection

數據庫鏈接。實現類有DatabaseConnection DatabaseDataSourceConnection ,執行數據庫操作時需要一個連接。

 

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.   
  4. IDataSet expected = new FlatXmlDataSet(new ClassPathResource(  
  5.     "com/taobao/dbunit/dao/user001_updated.xml").getFile());  
  6.   
  7. 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.   
  3. import java.sql.SQLException;  
  4.   
  5. import javax.sql.DataSource;  
  6.   
  7. import org.dbunit.Assertion;  
  8. import org.dbunit.database.DatabaseConnection;  
  9. import org.dbunit.database.IDatabaseConnection;  
  10. import org.dbunit.dataset.DataSetException;  
  11. import org.dbunit.dataset.DefaultDataSet;  
  12. import org.dbunit.dataset.DefaultTable;  
  13. import org.dbunit.dataset.IDataSet;  
  14. import org.dbunit.dataset.xml.FlatXmlDataSet;  
  15. import org.dbunit.operation.DatabaseOperation;  
  16. import org.junit.Assert;  
  17. import org.junit.Before;  
  18. import org.springframework.beans.factory.annotation.Autowired;  
  19. import org.springframework.core.io.ClassPathResource;  
  20. import org.springframework.jdbc.datasource.DataSourceUtils;  
  21. import org.springframework.test.context.ContextConfiguration;  
  22. import org.springframework.test.context.junit4.AbstractTransactionalJUnit4SpringContextTests;  
  23. import org.springframework.test.context.transaction.TransactionConfiguration;  
  24.   
  25. @ContextConfiguration(locations = { "classpath:testApplicationContext.xml" })  
  26. @TransactionConfiguration(defaultRollback = true)  
  27. public class BaseDaoTest extends AbstractTransactionalJUnit4SpringContextTests {  
  28.   
  29.     @Autowired  
  30.     private DataSource dataSource;  
  31.   
  32.     private IDatabaseConnection conn;  
  33.   
  34.     @Before  
  35.     public void initDbunit() throws Exception {  
  36.         conn = new DatabaseConnection(DataSourceUtils.getConnection(dataSource));  
  37.     }  
  38.   
  39.     /** 
  40.      * 清空file中包含的表中的數據,並插入file中指定的數據 
  41.      *  
  42.      * @param file 
  43.      * @throws Exception 
  44.      */  
  45.     protected void setUpDataSet(String file) throws Exception {  
  46.         IDataSet dataset = new FlatXmlDataSet(new ClassPathResource(file)  
  47.                 .getFile());  
  48.         DatabaseOperation.CLEAN_INSERT.execute(conn, dataset);  
  49.     }  
  50.   
  51.     /** 
  52.      * 驗證file中包含的表中的數據和數據庫中的相應表的數據是否一致 
  53.      *  
  54.      * @param file 
  55.      * @throws Exception 
  56.      */  
  57.     protected void verifyDataSet(String file) throws Exception {  
  58.         IDataSet expected = new FlatXmlDataSet(new ClassPathResource(file)  
  59.                 .getFile());  
  60.         IDataSet dataset = conn.createDataSet();  
  61.   
  62.         for (String tableName : expected.getTableNames()) {  
  63.             Assertion.assertEquals(expected.getTable(tableName), dataset  
  64.                     .getTable(tableName));  
  65.         }  
  66.   
  67.     }  
  68.   
  69.     /** 
  70.      * 清空指定的表中的數據 
  71.      *  
  72.      * @param tableName 
  73.      * @throws Exception 
  74.      */  
  75.     protected void clearTable(String tableName) throws Exception {  
  76.         DefaultDataSet dataset = new DefaultDataSet();  
  77.         dataset.addTable(new DefaultTable(tableName));  
  78.         DatabaseOperation.DELETE_ALL.execute(conn, dataset);  
  79.     }  
  80.   
  81.     /** 
  82.      * 驗證指定的表爲空 
  83.      *  
  84.      * @param tableName 
  85.      * @throws DataSetException 
  86.      * @throws SQLException 
  87.      */  
  88.     protected void verifyEmpty(String tableName) throws DataSetException,  
  89.             SQLException {  
  90.         Assert.assertEquals(0, conn.createDataSet().getTable(tableName)  
  91.                 .getRowCount());  
  92.     }  
  93. }  

 使用:

Java代碼  收藏代碼
  1. @Test  
  2. public void updateUser() throws Exception {  
  3.     setUpDataSet("com/taobao/dbunit/dao/user001.xml");  
  4.       
  5.     User user = new User();  
  6.     user.setNick("user001");  
  7.     user.setPassword("password002");  
  8.     userDao.update(user);  
  9.   
  10.     verifyDataSet("com/taobao/dbunit/dao/user001_updated.xml");  
  11. }  

 

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

 

Java代碼  收藏代碼
  1. import java.io.File;  
  2. import java.io.FileWriter;  
  3. import java.io.Writer;  
  4.   
  5. import javax.sql.DataSource;  
  6.   
  7. import org.apache.commons.io.FileUtils;  
  8. import org.dbunit.Assertion;  
  9. import org.dbunit.database.DatabaseConnection;  
  10. import org.dbunit.database.IDatabaseConnection;  
  11. import org.dbunit.database.QueryDataSet;  
  12. import org.dbunit.dataset.IDataSet;  
  13. import org.dbunit.dataset.xml.XmlDataSet;  
  14. import org.dbunit.dataset.xml.XmlDataSetWriter;  
  15. import org.dbunit.operation.DatabaseOperation;  
  16. import org.junit.Before;  
  17. import org.junit.Test;  
  18. import org.springframework.beans.factory.annotation.Autowired;  
  19. import org.springframework.core.io.ClassPathResource;  
  20. import org.springframework.jdbc.datasource.DataSourceUtils;  
  21.   
  22. public class DbUnitUtils extends SpringTxTestCase {  
  23.     private IDatabaseConnection conn;  
  24.   
  25.     public static final String SCHEMA_NAME = "test1";  
  26.   
  27.     @Autowired  
  28.     private DataSource dataSource;  
  29.   
  30.     @Before  
  31.     public void initDbunit() throws Exception {  
  32.         conn = new DatabaseConnection(DataSourceUtils.getConnection(dataSource), SCHEMA_NAME);  
  33.     }  
  34.   
  35.     // @Test  
  36.     public void exportData() {  
  37.         exportTable("/home/test1/temp/depart.xml""depart");  
  38.     }  
  39.   
  40.     @Test  
  41.     public void importData() {  
  42.         String insertFile = "dbunit/depart/insert.xml";  
  43.         importTable(insertFile);  
  44.         verifyDataSet(insertFile);  
  45.     }  
  46.   
  47.     /** 
  48.      * 驗證file中包含的表中的數據和數據庫中的相應表的數據是否一致 
  49.      *  
  50.      * @param xmlFileClassPath 
  51.      * @throws Exception 
  52.      */  
  53.     protected void verifyDataSet(String xmlFileClassPath) {  
  54.         try {  
  55.             IDataSet expected = new XmlDataSet(new ClassPathResource(xmlFileClassPath).getInputStream());  
  56.             IDataSet dataset = conn.createDataSet();  
  57.             for (String tableName : expected.getTableNames()) {  
  58.                 Assertion.assertEquals(expected.getTable(tableName), dataset.getTable(tableName));  
  59.             }  
  60.         } catch (Exception e) {  
  61.             e.printStackTrace();  
  62.             throw new RuntimeException(e);  
  63.         }  
  64.     }  
  65.   
  66.     /** 
  67.      * 導出數據到指定文件 
  68.      *  
  69.      * @param file 
  70.      * @param connection 
  71.      * @throws Exception 
  72.      */  
  73.     private void exportTable(String file, String tableName) {  
  74.         QueryDataSet dataSet = new QueryDataSet(conn);  
  75.         try {  
  76.             File outputFile = new File(file);  
  77.             FileUtils.touch(outputFile);  
  78.             dataSet.addTable(tableName);  
  79.             Writer writer = new FileWriter(file);  
  80.             XmlDataSetWriter w = new XmlDataSetWriter(writer);  
  81.             w.setIncludeColumnComments(true);  
  82.             w.write(dataSet);  
  83.             writer.flush();  
  84.             writer.close();  
  85.         } catch (Exception e) {  
  86.             e.printStackTrace();  
  87.             throw new RuntimeException(e);  
  88.         }  
  89.     }  
  90.   
  91.     /** 
  92.      * 導入數據到表 
  93.      *  
  94.      * @param xmlFileClassPath 
  95.      * @param tableName 
  96.      */  
  97.     public void importTable(String xmlFileClassPath) {  
  98.         try {  
  99.             IDataSet dataSet = new XmlDataSet(new ClassPathResource(xmlFileClassPath).getInputStream());  
  100.             DatabaseOperation.INSERT.execute(conn, dataSet);  
  101.             flush();  
  102.         } catch (Exception e) {  
  103.             e.printStackTrace();  
  104.             throw new RuntimeException(e);  
  105.         }  
  106.     }  
  107. }  

 

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