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. 依赖引入
org.dbunit
dbunit
2.5.0
jar
test
com.github.springtestdbunit
spring-test-dbunit
1.2.0
test
com.h2database
h2
1.3.157
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数据源,配置文件如下:
classpath:config/mapper/discount/auto/*.xml
如果需要配置多数据源(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)
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 初始化数据库。
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:
- spring如何做处理依赖注入
默认处理,见上方2.1 - spring-boot如何加载h2数据源
Spring Boot实现了自动加载DataSource及相关配置 - 数据如何初始化
使用dml初始化数据
6. 总结
使用DBUnit+H2数据库,可以满足数据隔离特性,保证数据可重复执行,同时不会操作真实DB
7. 参考
DbUnit学习笔记
DbUnit实践:Spring Test Dbunit,H2数据库