Github地址 html
Spring Test Framework提供了对JDBC的支持,可以让咱们很方便对关系型数据库作集成测试。java
同时Spring Boot提供了和Flyway的集成支持,可以方便的管理开发过程当中产生的SQL文件,配合Spring已经提供的工具可以更方便地在测试以前初始化数据库以及测试以后清空数据库。git
本章节为了方便起见,本章节使用了H2做为测试数据库。github
注意:在真实的开发环境中,集成测试用数据库应该和最终的生产数据库保持一致,这是由于不一样数据库的对于SQL不是彻底相互兼容的,若是不注意这一点,颇有可能出现集成测试经过,可是上了生产环境却报错的问题。spring
由于是集成测试,因此咱们使用了maven-failsafe-plugin
来跑,它和maven-surefire-plugin
的差异在于,maven-failsafe-plugin
只会搜索*IT.java
来跑测试,而maven-surefire-plugin
只会搜索*Test.java
来跑测试。sql
若是想要在maven打包的时候跳过集成测试,只须要mvn clean install -DskipITs
。数据库
先介绍一下被测试的类。springboot
Foo.java:dom
public class Foo { private String name; public String getName() { return name; } public void setName(String name) { this.name = name; } }
FooRepositoryImpl.java:maven
@Repository public class FooRepositoryImpl implements FooRepository { private JdbcTemplate jdbcTemplate; @Override public void save(Foo foo) { jdbcTemplate.update("INSERT INTO FOO(name) VALUES (?)", foo.getName()); } @Override public void delete(String name) { jdbcTemplate.update("DELETE FROM FOO WHERE NAME = ?", name); } @Autowired public void setJdbcTemplate(JdbcTemplate jdbcTemplate) { this.jdbcTemplate = jdbcTemplate; } }
Spring_1_IT_Configuration.java:
@Configuration @ComponentScan(basePackageClasses = FooRepository.class) public class Spring_1_IT_Configuration { @Bean(destroyMethod = "shutdown") public DataSource dataSource() { return new EmbeddedDatabaseBuilder() .generateUniqueName(true) .setType(EmbeddedDatabaseType.H2) .setScriptEncoding("UTF-8") .ignoreFailedDrops(true) .addScript("classpath:me/chanjar/domain/foo-ddl.sql") .build(); } @Bean public JdbcTemplate jdbcTemplate() { return new JdbcTemplate(dataSource()); } }
在Spring_1_IT_Configuration
中,咱们定义了一个H2的DataSource Bean,而且构建了JdbcTemplate Bean。
注意看addScript("classpath:me/chanjar/domain/foo-ddl.sql")
这句代码,咱们让EmbeddedDatabase
执行foo-ddl.sql脚原本建表:
CREATE TABLE FOO ( name VARCHAR2(100) );
@ContextConfiguration(classes = Spring_1_IT_Configuration.class) public class Spring_1_IT extends AbstractTestNGSpringContextTests { @Autowired private FooRepository fooRepository; @Autowired private JdbcTemplate jdbcTemplate; @Test public void testSave() { Foo foo = new Foo(); foo.setName("Bob"); fooRepository.save(foo); assertEquals( jdbcTemplate.queryForObject("SELECT count(*) FROM FOO", Integer.class), Integer.valueOf(1) ); } @Test(dependsOnMethods = "testSave") public void testDelete() { assertEquals( jdbcTemplate.queryForObject("SELECT count(*) FROM FOO", Integer.class), Integer.valueOf(1) ); Foo foo = new Foo(); foo.setName("Bob"); fooRepository.save(foo); fooRepository.delete(foo.getName()); assertEquals( jdbcTemplate.queryForObject("SELECT count(*) FROM FOO", Integer.class), Integer.valueOf(0) ); } }
在这段测试代码里能够看到,咱们分别测试了FooRepository
的save
和delete
方法,而且利用JdbcTemplate
来验证数据库中的结果。
在这个例子里,咱们会使用JdbcTestUtils来辅助测试。
Spring_2_IT_Configuration.java:
@Configuration @ComponentScan(basePackageClasses = FooRepository.class) public class Spring_2_IT_Configuration { @Bean public DataSource dataSource() { EmbeddedDatabase db = new EmbeddedDatabaseBuilder() .generateUniqueName(true) .setType(EmbeddedDatabaseType.H2) .setScriptEncoding("UTF-8") .ignoreFailedDrops(true) .addScript("classpath:me/chanjar/domain/foo-ddl.sql") .build(); return db; } @Bean public JdbcTemplate jdbcTemplate() { return new JdbcTemplate(dataSource()); } @Bean public PlatformTransactionManager transactionManager() { return new DataSourceTransactionManager(dataSource()); } }
这里和例子1的区别在于,咱们提供了一个PlatformTransactionManager
Bean,这是由于在下面的测试代码里的AbstractTransactionalTestNGSpringContextTests
须要它。
@ContextConfiguration(classes = Spring_2_IT_Configuration.class) public class Spring_2_IT extends AbstractTransactionalTestNGSpringContextTests { @Autowired private FooRepository fooRepository; @Test public void testSave() { Foo foo = new Foo(); foo.setName("Bob"); fooRepository.save(foo); assertEquals(countRowsInTable("FOO"), 1); countRowsInTableWhere("FOO", "name = 'Bob'"); } @Test(dependsOnMethods = "testSave") public void testDelete() { assertEquals(countRowsInTable("FOO"), 0); Foo foo = new Foo(); foo.setName("Bob"); fooRepository.save(foo); fooRepository.delete(foo.getName()); assertEquals(countRowsInTable("FOO"), 0); } }
在这里咱们使用countRowsInTable("FOO")
来验证数据库结果,这个方法是AbstractTransactionalTestNGSpringContextTests
对JdbcTestUtils
的代理。
并且要注意的是,每一个测试方法在执行完毕后,会自动rollback,因此在testDelete
的第一行里,咱们assertEquals(countRowsInTable("FOO"), 0)
,这一点和例子1里是不一样的。
更多关于Spring Testing Framework与Transaction相关的信息,能够见Spring官方文档 Transaction management。
@SpringBootTest @SpringBootApplication(scanBasePackageClasses = FooRepository.class) public class Boot_1_IT extends AbstractTransactionalTestNGSpringContextTests { @Autowired private FooRepository fooRepository; @Test public void testSave() { Foo foo = new Foo(); foo.setName("Bob"); fooRepository.save(foo); assertEquals(countRowsInTable("FOO"), 1); countRowsInTableWhere("FOO", "name = 'Bob'"); } @Test(dependsOnMethods = "testSave") public void testDelete() { assertEquals(countRowsInTable("FOO"), 0); Foo foo = new Foo(); foo.setName("Bob"); fooRepository.save(foo); fooRepository.delete(foo.getName()); assertEquals(countRowsInTable("FOO"), 0); } @AfterTest public void cleanDb() { flyway.clean(); } }
由于使用了Spring Boot来作集成测试,得益于其AutoConfiguration机制,不须要本身构建DataSource
、JdbcTemplate
和PlatformTransactionManager
的Bean。
而且由于咱们已经将flyway-core
添加到了maven依赖中,Spring Boot会利用flyway来帮助咱们初始化数据库,咱们须要作的仅仅是将sql文件放到classpath的db/migration
目录下:
V1.0.0__foo-ddl.sql
:
CREATE TABLE FOO ( name VARCHAR2(100) );
并且在测试最后,咱们利用flyway清空了数据库:
@AfterTest public void cleanDb() { flyway.clean(); }
使用flyway有不少好处:
每一个sql文件名都规定了版本号
flyway按照版本号顺序执行
在开发期间,只须要将sql文件放到db/migration目录下就能够了,不须要写相似EmbeddedDatabaseBuilder.addScript()
这样的代码
基于以上三点,就可以将数据库初始化SQL语句也归入到集成测试中来,保证代码配套的SQL语句的正确性
能够帮助你清空数据库,这在你使用非内存数据库的时候很是有用,由于无论测试前仍是测试后,你都须要一个干净的数据库
本章节涉及到的Spring Testing Framework JDBC、SQL相关的工具:
和flyway相关的: