SpringBoot教程&笔记|Demo08-整合DBUnit进行单元测试

  heardfate

本文主要讲解如何在springboot下整合DBUnit进行单元测试。

本教程在Demo08基础上添加DBUnit进行单元测试

官方使用指南:http://dbunit.sourceforge.net/howto.html

DBUnit介绍

DBunit 是一种扩展于JUnit的数据库驱动测试框架,它使数据库在测试过程之间处于一种已知状态,如果一个测试用例对数据库造成了破坏性影响,它可以帮助避免造成后面的测试失败或者给出错误结果。
DBunit通过维护真实数据库与数据集(IDataSet)之间的关系来发现与暴露测试过程中的问题。IDataSet 代表一个或多个表的数据。此处IDataSet可以自建,可以由数据库导出,并以多种方式体现,xml文件、XLS文件和数据库查询数据等。
基于DBUnit 的测试的主要接口是IDataSet,可以将数据库模式的全部内容表示为单个IDataSet 实例。这些表本身由Itable 实例来表示。
IDataSet 的实现有很多,每一个都对应一个不同的数据源或加载机制。最常用的几种 IDataSet 实现为: 
FlatXmlDataSet :数据的简单平面文件 XML 表示 
QueryDataSet :用 SQL 查询获得的数据 
DatabaseDataSet :数据库表本身内容的一种表示 
XlsDataSet :数据的excel 表示

测试流程大概是这样的,建立数据库连接 -> 备份表 -> 清空数据表 -> 插入准备的数据 -> 调用Dao层接口 -> 从数据库取实际结果-> 事先准备的期望结果 -> 断言 -> 回滚数据库 -> 关闭数据库连接

添加依赖

引入 dbunitspring-boot-starter-log4j2 依赖:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
    <exclusions>
        <exclusion>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-logging</artifactId>
        </exclusion>
    </exclusions>
</dependency>

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-log4j2</artifactId>
    <version>2.1.0.RC1</version>
</dependency>

<dependency>
    <groupId>org.dbunit</groupId>
    <artifactId>dbunit</artifactId>
    <version>2.6.0</version>
</dependency>

添加依赖

数据准备

初始数据UserDriver.xml,这里是dbunitFlatXmlDataSet格式,元素名代表表名,属性对应字段名。

<?xml version="1.0" encoding="UTF-8"?>
<dataset>
    <user id="1" name="userOne" age="18" email="user-one@heardfate.com"/>
    <user id="2" name="userTwo" age="20" email="userTwo@heardfate.com"/>
    <user id="1060052237347897346" age="55" email="moreuser@heardfate.com"/>
</dataset>

数据准备

期望数据UserDriver_check.xml,期望数据必须映射所以字段,为空的用[NULL]表示

<?xml version="1.0" encoding="UTF-8"?>
<dataset>
    <user id="1" name="userOne" age="18" email="user-one@heardfate.com"/>
    <user id="2" name="userTwo" age="20" email="userTwo@heardfate.com"/>
    <user id="1060052237347897346" name="[NULL]" age="55" email="moreuser@heardfate.com"/>
</dataset>

数据准备

LOG4J2配置

配置log4j,打印日志,查看数据流转信息。
log4j.xml内容:

<?xml version="1.0" encoding="UTF-8"?>
<!-- status=debug 可以查看log4j的装配过程 -->
<configuration status="off" monitorInterval="1800">
	<properties>
		<property name="LOG_HOME">/Volumes/MacDisk/logs/springbootdemo</property>
		<property name="BACKUP_HOME">${LOG_HOME}/backup</property>
		<property name="SERVER_NAME">demo04</property>
	</properties>
	<appenders>
		<!-- 定义控制台输出 -->
		<Console name="Console" target="SYSTEM_OUT" follow="true">
			<PatternLayout pattern="%date{yyyy-MM-dd HH:mm:ss.SSS} %-7level [%thread][%class{36}:%line] - %msg%n" />
		</Console><!-- 程序员调试日志 -->
		<RollingRandomAccessFile name="DevLog"
			fileName="${LOG_HOME}/dev/${SERVER_NAME}.log" filePattern="${BACKUP_HOME}/${SERVER_NAME}.%d{yyyy-MM-dd}.log">
			<PatternLayout
				pattern="%date{yyyy-MM-dd HH:mm:ss.SSS} %-7level [%thread][%class{36}:%line] - %msg%n" />
			<Policies>
				<TimeBasedTriggeringPolicy interval="1" modulate="true" />
			</Policies>
		</RollingRandomAccessFile>
	</appenders>
	<Loggers>
		<!-- 3rdparty Loggers -->
		<Root level="INFO">
			<AppenderRef ref="Console" />
		</Root>
		<!--只有com.heardfate.springboot.demo输出DEBUG日志-->
		<Logger name="com.heardfate.springboot.demo" level="DEBUG">
			<AppenderRef ref="DevLog" />
		</Logger>
	</Loggers>
</configuration>

log4j配置

编写DBUnit基类

com/heardfate/springboot/demo/demo04/dbunit/BaseDBUnit.java下添加DBUnit基类

package com.heardfate.springboot.demo.demo04.dbunit;

import org.dbunit.Assertion;
import org.dbunit.database.DatabaseConfig;
import org.dbunit.database.DatabaseConnection;
import org.dbunit.database.IDatabaseConnection;
import org.dbunit.database.QueryDataSet;
import org.dbunit.dataset.*;
import org.dbunit.dataset.excel.XlsDataSet;
import org.dbunit.dataset.xml.FlatXmlDataSet;
import org.dbunit.dataset.xml.FlatXmlDataSetBuilder;
import org.dbunit.ext.mysql.MySqlDataTypeFactory;
import org.dbunit.operation.DatabaseOperation;
import org.junit.Assert;
import org.junit.Before;
import org.junit.runner.RunWith;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.jdbc.datasource.DataSourceUtils;
import org.springframework.test.annotation.Rollback;
import org.springframework.test.context.junit4.AbstractTransactionalJUnit4SpringContextTests;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.transaction.annotation.Transactional;

import javax.sql.DataSource;
import java.io.*;
import java.sql.SQLException;

/**
 * @since: 2018/11/7
 * @author: Mr.HeardFate
 */

@RunWith(SpringRunner.class)
@SpringBootTest
@Transactional("transactionManager")
@Rollback
public class BaseDBUnit extends AbstractTransactionalJUnit4SpringContextTests {
    protected static final Logger logger = LoggerFactory.getLogger(BaseDBUnit.class);
    @Autowired
    private DataSource dataSource;

    private IDatabaseConnection conn;

    private File tempFile;

    public static final String ROOT_URL = System.getProperty("user.dir") + "/src/test/resources/";

    @Before
    public void setup() throws Exception {
        logger.debug("Get DataBaseSourceConnection!");
        // get DataBaseSourceConnection
        conn = new DatabaseConnection(DataSourceUtils.getConnection(dataSource));

        logger.debug("Config database as MySql");
        // config database as MySql
        DatabaseConfig dbConfig = conn.getConfig();
        dbConfig.setProperty(DatabaseConfig.PROPERTY_DATATYPE_FACTORY, new MySqlDataTypeFactory());

    }

    /**
     * @return
     * @Title: getConnection
     */
    protected IDatabaseConnection getConnection() {
        return conn;
    }

    /**
     * @param name
     * @return
     * @throws DataSetException
     * @throws IOException
     * @Title: getXmlDataSet
     */
    protected IDataSet getXmlDataSet(String name) throws DataSetException, IOException {
        FlatXmlDataSetBuilder builder = new FlatXmlDataSetBuilder();
        builder.setColumnSensing(true);
        return builder.build(new FileInputStream(new File(ROOT_URL + name)));
    }

    /**
     * Get DB DataSet
     *
     * @return
     * @throws SQLException
     * @Title: getDBDataSet
     */
    protected IDataSet getDBDataSet() throws SQLException {
        return conn.createDataSet();
    }

    /**
     * Get Query DataSet
     *
     * @return
     * @Title: getQueryDataSet
     */
    protected QueryDataSet getQueryDataSet() {
        return new QueryDataSet(conn);
    }

    /**
     * Get Excel DataSet
     *
     * @param name
     * @return
     * @throws DataSetException
     * @throws IOException
     * @Title: getXlsDataSet
     */
    protected XlsDataSet getXlsDataSet(String name) throws DataSetException, IOException {
        InputStream is = new FileInputStream(new File(ROOT_URL + name));
        return new XlsDataSet(is);
    }

    /**
     * backup the whole DB
     *
     * @throws Exception
     * @Title: backupAll
     */
    protected void backupAll() throws Exception {
        // create DataSet from database.
        IDataSet ds = conn.createDataSet();

        // create temp file
        tempFile = File.createTempFile("temp", "xml");

        // write the content of database to temp file
        FlatXmlDataSet.write(ds, new FileWriter(tempFile), "UTF-8");
    }

    /**
     * back specified DB table
     *
     * @param tableName
     * @throws Exception
     * @Title: backupCustom
     */
    protected void backupCustom(String... tableName) throws Exception {
        // back up specific files
        QueryDataSet qds = new QueryDataSet(conn);
        for (String str : tableName) {
            qds.addTable(str);
        }
        tempFile = File.createTempFile("temp", "xml");
        FlatXmlDataSet.write(qds, new FileWriter(tempFile), "UTF-8");

    }

    /**
     * back specified DB table
     *
     * @param tableName
     * @throws Exception
     * @Title: backupCustomAud
     */
    protected void backupCustomAud(String... tableName) throws Exception {
        // back up specific files
        QueryDataSet qds = new QueryDataSet(conn);
        qds.addTable("revinfo");
        for (String str : tableName) {
            qds.addTable(str);
            qds.addTable(str + "_aud");
        }
        tempFile = File.createTempFile("temp", "xml");
        FlatXmlDataSet.write(qds, new FileWriter(tempFile), "UTF-8");

    }

    /**
     * rollback database
     *
     * @throws Exception
     * @Title: rollback
     */
    protected void rollback() throws Exception {
        logger.debug("rollback database Start!");
        logger.debug("get the temp file");
        // get the temp file
        FlatXmlDataSetBuilder builder = new FlatXmlDataSetBuilder();
        builder.setColumnSensing(true);
        IDataSet ds = builder.build(new FileInputStream(tempFile));

        logger.debug("recover database");
        // recover database
        DatabaseOperation.CLEAN_INSERT.execute(conn, ds);
        logger.info("rollback database success!");
    }

    /**
     * Clear data of table
     *
     * @param tableName
     * @throws Exception
     */
    protected void clearTable(String tableName) throws Exception {
        logger.debug("clear table");
        DefaultDataSet dataset = new DefaultDataSet();
        dataset.addTable(new DefaultTable(tableName));
        DatabaseOperation.DELETE_ALL.execute(conn, dataset);
    }

    /**
     * Clear data of table And verify Table is Empty
     *
     * @param tableName
     * @throws Exception
     */
    protected void clearAndVerifyTable(String tableName) {
        try {
            clearTable(tableName);
            verifyTableEmpty(tableName);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * verify Table is Empty
     *
     * @param tableName
     * @throws DataSetException
     * @throws SQLException
     */
    protected void verifyTableEmpty(String tableName) throws DataSetException, SQLException {
        logger.debug("verify table is empty");
        Assert.assertEquals(0, conn.createDataSet().getTable(tableName).getRowCount());
    }

    /**
     * verify Table is not Empty
     *
     * @param tableName
     * @throws DataSetException
     * @throws SQLException
     * @Title: verifyTableNotEmpty
     */
    protected void verifyTableNotEmpty(String tableName) throws DataSetException, SQLException {
        logger.debug("verify table is not empty");
        Assert.assertNotEquals(0, conn.createDataSet().getTable(tableName).getRowCount());
    }

    /**
     * @param dataSet
     * @return
     * @Title: createReplacementDataSet
     */
    protected ReplacementDataSet createReplacementDataSet(IDataSet dataSet) {
        ReplacementDataSet replacementDataSet = new ReplacementDataSet(dataSet);

        // Configure the replacement dataset to replace '[NULL]' strings with
        // null.
        replacementDataSet.addReplacementObject("[null]", null);

        return replacementDataSet;
    }

    protected void verifyXmlDataWithCheckXml(String tableName, String checkDriverXml) {
        try {
            ITable dbTable = getDBDataSet().getTable(tableName);
            logger.debug("dbTable {}",dbTable);
            // 预期结果
            IDataSet expectedDataSet = getXmlDataSet(checkDriverXml);
            ReplacementDataSet replacementDataSet = createReplacementDataSet(expectedDataSet);
            replacementDataSet.addReplacementObject("[NULL]", null);
            ITable expectedTable = replacementDataSet.getTable(tableName);
            logger.debug("expectedTable {}",expectedTable);
            Assertion.assertEquals(dbTable, expectedTable);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }
}

编写基类

编写测试类UserDaoTest

com/heardfate/springboot/demo/demo04/dao/UserDaoTest.java下添加测试类
只测试类数据是否符合期望,插入新数据,获取所有数据

package com.heardfate.springboot.demo.demo04.dao;

import com.baomidou.mybatisplus.core.toolkit.IdWorker;
import com.heardfate.springboot.demo.demo04.dbunit.BaseDBUnit;
import com.heardfate.springboot.demo.demo04.entity.User;
import org.dbunit.dataset.IDataSet;
import org.dbunit.operation.DatabaseOperation;
import org.hamcrest.Matchers;
import org.junit.Before;
import org.junit.Test;
import org.springframework.beans.factory.annotation.Autowired;

import java.math.BigInteger;
import java.util.List;

import static org.junit.Assert.*;

/**
 * @since: 2018/11/7
 * @author: Mr.HeardFate
 */
public class UserDaoTest extends BaseDBUnit {

    @Autowired
    private UserDao mapper;

    private final String tableName = "user";

    /**
     * 清空数据插入准备的数据
     *
     * @throws Exception
     */
    @Before
    public void setUp() throws Exception {
        try {
            IDataSet dataSet = getXmlDataSet("UserDriver.xml");
            DatabaseOperation.CLEAN_INSERT.execute(getConnection(), dataSet);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * 检测XML数据插入是否符合预期
     *
     * @throws Exception
     */
    @Test
    public void check_xml_data() {
        verifyXmlDataWithCheckXml(tableName, "UserDriver_check.xml");
    }

    @Test
    public void insert() {
        // 插入数据前先清空所有数据
        clearAndVerifyTable(tableName);

        User bean = new User();
        bean.setName("testUser");
        bean.setAge(20);
        bean.setEmail("testUser@heardfate.com");
        bean.setId(new BigInteger(IdWorker.getIdStr()));

        logger.debug("{}:{}", bean.getClass().getName(), bean);
        assertEquals("insert data error", 1, mapper.insert(bean));
        logger.debug("{} id:{}", bean.getClass().getName(), bean.getId());
        assertNotNull("Id is null", bean.getId());
    }

    @Test
    public void deleteById() {

    }

    @Test
    public void deleteByMap() {
    }

    @Test
    public void delete() {
    }

    @Test
    public void deleteBatchIds() {
    }

    @Test
    public void updateById() {
    }

    @Test
    public void update() {
    }

    @Test
    public void selectById() {
    }

    @Test
    public void selectBatchIds() {
    }

    @Test
    public void selectByMap() {
    }

    @Test
    public void selectOne() {
    }

    @Test
    public void selectCount() {
    }

    @Test
    public void selectList() {
        List<User> list = mapper.selectList(null);
        assertNotNull("Client list is null", list);
        logger.debug("get page row count={}", list.size());
        assertThat("select data size is zero", list.size(), Matchers.greaterThanOrEqualTo(1));
        list.forEach((user) -> logger.debug("user inof: {}", user));
    }

    @Test
    public void selectMaps() {
    }

    @Test
    public void selectObjs() {
    }

    @Test
    public void selectPage() {
    }

    @Test
    public void selectMapsPage() {
    }
}

编写测试类

运行测试方法

运行测试方法UserDaoTest查看是否通过测试
测试全通过!
运行测试