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

相关文章
相关标签/搜索