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相關的: