【概念】
dbunit是一个基于junit扩展的数据库测试框架。它提供了大量的类对与数据库相关的操作进行了抽象和封装,虽然在80%的情况,你只需使用它极少的api。它通过使用用户自定义的数据集以及相关操作使数据库处于一种可知的状态,从而使得测试自动化、可重复和相对独立。虽然不用dbunit也可以达到这种目的,但是我们必须为此付出代价(编写大量代码,测试及维护),既然有了这么优秀的开源框架,我们又何必再造轮子。
DbUnit是为数据库驱动的项目提供的一个对JUnit 的扩展,除了提供一些常用功能,它可以将你的数据库置于一个测试轮回之间的状态。
【简介】
为依赖于其他外部系统(如数据库或其他接口)的代码编写单元测试是一件很困难的工作。在这种情况下,有效的单元必须隔离测试对象和外部依赖,以便管理测试对象的状态和行为。
使用mock object对象,是隔离外部依赖的一个有效方法。如果我们的测试对象是依赖于DAO的代码,mock object技术很方便。但如果测试对象变成了DAO本身,又如何进行单元测试呢?
开源的DbUnit项目,为以上的问题提供了一个相当优雅的解决方案。使用DbUnit,开发人员可以控制测试数据库的状态。进行一个DAO单元测试之前,DbUnit为数据库准备好初始化数据;而在测试结束时,DbUnit会把数据库状态恢复到测试前的状态。
【原理】
dbunit的与单元测试相关的两个最重要的核心是org.dbunit.database.IDatabaseConnection 和 org.dbunit.dataset.IDataSet ,前者是产品代码使用的数据库连接的一个简单的封装,后者是对单元测试人员自定义的数据集(通常以xml文件的形式存在,且xml文件的格式也有好几种)的封装。
还有一个很重要的咚咚就是org.dbunit.operation.DatabaseOperation,该类是一个抽象类代表了对数据库的操作,例如CUD以及其组合等, 它采用了退化的工厂模式,可直接通过它获取其具体的子类(代表具体的某种操作)如下:
DatabaseOperation.UPDATE
DatabaseOperation.DELETE
DatabaseOperation.DELETE_ALL
DatabaseOperation.TRUNCATE
DatabaseOperation.REFRESH
DatabaseOperation.CLEAN_INSERT
DatabaseOperation.NONE
工作流程如下:
1)testcase.setup--->testcase.getConnection-->getDataSet----->operation.execute(通常DatabaseOperation.CLEAN_INSERT)
2)testcase.testSomeMethod---->dao.someMethod
3)testcase.teardown---->operation.execute(通常DatabaseOperation.DELETE_ALL或者DatabaseOperation.NONE)
下面是一个Step by Step的教程
1、导入jar包
2、建数据库
create database junit_db default character set UTF8;
use junit_db;
create table j_user(username varchar(50), password varchar(50), nickname varchar(50));
3、创建实体
package com.lxh.model;
public class User {
private String username;
private String password;
private String nickname; ....
}
4、创建数据库连接的工具类
package com.lxh.util;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
public class DbUtil {
public static Connection getConnection() throws SQLException {
Connection con = null;
con = DriverManager.getConnection("jdbc:mysql://localhost:3306/junit_db?useUnicode=true&characterEncoding=utf8", "root", "root");
return con;
}
public static void close(Connection con) {
try {
if (con != null) {
con.close();
}
} catch (SQLException e) {
e.printStackTrace();
}
}
public static void close(PreparedStatement ps) {
try {
if (ps != null) {
ps.close();
}
} catch (SQLException e) {
e.printStackTrace();
}
}
public static void close(ResultSet rs) {
try {
if (rs != null) {
rs.close();
}
} catch (SQLException e) {
e.printStackTrace();
}
}
}
5、创建dbunit的测试数据xml文件,并将文件存储在类路径下的dbunit_xml/j_user.xml中(这个路径随便放,只要在读取文件的时候能读取到就行)
<?xml version="1.0" encoding="UTF-8"?>
<dataset>
<j_user username="admin" password="123" nickname="管理员"/>
</dataset>
dbunit的XML属性支持两种格式的描述:基于XML节点的数据描述;基于XML属性的数据描述。这里展示的是基于XML属性的数据描述。
6、在测试中dbunit的基本使用步骤:(1)、备份数据库 (2)、导入测试基本数据 (3)、还原数据库 (4)、执行测试案例,接下来就按照这个顺序一一展示dbunit的神秘
6.1、备份数据库
6.1.1、备份整个数据库
@Test
public void testBackupAllTable() {
try {
// jdbc的数据库连接
Connection conn = DbUtil.getConnection();
// 创建dbunit的Connection,需要传入数据库连接
IDatabaseConnection jconn = new DatabaseConnection(conn);
// 从dbunit的连接中获取数据集,因为是从数据库连接中获取的数据集,所以数据集就是整个数据库
IDataSet ds = jconn.createDataSet();
// 将整个数据集中的数据导出到E:\\db_backup.xml文件中,并设置编码为UTF-8
FlatXmlDataSet.write(ds, new BufferedWriter(new OutputStreamWriter(new FileOutputStream("E:\\db_backup.xml"), "UTF-8")));
} catch (DataSetException e) {
e.printStackTrace();
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (DatabaseUnitException e) {
e.printStackTrace();
} catch (SQLException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
将这个备份数据的操作做成一个TestCase了,这样就实现了整个数据库的备份,数据的所有数据将会备份到 E:\\db_backup.xml文件中。
6.1.2、备份制定的数据库表
@Test
public void testBackUpTable() {
try {
// jdbc的数据库连接
Connection conn = DbUtil.getConnection();
// 创建dbunit的Connection,需要传入数据库连接
IDatabaseConnection jconn = new DatabaseConnection(conn);
// 通过QueryDataSet可以有效的选择要处理的表作为数据集
QueryDataSet backup = new QueryDataSet(jconn);
// 添加j_user这张表作为备份表
backup.addTable("j_user");
FlatXmlDataSet.write(backup, new BufferedWriter(
new OutputStreamWriter(
new FileOutputStream("E:\\db_backup.xml"), "UTF-8")));
} catch (AmbiguousTableNameException e) {
e.printStackTrace();
} catch (DataSetException e) {
e.printStackTrace();
} catch (DatabaseUnitException e) {
e.printStackTrace();
} catch (SQLException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
同时可以添加多张表,这样就能实现备份指定的表了
6.2、导入测试基本数据
@Test
public void testLoad() {
try {
// jdbc的数据库连接
Connection conn = DbUtil.getConnection();
// 创建dbunit的Connection,需要传入数据库连接
IDatabaseConnection jconn = new DatabaseConnection(conn);
// FlatXmlDataSet是用来获取基于属性存储的属性值 XMLDataSet用来获取基于节点类型存储的属性值
IDataSet ds = new FlatXmlDataSet(
new FlatXmlProducer(
new InputSource(
new BufferedReader(
new InputStreamReader(
this.getClass().getClassLoader().getResourceAsStream("dbunit_xml/j_user.xml"), "UTF-8")
)
)
)
);
// 将数据的数据清空,并且把测试数据插入
DatabaseOperation.CLEAN_INSERT.execute(jconn, ds);
} catch (DataSetException e) {
e.printStackTrace();
} catch (DatabaseUnitException e) {
e.printStackTrace();
} catch (SQLException e) {
e.printStackTrace();
} catch (Exception e) {
e.printStackTrace();
}
}
通过这个方法,我们就可以把 E:\\db_backup.xml中的数据作为基本数据先导入到数据,为单元测试做准备数据
6.3、还原数据库
在进行完我们的所有测试工作后,我们需要将数据库还原,这个时候就要用到我们备份数据库时生成的备份XML文件了
@Test
public void testResume() {
try {
// jdbc的数据库连接
Connection conn = DbUtil.getConnection();
// 创建dbunit的Connection,需要传入数据库连接
IDatabaseConnection jconn = new DatabaseConnection(conn);
// 根據備份文件創建dataset
IDataSet ds = new FlatXmlDataSet(
new FlatXmlProducer(
new InputSource(
new BufferedReader(
new InputStreamReader(
new FileInputStream("E:\\db_backup.xml"), "UTF-8")))));
DatabaseOperation.CLEAN_INSERT.execute(jconn, ds);
} catch (DataSetException e) {
e.printStackTrace();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (DatabaseUnitException e) {
e.printStackTrace();
} catch (SQLException e) {
e.printStackTrace();
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
}
将e://db_backup.xml文件重新导入数据库,就实现了数据库的还原操作。
6.4、通过以上几个步骤的操作,我们现在已经可以写做一个完成整的与数据库隔离的测试代码了
@Test
public void testDbUnit() throws Exception {
// 备份数据库
testBackUpTable();
// 加载测试数据
testLoad();
// 进行测试
IUserDao ud = new UserDaoImpl();
User tu = ud.load("admin");
assertEquals(tu.getUsername(), "admin");
assertEquals(tu.getPassword(), "123");
assertEquals(tu.getNickname(), "管理员");
// 还原数据库
testResume();
}