之前记录了使用flyway管理数据库的初始化,flyway也可以进行数据初始化,不过是直接使用sql语句,dbunit可以使用xml文件对数据库里面的数据进行管理,而且对于数据的管理更为灵活,下面就简单介绍一下如何使用spring-test-dbunit。

添加gradle依赖

testCompile group: 'org.dbunit', name: 'dbunit', version: '2.6.0'
testCompile group: 'com.github.springtestdbunit', name: 'spring-test-dbunit', version: '1.3.0'

在类中使用

首先在测试类上面加注解@TestExecutionListeners,把常用的listener都添加进去
在想要使用dbunit做数据初始化和恢复的方法上面加注解@DatabaseSetup,@DatabaseTearDown
方法开始前将使用DatabaseSetup中指定的文件对数据库中的数据进行初始化,方法结束之后将使用DatabaseTearDown指定的文件恢复数据库的数据。其中,type指定数据库的操作类型,我这里随便指定了一下。

@WebAppConfiguration
@SpringBootTest
@ContextConfiguration(classes = { Application.class })
@AutoConfigureMockMvc
@TestExecutionListeners({
        DependencyInjectionTestExecutionListener.class,
        DirtiesContextTestExecutionListener.class,
        TransactionalTestExecutionListener.class,
        DbUnitTestExecutionListener.class
})
public class SpringBootTestBaseClass {
    @Test
    @DisplayName("test for spring-test-dbunit")
    @DatabaseSetup(type = DatabaseOperation.INSERT, value = "classpath:dbunit/test.xml")
    @DatabaseTearDown(type = DatabaseOperation.DELETE, value = "classpath:dbunit/test.xml")
    public void springTestDbunit() {
       //todo sth.
    }
}

数据库的操作类型如下:

public enum DatabaseOperation {

	/**
	 * Updates the contents of existing database tables from the dataset.
	 */
	UPDATE,

	/**
	 * Inserts new database tables and contents from the dataset.
	 */
	INSERT,

	/**
	 * Refresh the contents of existing database tables. Rows from the dataset will insert or replace existing data. Any
	 * database rows that are not in the dataset remain unaffected.
	 */
	REFRESH,

	/**
	 * Deletes database table rows that matches rows from the dataset.
	 */
	DELETE,

	/**
	 * Deletes all rows from a database table when the table is specified in the dataset. Tables in the database but not
	 * in the dataset remain unaffected.
	 * @see #TRUNCATE_TABLE
	 */
	DELETE_ALL,

	/**
	 * Deletes all rows from a database table when the table is specified in the dataset. Tables in the database but not
	 * in the dataset are unaffected. Identical to {@link #DELETE_ALL} expect this operation cannot be rolled back and
	 * is supported by less database vendors.
	 * @see #DELETE_ALL
	 */
	TRUNCATE_TABLE,

	/**
	 * Deletes all rows from a database table when the tables is specified in the dataset and subsequently insert new
	 * contents. Equivalent to calling {@link #DELETE_ALL} followed by {@link #INSERT}.
	 */
	CLEAN_INSERT;

}

spring-test-dbunit提供了使用注解来对数据进行操作的方法,但是,如果我们在测试开始之前想要对整个数据库进行备份,测试之后再恢复原先数据的话,我并没有找到可以用的注解,为了实现这个需求,可以直接使用dbunit进行实现。

@WebAppConfiguration
@SpringBootTest
@ContextConfiguration(classes = { Application.class })
@AutoConfigureMockMvc
@TestExecutionListeners({
        DependencyInjectionTestExecutionListener.class,
        DirtiesContextTestExecutionListener.class,
        TransactionalTestExecutionListener.class,
        DbUnitTestExecutionListener.class
})
public class SpringBootTestBaseClass {
    @Resource
    protected DataSource dataSource;

    protected IDatabaseConnection connection;

    String backupFile = "dbunit/backup.xml";

    @BeforeEach
    public void init() throws Exception {
        DatabaseDataSourceConnectionFactoryBean factoryBean = dbUnitDatabaseConnection();
        connection = factoryBean.getObject();
    }
    /*
    这个方法主要是对dbunit进行配置,其中setDatatypeFactory的设置表示我们的测试数据库是h2数据库,
    setEscapePattern的设置给每个字段的前后各提供了一个`,主要是为了防止表里有特殊字段
    (比如字段名为order)导致的sql语句错误,setAllowEmptyFields的设置表示允许空的字段。
    */
    private DatabaseDataSourceConnectionFactoryBean dbUnitDatabaseConnection() {
        DatabaseConfigBean bean = new DatabaseConfigBean();
        bean.setDatatypeFactory(new H2DataTypeFactory());
        bean.setEscapePattern("`");
        bean.setAllowEmptyFields(true);
        DatabaseDataSourceConnectionFactoryBean dbConnectionFactory =
                new DatabaseDataSourceConnectionFactoryBean(dataSource);
        dbConnectionFactory.setDatabaseConfig(bean);
        return dbConnectionFactory;
    }

    /*
    备份指定的表格
    */
    public void backup(List<String> tables) throws Exception {
        QueryDataSet dataSet = new QueryDataSet(connection);
        for (String t : tables) {
            dataSet.addTable(t);
        }
        FlatXmlDataSet.write(dataSet, getBackupFile());
    }
    /*
    备份所有的表格,其中filter表示备份的时候忽略掉名为FLYWAY_SCHEMA_HISTORY的表格
    (这个是之前使用flyway自动创建的表格)
    */
    public void backup() throws Exception {
        ITableFilter filter = new ExcludeTableFilter(new String[] { "FLYWAY_SCHEMA_HISTORY" });
        IDataSet dataSet = new FilteredDataSet(filter, connection.createDataSet());
        FlatXmlDataSet.write(dataSet, getBackupFile());
    }

    /*
    恢复数据库信息
    CLEAN_INSERT表示删除旧的并插入新的数据
    */
    public void recover() throws Exception {
        IDataSet ds = getiDataSet(backupFile);
        DatabaseOperation.CLEAN_INSERT.execute(connection, ds);
    }

    private IDataSet getiDataSet(String fileName) throws Exception {
        FlatXmlDataSetBuilder builder = new FlatXmlDataSetBuilder();
        builder.setColumnSensing(true);
        return builder.build(Resources.getResource(fileName));
    }

    /*
    我的backup的文件放在resources下面的dbunit文件夹下面,这个方法是为了拿到backup文件,
    使用之前要先创建它,其实backup文件可以直接使用相对的文件名称backup.xml,不放在resources下面
    */
    private FileOutputStream getBackupFile() throws FileNotFoundException, URISyntaxException {
        String path = Resources.getResource(backupFile).toURI().getPath();
        return new FileOutputStream(path);
    }

}

下面是xml文件的格式,service里面定义的是表service的数据,user表是空的,backup整个数据库的时候,就显示<user/>

<?xml version='1.0' encoding='UTF-8'?>
<dataset>
  <service id="1" name="Test" description="test"/>
  <service id="2" name="Preference" description="preference"/>
  <user/>
</dataset>