DBunit + H2实战

0. 概述

DBUnit是一种基于JUnit的数据库驱动测试框架。DBUnit 的设计理念就是在测试之前,给对象数据库植入我们需要的准备数据,最后,在测试完毕后,回溯到测试前的状态。它使数据库在测试过程之间处于一种已知状态,如果一个测试用例对数据库造成了破坏性影响,它可以帮助避免造成后面的测试失败或者给出错误结果。spring-test-dbunit提供了Spring Test Framework与DBUnit之间的集成。使用Spring Test DbUnit提供了注解驱动的数据库集成测试方式。
DBUnit的实施步骤:

1. 根据业务,做好测试用的准备数据和预想结果数据,通常准备成xml 格式文件。
2. 在setUp() 方法里边备份数据库中的关联表。
3. 在setUp() 方法里边读入准备数据。
4. 对测试类的对应测试方法进行实装: 执行对象方法,把数据库的实际执行结果和预想结果进行比较。
5. 在tearDown() 方法里边, 把数据库还原到测试前状态。

但是仅使用DBUnit会操作真实的Db,测试之前会清空数据库,会影响其他同学的自测、QA用例的跑通等等。本文介绍将内存数据库用于DbUnit,保证数据库的隔离性与可重复执行的同时,也不会影响到其他同事。

1. 依赖引入

<dependency>
    <groupId>org.dbunit</groupId>
    <artifactId>dbunit</artifactId>
    <version>2.5.0</version>
    <type>jar</type>
    <scope>test</scope>
</dependency>
<dependency>
    <groupId>com.github.springtestdbunit</groupId>
    <artifactId>spring-test-dbunit</artifactId>
    <version>1.2.0</version>
    <scope>test</scope>
</dependency>
<dependency>
    <groupId>com.h2database</groupId>
    <artifactId>h2</artifactId>
    <version>1.3.157</version>
</dependency>

2. 定义基类

定义一个基类,在基类上面使用@TestExecutionListeners注解注册Spring Test Dbunit监听器,用于指定在测试类执行之前,可以做的一些动作,这里处理两个listener,监视器中配置2个listener,其他所有测试类继承该基类

关于TestExecutionListener用法 其他Listener配置类

@RunWith(SpringRunner.class)
@SpringBootTest(classes = ApplicationLoader.class)
@TestExecutionListeners({DependencyInjectionTestExecutionListener.class,
        DbUnitTestExecutionListener.class})
public abstract class BaseTest {
  
}

2.1 DependencyInjectionTestExecutionListener

对测试中的类进行依赖注入
下面是spring-test 5.0.11.RELEASE默认使用的listener,所以不使用@TestExecutionListeners注解时,spring支持自动处理依赖注入(类TestContextManager中可以查看到容器启动时加载的Listener),当使用了该注解时,需要手动添加依赖注册listener

# Default TestExecutionListeners for the Spring TestContext Framework
#
org.springframework.test.context.TestExecutionListener = \
    org.springframework.test.context.web.ServletTestExecutionListener,\
    org.springframework.test.context.support.DirtiesContextBeforeModesTestExecutionListener,\
    org.springframework.test.context.support.DependencyInjectionTestExecutionListener,\
    org.springframework.test.context.support.DirtiesContextTestExecutionListener,\
    org.springframework.test.context.transaction.TransactionalTestExecutionListener,\
    org.springframework.test.context.jdbc.SqlScriptsTestExecutionListener

2.2 DbUnitTestExecutionListener

处理带有@DbUnitConfiguration注解的类,用于配置DataSource

3. 编写测试类

编写测试类,该类继承基类。并在改测试类加上@DbUnitConfiguration注解和@DatabaseSetup注解

@DbUnitConfiguration(databaseConnection = "h2UnifiedCouponGroupDataSource")
@DatabaseSetup(value = {"classpath:config/spring/database/CouponGroup.xml"}, connection = "h2UnifiedCouponGroupDataSource", type = DatabaseOperation.INSERT)
public class UnifiedCouponGroupDAOTest extends BaseTest {
    
    @Resource
    UnifiedCouponGroupDAO unifiedCouponGroupDAO;
    
    @Test
    public void getCoupon() throws Exception {
        CouponGroup coupon = unifiedCouponGroupDAO.getCoupon(328627430);
        Assert.assertEquals(328627430, (int) coupon.getCoupongroupid());
    }
}

3.1 DbUnitConfiguration用于配置数据源

如果不配置spring-test-dbunit数据源,则会在容器中查找id="dbUnitDatabaseConnection"或是"dataSource"的数据源。这里不使用zebra(美团开源)数据源,使用h2数据源,配置文件如下:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:jdbc="http://www.springframework.org/schema/jdbc"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/jdbc http://www.springframework.org/schema/jdbc/spring-jdbc.xsd"
       default-autowire="byName">

    <!--h2 DataSource-->
    <bean id="h2UnifiedCouponGroupDataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
        <property name="driverClassName" value="org.h2.Driver" />
        <property name="url" value="jdbc:h2:mem:PCTDiscount;MODE=MYSQL;DB_CLOSE_DELAY=-1" />
    </bean>

    <bean id="unifiedCouponGroupSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
        <property name="dataSource" ref="h2UnifiedCouponGroupDataSource"/>
        <property name="mapperLocations">
            <list>
                <value>classpath:config/mapper/discount/auto/*.xml</value>
            </list>
        </property>
        <!-- 配置mybatis配置文件的位置 -->
        <property name="configLocation" value="classpath:config/mybatis/mybatis-configuration.xml"/>
    </bean>

    <!-- 配置扫描Mapper接口的包路径 -->
    <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
        <property name="sqlSessionFactoryBeanName" value="unifiedCouponGroupSessionFactory" />
        <property name="basePackage" value="com.dianping.unified.coupon.dal.dao.mapper.discount"/>
    </bean>
</beans>

如果需要配置多数据源(spring-test-dbunit 1.2.0之后的版本才可以配置多数据源),则配置如下
@DbUnitConfiguration(databaseConnection={"h2DataSource","h2DataSource_2"})

3.2 DatabaseSetup

将特定xml文件中的数据同步到数据库,这个注解可以放在整个测试类上或者单个测试方法上,如果放在类上,则对所有方法都有效。如果不需要准备初始数据,可以不用此注解。

3.2.1 value:数据集文件

测试执行之前设置数据库初始状态的数据集(DataSet)文件,是标准的DbUnit XML文件(可以利用sequel pro的bundles实现生成xml文件https://github.com/alsbury/SequelProCopyPHPUnitDataset

<?xml version="1.0" encoding="UTF-8"?>
<dataset>
    <My_Table
            ID="30"
            Name="xxx"
    />
</dataset>

3.2.2 type:对数据库的操作类型

如果不设置默认是DatabaseOperation.CLEAN_INSERT

public enum DatabaseOperation {
  
  //将数据集中的内容更新到数据库中。它假设数据库中已经有对应的记录,否则将失败。
  UPDATE,
  
  //将数据集中的内容插入到数据库中。它假设数据库中没有对应的记录,否则将失败。
  INSERT,
  
  //将数据集中的内容刷新到数据库中。如果数据库有对应的记录,则更新,没有则插入。
  REFRESH,
  
  //删除数据库中与数据集对应的记录。
  DELETE,
  
  //删除表中所有的记录,如果没有对应的表,则不受影响。
  DELETE_ALL,
  
  //与DELETE_ALL类似,更轻量级,不能rollback。
  TRUNCATE_TABLE,
  
  //是一个组合操作,是DELETE_ALL和INSERT的组合
  CLEAN_INSERT;
}

3.2.3 connection:连接数据源

必须是@DbUnitConfiguration中配置的数据源,如果不指定,默认是@DbUnitConfiguration配置的第一个数据源。Spring-test-dbunit 1.2.0之后才支持。

3.2.4 其他注解

与@DatabaseSetup相对应的还有下面两个注解

  • @DatabaseTearDown:清理数据
    测试完成之后,用指定数据库去重置数据库

  • @ExpectedDatabase :数据验证
    通常的验证方式是更新后再去查询数据库再做比对。而DBUnit支持你将一个预期结果写到xml文件中,当测试用例更新完后会和数据库中的数据自动做比较,看看是否符合预期
    默认比对模式为DatabaseAssertionMode.DEFAULT

public enum DatabaseAssertionMode {

    //要验证所有的字段
    DEFAULT(new DefaultDatabaseAssertion()),

  //支持只验证部分字段,将忽略没有在期望数据集中出现,但是在实际数据集中出现的表和列名
    NON_STRICT(new NonStrictDatabaseAssertion()),

    //支持只验证部分字段,将忽略没有在期望数据集中出现,但是在实际数据集中出现的表和列名;且支持数据的行排序不同
    NON_STRICT_UNORDERED(new NonStrictUnorderedDatabaseAssertion());
}

4. H2数据库

使用spring schema jdbc:initialize-database 初始化数据库。

<!-- 初始化数据库 -->
<jdbc:initialize-database data-source="h2UnifiedCouponGroupDataSource" ignore-failures="DROPS">
    <jdbc:script location="classpath:config/spring/sql/mytable-ddl.sql" />
    <jdbc:script location="classpath:config/spring/sql/mytable*-dml.sql" /> 
</jdbc:initialize-database>

4.1 DDL

h2数据库对mysql的ddl语句的语法不是完全支持,目前已发现的一些不支持的情况:

  • 不支持表级别comment
  • 字段不支持 COLLATE utf8_bin
  • 字段不支持CHARACTER SET utf8mb4
  • 字段不支持CHARACTER SET utf8
  • 字段不支持ON UPDATE CURRENT_TIMESTAMP

4.2 DML

如果使用@DatabaseSetup,指定xml的数据源,则可以不执行dml.sql。(dml.sql也可以使用sequel pro的Copy as SQL INSERT生成)
如果同时使用xml和dml,注意数据的主键、唯一键冲突

5. 终极偷懒指南

骚操作:使用spring-boot,省略上面所有注解配置,直接配置h2数据源(所有db的mapper均在该xml配置)、使用sql初始化数据。
FAQ:

6. 总结

使用DBUnit+H2数据库,可以满足数据隔离特性,保证数据可重复执行,同时不会操作真实DB

7. 参考

DbUnit学习笔记
DbUnit实践:Spring Test Dbunit,H2数据库

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。