使用 DbUnit


接 續 資 料庫單元測試 的內容,自行撰寫測試用的輔助類別過於繁瑣且存在信任問題,對於類似的需求,你可以使用 DbUnit。假設你的資料表格如下建立:
create table T_BOOKMARK (
   id int not null auto_increment primary key,
   url varchar(255) not null,
   title char(255) not null,
   category char(255) not null
)

你可以建立表格的初始資料集合,每次測試前由DbUnit讀入並建立在表格中,初始資料集合使用XML建立。例如建立dataset.xml:
<?xml version="1.0" encoding="UTF-8"?>
<dataset>
<t_bookmark id="1" url="http://a" title="b" category="c"/>
<t_bookmark id="2" url="http://x" title="y" category="z"/>
</dataset>

<b_bookmark>表 示資料是在T_BOOKMARK表格中,當中的id、url、title與 category屬性分別代表資料將安插至T_BOOKMAR的id、url、title與category欄 位。由於你也會測試資料表格變更後的狀況,所以可以再建立各種預期資料集合。例如建立expected.xml:
<?xml version="1.0" encoding="UTF-8"?>
<dataset>
<t_bookmark id="1" url="http://a" title="b" category="c"/>
<t_bookmark id="2" url="http://x" title="y" category="z"/>
<t_bookmark id="3" url="http://m" title="n" category="o"/>
</dataset>

接下來可以如下建立測試類別,相關說明列於註解中:
package test.cc.openhome;

import static org.junit.Assert.assertEquals;
import static org.dbunit.Assertion.assertEqualsIgnoreCols;

import java.io.FileInputStream;
import java.util.ArrayList;
import java.util.List;

import org.dbunit.database.IDatabaseConnection;
import org.dbunit.dataset.IDataSet;
import org.dbunit.dataset.ITable;
import org.dbunit.dataset.xml.FlatXmlDataSetBuilder;
import org.dbunit.ext.mysql.MySqlConnection;
import org.dbunit.operation.DatabaseOperation;

import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.AfterClass;
import org.junit.Test;

import cc.openhome.dao.BookmarkDAO;
import cc.openhome.dao.BookmarkDAOImpl;
import cc.openhome.model.Bookmark;

import com.mysql.jdbc.jdbc2.optional.MysqlDataSource;

public class BookmarkDAOImplTest {
private static MysqlDataSource dataSource;
// DbUnit 用來與資料庫連線
private static IDatabaseConnection dbConn;

private BookmarkDAO dao;

@BeforeClass
public static void setUpClass() throws Exception {
dataSource = new MysqlDataSource();
dataSource.setUrl("jdbc:mysql://localhost:3306/exercise");
dataSource.setUser("root");
dataSource.setPassword("123456");

dbConn = new MySqlConnection(dataSource.getConnection(), null);
}

@AfterClass
public static void tearDownClass() throws Exception {
dbConn.close();
}

// 讀取指定的資料集合
private static IDataSet getXMLDataSet(String file) throws Exception {
return new FlatXmlDataSetBuilder()
.build(new FileInputStream(file));
}
@Before
public void setUp() throws Exception {
// 測試前讀入初始資料集合、清除表格並將初始資料集合新增至資料庫中的表格
DatabaseOperation.CLEAN_INSERT.execute(
dbConn, getXMLDataSet("dataset.xml"));
dao = new BookmarkDAOImpl(dataSource);
}

@Test
public void testGet() throws Exception {
// 用待測的DAO讀取資料
List<Bookmark> result = dao.get();

// 用 DbUnit 讀取資料
IDataSet dataSet = dbConn.createDataSet();
// 取得表格資料
ITable table = dataSet.getTable("T_BOOKMARK");
List<Bookmark> expected = new ArrayList<Bookmark>();
// 表格中的列(row)數
int rows = table.getRowCount();
for(int i = 0; i < rows; i++) {
// 取得每列的各個欄位
String url = (String) table.getValue(i, "url");
String title = (String) table.getValue(i, "title");
String category = (String) table.getValue(i, "category");
expected.add(new Bookmark(url, title, category));
}

// 斷言相等性
assertEquals(expected, result);
}

@Test
public void testAdd() throws Exception {
// 用待測的DAO安插資料
Bookmark bookmark = new Bookmark("http://m", "n", "o");
dao.add(bookmark);

// 用 DbUnit 取得資料
IDataSet dataSet = dbConn.createDataSet();
// 讀取預期資料集合
IDataSet expected = getXMLDataSet("expected.xml");
// 斷言資料集合相等性,但忽略id欄位
assertEqualsIgnoreCols(expected, dataSet,
"T_BOOKMARK", new String[] {"id"});
}
}

在這邊指定 DatabaseOperation的操作為CLEAN_INSERT,表示清空資料並重新安插指定的資料集合,為DELETE_ALL及INSERT兩 個動作的結合。你可以在這邊找到相關操作的說明:

在斷言的部份,DbUnit提供org.dbunit.Assertion類 別,其中assertEquals()方 法可直接斷言資料集合相等性,由於T_BOOKMARK的id主鍵採用自動遞增,所以在這邊測試時 想忽略id欄位的判別,因此採用org.dbunit.AssertionassertEqualsIgnoreCols()指 定忽略id欄位。

上面的測試類別中,分別使用IDatabaseConnection、 DatabaseOperation等API,你可以使用IDatabaseTester的實作類別來集中進行這些操作。 IDatabaseTester提供了幾個實作類別:
  • JdbcDatabaseTester: 使用DriverManager建立連線
  • PropertiesBasedJdbcDatabaseTester: 讀取系統屬性,使用DriverManager建立連線
  • DataSourceDatabaseTester: 使用DataSource建立連線
  • JndiDatabaseTester: 使用JNDI取 得的DataSource建立連線

例如上例可以改用DataSourceDatabaseTester
package test.cc.openhome;

import static org.junit.Assert.assertEquals;
import static org.dbunit.Assertion.assertEqualsIgnoreCols;

import java.io.FileInputStream;
import java.util.ArrayList;
import java.util.List;

import org.dbunit.DataSourceDatabaseTester;
import org.dbunit.IDatabaseTester;
import org.dbunit.dataset.IDataSet;
import org.dbunit.dataset.ITable;
import org.dbunit.dataset.xml.FlatXmlDataSetBuilder;
import org.dbunit.operation.DatabaseOperation;

import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Test;

import cc.openhome.dao.BookmarkDAO;
import cc.openhome.dao.BookmarkDAOImpl;
import cc.openhome.model.Bookmark;

import com.mysql.jdbc.jdbc2.optional.MysqlDataSource;

public class BookmarkDAOImplTest {
private static MysqlDataSource dataSource;

private static IDatabaseTester databaseTester;

private BookmarkDAO dao;

@BeforeClass
public static void setUpClass() throws Exception {
dataSource = new MysqlDataSource();
dataSource.setUrl("jdbc:mysql://localhost:3306/exercise");
dataSource.setUser("root");
dataSource.setPassword("123456");

databaseTester = new DataSourceDatabaseTester(dataSource);
databaseTester.setDataSet(getXMLDataSet("dataset.xml"));
// 讀入初始資料集合、清除表格並將初始資料集合新增至資料庫中的表格(預設)
//databaseTester.setSetUpOperation(DatabaseOperation.CLEAN_INSERT);
}

// 讀取指定的資料集合
private static IDataSet getXMLDataSet(String file) throws Exception {
return new FlatXmlDataSetBuilder()
.build(new FileInputStream(file));
}

@Before
public void setUp() throws Exception {
databaseTester.onSetup();
dao = new BookmarkDAOImpl(dataSource);
}

@Test
public void testGet() throws Exception {
// 用待測的DAO讀取資料
List<Bookmark> result = dao.get();

// 用 DbUnit 讀取資料
IDataSet dataSet = databaseTester.getConnection().createDataSet();
// 取得表格資料
ITable table = dataSet.getTable("T_BOOKMARK");
List<Bookmark> expected = new ArrayList<Bookmark>();
// 表格中的列(row)數
int rows = table.getRowCount();
for(int i = 0; i < rows; i++) {
// 取得每列的各個欄位
String url = (String) table.getValue(i, "url");
String title = (String) table.getValue(i, "title");
String category = (String) table.getValue(i, "category");
expected.add(new Bookmark(url, title, category));
}

// 斷言相等性
assertEquals(expected, result);
}

@Test
public void testAdd() throws Exception {
// 用待測的DAO安插資料
Bookmark bookmark = new Bookmark("http://m", "n", "o");
dao.add(bookmark);

// 用 DbUnit 取得資料
IDataSet dataSet = databaseTester.getConnection().createDataSet();
// 讀取預期資料集合
IDataSet expected = getXMLDataSet("expected.xml");
// 斷言資料集合相等性,但忽略id欄位
assertEqualsIgnoreCols(expected, dataSet,
"T_BOOKMARK", new String[] {"id"});
}
}

IDatabaseTester 的setSetUpOperation()可指定初始時的操作,onSetup()會執行指定的初始操作,setTearDownOperation()則可指定結束時的操作,onTearDown()則可以執行指定的結束操作。