Spring、Spring Boot和TestNG測試指南 - 測試關係型數據庫

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.javadom

public class Foo {

  private String name;

  public String getName() {
    return name;
  }

  public void setName(String name) {
    this.name = name;
  }
}

FooRepositoryImpl.javamaven

@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;
  }

}

例子1:不使用Spring Testing提供的工具

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)
);

Spring_1_IT.java

@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)
    );
  }

}

在這段測試代碼裏能夠看到,咱們分別測試了FooRepositorysavedelete方法,而且利用JdbcTemplate來驗證數據庫中的結果。

例子2:使用Spring Testing提供的工具

在這個例子裏,咱們會使用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須要它。

Spring_2_IT.java

@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")來驗證數據庫結果,這個方法是AbstractTransactionalTestNGSpringContextTestsJdbcTestUtils的代理。

並且要注意的是,每一個測試方法在執行完畢後,會自動rollback,因此在testDelete的第一行裏,咱們assertEquals(countRowsInTable("FOO"), 0),這一點和例子1裏是不一樣的。

更多關於Spring Testing Framework與Transaction相關的信息,能夠見Spring官方文檔 Transaction management

例子3:使用Spring Boot

Boot_1_IT.java

@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機制,不須要本身構建DataSourceJdbcTemplatePlatformTransactionManager的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有不少好處:

  1. 每一個sql文件名都規定了版本號

  2. flyway按照版本號順序執行

  3. 在開發期間,只須要將sql文件放到db/migration目錄下就能夠了,不須要寫相似EmbeddedDatabaseBuilder.addScript()這樣的代碼

  4. 基於以上三點,就可以將數據庫初始化SQL語句也歸入到集成測試中來,保證代碼配套的SQL語句的正確性

  5. 能夠幫助你清空數據庫,這在你使用非內存數據庫的時候很是有用,由於無論測試前仍是測試後,你都須要一個乾淨的數據庫

參考文檔

本章節涉及到的Spring Testing Framework JDBC、SQL相關的工具:

和flyway相關的:

相關文章
相關標籤/搜索