dbunit是一个用来进行数据库测试的框架,使用dbunit可以有效的进行数据库的初始化操作,方便开发人员进行数据库的备份,测试和恢复,目前Spring提供比较好的测试整合,可以通过Annotation非常方便的和junit整合,该文主要讲解一些个人对Spring,Junit和dbunit三者整合的心得和一些问题,
主要分成以下几部分介绍。
一、Spring和Junit
Spring和Junit整合非常容易,在Spring3之后就提供了一些非常方便的Annotation来完成对要测试对象的依赖注入
@RunWith(SpringJUnit4ClassRunner.class)//让junit工作在spring环境中
@ContextConfiguration("/beans.xml")//在classes中spring的配置文件
public class TestUser {
@Inject
private
IUserDao userDao;
}
以上基本上完成了Spring和junit的整合,但是无法开启事务,需要通过@TransactionConfiguration和@Transactional两个Anontation来进行指定
@RunWith(SpringJUnit4ClassRunner.class)//让junit工作在spring环境中
@ContextConfiguration("/beans.xml")//在classes中spring的配置文件
//transactionManager表示在spring配置文件中所声明的事务对象
//defaultRollback=true表示操作会自动回滚,这样你在单元测试中所作的操作都不会影响数据库中的数据
@TransactionConfiguration(transactionManager="txManager",
defaultRollback=true)
@Transactional
public class TestUser {
@Inject
private
IUserDao userDao;
@Test
public
void testLoad() {
User u =
userDao.load(1);
u.setNickname("ddd");
//此时可以完成更新,但是当测试结束不会影响数据库
userDao.update(u);
System.out.println(u.getNickname());
}
}
二、dbunit的使用
dbunit可以自动导入配置文件中的数据,并且可以把数据库中的表生成相应的xml配置文件。
2.1、使用dbunit首先创建一个基本的配置文件在classpath中,以下是user.xml文件内容
nickname="admin1"
email="admin1@admin.com" status="1" phone="110"
create_date="2010-12-12"/>
nickname="admin1"
email="admin1@admin.com" status="1" phone="110"
create_date="2010-12-12"/>
nickname="admin1"
email="admin1@admin.com" status="1" phone="110"
create_date="2010-12-12"/>
2.2、创建IDatabaseConnection、IDataSet并且完成测试
IDatabaseConnection是dbunit中重要的部分,等于jdbc的Connection,通过IDatabaseConnection可以创建dbunit所需要的记录集等对象,IDatabaseConnection的创建需要基于JDBC的Connection,以下是创建IDatabaseConnection的代码,Dbutil中可以通过原始的jdbc的方式创建Connection也可以通过Spring中配置的SessionFactory来获取Connection
1
|
dbunitCon = new DatabaseConnection(DbUtil.getConnection());
|
可以通过该对象创建相应的IDataSet对象,以上xml中的格式是通过FlatXmlDataSet对象进行解析,所以通过该对象可以进行记录集的初始化等操作
protected IDataSet createDateSet(String tname) throws
DataSetException {
//配置文件在classes的根目录下
InputStream is =
AbstractDbUnitTestCase
.class
.getClassLoader().getResourceAsStream(tname+".xml");
Assert.assertNotNull("dbunit的基本数据文件不存在",is);
//创建DataSet
return new FlatXmlDataSet(new
FlatXmlProducer(new InputSource(is)));
}
以下代码是进行测试的代码
@Test
public void testListUserRoleIds() throws
Exception {
//创建DataSet对象
IDataSet ds = createDateSet("user");
//将配置文件的数据插入到数据库中
DatabaseOperation.CLEAN_INSERT.execute(dbunitCon,ds);
//进行验证
List
actuals = Arrays.asList(2,3);
List
expected = userDao.listUserRoleIds(2);
EntitiesHelper.assertObjects(expected, actuals);
}
三、解决dbunit可能存在的问题
3.1、对象的外键自关联问题,当存在某个对象和自己有关联的时候,使用dbunit可能存在如下问题,第一外键初始化问题
对于dbunit而言读取配置文件,会以第一个节点作为插入数据的基础,此时由于第一个节点没有pid所以,在数据库的初始化的时候对于其他节点就不会将pid初始化到数据库中,所以无法满足要求,解决这个问题的最好方法是为这个节点设置相应的DTD文件,通过DTD文件来指定这个表的属性,DTD的文件可以通过-->FlatDtdDataSet.write(ds,
new FileWriter(dtdFile));写到指定的文件中,并且在创建IDataSet时通过
1
|
new FlatXmlDataSet( new FlatXmlProducer( new InputSource( is ), new FlatDtdDataSet( new FileReader(dtdFile))));
|
引入相应的DTD文件,这样就可以解决该问题。
3.2、在自关联进行CLEAN_INSERT时报com.mysql.jdbc.exceptions.jdbc4.MySQLIntegrityConstraintViolationException错
这个是由于删除时有外键约束规则的限制,无法删除,此时只要在链接字符串中设置不进行外键约束即可
1
2
3
4
5
6
7
|
public static Connection getConnection() throws SQLException {
Connection con = null ;
//sessionVariables=FOREIGN_KEY_CHECKS=0表示忽略外键关联
"fz" , "fz123" );
return con;
}
|
3.3、数据的还原问题,可能在进行数据库测试时希望测试数据不会影响到原有数据,可以考虑在进行测试之前把数据库先进行备份,之后在测试之后进行还原。为了让测试可以复用,可以考虑写一个公共的测试类来完成dbunit的初始化操作,以下是这个类完整代码
public class
AbstractDbUnitTestCase {
public
static IDatabaseConnection dbunitCon;
//临时文件,用来存储数据库的备份数据
private File tempFile;
//临时文件,用来存储数据库的dtd文件
private File dtdFile;
@BeforeClass
public static void init()
throws DatabaseUnitException, SQLException {
//初始化IDatabaseConnection
dbunitCon = new
DatabaseConnection(DbUtil.getConnection());
}
//根据文件名称创建DataSet对象
protected IDataSet
createDateSet(String tname) throws DataSetException,
FileNotFoundException, IOException {
InputStream is = AbstractDbUnitTestCase
.class
.getClassLoader().getResourceAsStream(tname+".xml");
Assert.assertNotNull("dbunit的基本数据文件不存在",is);
//通过dtd和传入的文件创建测试的IDataSet
return new FlatXmlDataSet(new
FlatXmlProducer(new InputSource(is),new FlatDtdDataSet(new
FileReader(dtdFile))));
}
//备份数据库的所有表
protected void
backupAllTable() throws SQLException, IOException, DataSetException
{
IDataSet ds = dbunitCon.createDataSet();
writeBackupFile(ds);
}
//将表写到临时文件中
private void
writeBackupFile(IDataSet ds) throws IOException, DataSetException
{
tempFile = File.createTempFile("back",
"xml");
dtdFile =
File.createTempFile("back","dtd");
//写dtd
FlatDtdDataSet.write(ds, new
FileWriter(dtdFile));
//写数据表中的文件
FlatXmlDataSet.write(ds, new
FileWriter(tempFile));
}
//备份指定表
protected void
backupCustomTable(String[] tname) throws DataSetException,
IOException {
QueryDataSet ds = new
QueryDataSet(dbunitCon);
for(String str:tname) {
ds.addTable(str);
}
writeBackupFile(ds);
}
//备份一张表
protected void
bakcupOneTable(String tname) throws DataSetException, IOException
{
backupCustomTable(new String[]{tname});
}
//还原备份的表
protected void resumeTable()
throws DatabaseUnitException, SQLException, IOException {
//创建ds的时候引入相应的dtd
IDataSet ds = new FlatXmlDataSet(new
FlatXmlProducer(
new InputSource(new
FileInputStream(tempFile)),new FlatDtdDataSet(new
FileReader(dtdFile))));
DatabaseOperation.CLEAN_INSERT.execute(dbunitCon, ds);
}
//清空数据
@AfterClass
public static void destory()
{
try {
if(dbunitCon!=null) dbunitCon.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
此时只要将测试类继承于这个类就拥有了相应的方法了
|