DbUnit

DbUnit 是一个 JUnit 扩展(也可与 Ant 一起使用),针对数据库驱动的项目,除此之外,在测试运行之间将数据库置于已知状态。

这是一种很好的方法,可以避免一个测试用例损坏数据库时可能出现的无数问题并导致后续测试失败或加剧损坏。


数据库单元测试工具。

Junit 不足之处,如果测试依赖数据库,就会导致测试依赖于环境

如:Dev/Test/Prod 的数据肯定不同,无法保证所有环境下的测试保持一致。DbUnit 正是为此而生。

Quick Start

完整代码地址:test-dbunit

项目结构

│  pom.xml
│
└─src
    ├─main
    │  ├─java
    │  │  └─com
    │  │      └─ryo
    │  │          └─test
    │  │              └─dbunit
    │  │                  ├─dal
    │  │                  │      UserDao.java
    │  │                  │
    │  │                  ├─model
    │  │                  │      User.java
    │  │                  │
    │  │                  └─util
    │  │                          DBUtil.java
    │  │
    │  └─resources
    │      ├─sql
    │      │      init.sql
    │      │
    │      └─xml
    │              user.xml
    │
    └─test
        └─java
            └─com
                └─ryo
                    └─test
                        └─dbunit
                            └─dal
                                    UserDaoTest.java

文件

  • jar

引入需要的 jar

<dependencies>
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <version>5.1.24</version>
    </dependency>

    <dependency>
        <groupId>org.slf4j</groupId>
        <artifactId>slf4j-log4j12</artifactId>
        <version>1.7.5</version>
    </dependency>

    <dependency>
        <groupId>org.dbunit</groupId>
        <artifactId>dbunit</artifactId>
        <version>2.4.8</version>
    </dependency>
</dependencies>
  • init.sql

初始化脚本

CREATE DATABASE IF NOT EXISTS `test`;
USE `test`;

CREATE TABLE `user` (
  `id`       INT(11) NOT NULL AUTO_INCREMENT
  COMMENT '唯一标识',
  `username` VARCHAR(255)     DEFAULT NULL
  COMMENT '用户名',
  `password` VARCHAR(255)     DEFAULT NULL
  COMMENT '密码',
  PRIMARY KEY (`id`)
)
  ENGINE = InnoDB
  DEFAULT CHARSET = utf8
  COMMENT '用户表';
  • User.java

实体 bean

public class User {

    private int id;

    private String username;

    private String password;

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }
}
  • UserDao.java

数据库访问

import com.ryo.test.dbunit.model.User;
import com.ryo.test.dbunit.util.DBUtil;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;

public class UserDao {

    /**
     * 查询用户信息
     *
     * @param username 用户名称
     * @return
     */
    public User queryUser(String username) {
        User user = new User();
        Connection conn = DBUtil.getConnection();
        PreparedStatement pstmt = null;
        ResultSet rs = null;
        try {
            pstmt = conn.prepareStatement("select id, username, password from user where username=?");
            pstmt.setString(1, username);
            rs = pstmt.executeQuery();
            if (rs.next()) {
                user.setId(rs.getInt("id"));
                user.setUsername(rs.getString("username"));
                user.setPassword(rs.getString("password"));
            } else {
                return null;
            }
        } catch (SQLException e) {
            e.printStackTrace();
        } finally {
            DBUtil.closeAll(rs, pstmt, conn);
        }
        return user;
    }

}
  • DBUtil.java

数据库工具类

import java.sql.*;

public class DBUtil {

    /**
     * 获取数据库连接
     * @return
     */
    public static Connection getConnection(){
        Connection conn = null;
        final String className = "com.mysql.jdbc.Driver";
        final String url = "jdbc:mysql://127.0.0.1:3306/test?characterEncoding=UTF-8";
        final String username = "root";
        final String password = "123456";

        try {
            Class.forName(className);
            conn = DriverManager.getConnection(url, username, password);
        } catch (ClassNotFoundException e) {
            System.out.println("数据库驱动加载失败,堆栈轨迹如下");
            e.printStackTrace();
        } catch (SQLException e) {
            System.out.println("数据库连接创建失败,堆栈轨迹如下");
            e.printStackTrace();
        }
        return conn;
    }

    public static void closeAll(ResultSet rs, PreparedStatement pstmt, Connection conn){
        if(null != rs){
            try {
                rs.close();
            } catch (SQLException e) {
                System.out.println("数据库操作的ResultSet关闭失败,堆栈轨迹如下");
                e.printStackTrace();
            }
        }
        if(null != pstmt){
            try {
                pstmt.close();
            } catch (SQLException e) {
                System.out.println("数据库操作的PreparedStatement关闭失败,堆栈轨迹如下");
                e.printStackTrace();
            }
        }
        close(conn);
    }

    public static void close(Connection conn){
        if(null != conn){
            try {
                conn.close();
                if(conn.isClosed()){
                    System.out.println("此数据库连接已关闭-->" + conn);
                }else{
                    System.out.println("此数据库连接关闭失败-->" + conn);
                }
            } catch (SQLException e) {
                System.out.println("数据库连接关闭失败,堆栈轨迹如下");
                e.printStackTrace();
            }
        }
    }
}
  • UserDaoTest.java

测试。将会在项目根路径下生成文件 dataBackup_user.xml 用以备份数据库的数据信息。

import com.ryo.test.dbunit.model.User;
import com.ryo.test.dbunit.util.DBUtil;
import org.dbunit.DatabaseUnitException;
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.dataset.xml.FlatXmlProducer;
import org.dbunit.operation.DatabaseOperation;
import org.junit.*;
import org.xml.sax.InputSource;

import java.io.FileInputStream;
import java.io.FileWriter;
import java.sql.Connection;
import java.sql.SQLException;

/**
 * 缺陷:哪怕是对于数据进行备份还原,当数据的量很大时仍然很浪费时间。我认为这不是一个好方法,但作为入门足以。
 * 建议:可以考虑使用小型数据库,或者单独建立数据库作为测试。
 * @author bbhou
 * @version 1.0.0
 */
public class UserDaoTest {

    private static Connection conn;
    private static IDatabaseConnection dbUnitConn;
    private static String DATA_BACKUP_FILE = "dataBackup_user.xml";

    @BeforeClass
    public static void globalInit() {
        conn = DBUtil.getConnection();
        System.out.println("DB-Unit时获取到数据库连接-->" + conn);
        try {
            //DBUnit中用来操作数据文件的Connection需依赖于数据库连接的Connection
            dbUnitConn = new DatabaseConnection(conn);
        } catch (DatabaseUnitException e) {
            e.printStackTrace();
        }
    }

    @AfterClass
    public static void globalDestroy() {
        DBUtil.close(conn);
        if (null != dbUnitConn) {
            try {
                dbUnitConn.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
    }

    /**
     * 备份数据库中某一张或某几张表的数据
     */
    @Before
    public void init() throws Exception {
        //通过QueryDataSet可以有效的选择要处理的表来作为DataSet
        QueryDataSet dataSet = new QueryDataSet(dbUnitConn);
        //这里指定只备份user表中的数据,如果想备份多个表,那就再addTable(tableName)即可
        dataSet.addTable("user");
        FlatXmlDataSet.write(dataSet, new FileWriter(DATA_BACKUP_FILE));
    }

    /**
     * 还原表数据
     */
    @After
    public void destroy() throws Exception {
        IDataSet dataSet = new FlatXmlDataSet(new FlatXmlProducer(new InputSource(new FileInputStream(DATA_BACKUP_FILE))));
        DatabaseOperation.CLEAN_INSERT.execute(dbUnitConn, dataSet);
    }

    /**
     * 测试查询方法
     */
    @Test
    public void queryUserTest() throws Exception {
        //FlatXmlDataSet用来获取基于属性存储的属性值,XmlDataSet用来获取基于节点类型存储的属性值
        InputSource inputSource = new InputSource(UserDaoTest.class.getClassLoader().getResourceAsStream("xml/user.xml"));
        IDataSet dataSet = new FlatXmlDataSet(new FlatXmlProducer(inputSource));
        //DatabaseOperation类的几个常量值
        //CLEAN_INSERT---->先删除数据库中的所有数据,然后将user.xml中的数据插入数据库
        //DELETE---------->如果数据库存在与user.xml记录的相同的数据,则删除数据库中的该条数据
        //DELETE_ALL------>删除数据库中的所有数据
        //INSERT---------->将user.xml中的数据插入数据库
        //NONE------------>nothing to do
        //REFRESH--------->刷新数据库中的数据
        //TRUNCATE_TABLE-->清空表中的数据
        //UPDATE---------->将数据库中的那条数据更新为user.xml中的数据
        DatabaseOperation.CLEAN_INSERT.execute(dbUnitConn, dataSet);

        //下面开始数据测试
        UserDao dao = new UserDao();
        User user = dao.queryUser("root");
        Assert.assertEquals(user.getId(), 2);
        Assert.assertEquals(user.getPassword(), "123456");
    }
}
  • user.xml

指定测试的内容如下:

<?xml version='1.0' encoding="UTF-8"?>
<dataset>
    <!-- 根据表名编写节点标签,接下来构造数据就可以使用两种方式:子节点或属性 -->
    <!--
    <user>
        <id>2</id>
        <username>root</username>
        <password>123456</password>
    </user>
     -->
    <user id="2" username="root" password="123456"/>
</dataset>

小结

数据库测试最大的问题就是数据准备和数据清空,这个工具很好的解决了这2个问题,值得学习应用一下。

可以考虑和 h2 数据库结合,更加轻量优雅。

h2-database