分类
Spring Persistence Testing

Spring JPA使用内存数据库进行测试

1. 概述

本文将构建一个简单的Spirng应用,并在该应用中使用内存数据库进行单元测试。

往往在开发过程中我们习惯于使用与生产环境相同的数据库,比如MySQL。这就要求在进行开发时,必须在本地准备一个与生产环境相同的数据库环境。

在进行单元测试时,由于每个测试用例都是测试的某一小部分功能,这决定了大多数的单元测试可以不依赖于与生产环境相同的数据库。此时,为这部分单元测试提供快速、轻量化的内存数据库便有了必要。

下面,我们将共同学习如何在单元测试中使用h2内存型数据库来替代MySQL数据库。

2. Maven依赖

我们在Spring Boot(2.3.3.release)项目中的pom.xml中加入以下依赖:

<dependency>
	<groupId>org.springframework.boot</groupId>
	<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>

<dependency>
	<groupId>com.h2database</groupId>
	<artifactId>h2</artifactId>
	<scope>runtime</scope>
</dependency>
<dependency>
	<groupId>mysql</groupId>
	<artifactId>mysql-connector-java</artifactId>
	<scope>runtime</scope>
</dependency>
<dependency>
	<groupId>org.springframework.boot</groupId>
	<artifactId>spring-boot-starter-test</artifactId>
	<scope>test</scope>
	<exclusions>
		<exclusion>
			<groupId>org.junit.vintage</groupId>
			<artifactId>junit-vintage-engine</artifactId>
		</exclusion>
	</exclusions>
</dependency>

3. 数据模型及仓库

我们首先创建一个Student学生实体

@Entity
public class Student {
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private Long id;

    private String name;

    // 空构造函数及setter/getter请自行补充
}

然后创建该Student学生实体对应的数据仓库StudentRepository

public interface StudentRepository extends JpaRepository<Student, Long> {
}

4. 为测试指定H2数据库

为测试指定与运行不同的数据库,我们需要为测试指定一个特定的配置文件。然后设置该配置文件仅在测试环境下生效。

Spring Boot的配置文件位于src/main/resources文件夹中。如果要定义专门用于单元测试的配置文件,则应该将其放置到src/test/resources文件夹中。

Spring在运行单元测试时,首先在src/test/resources查找相应的配置文件。如果在文件夹中没有找到,则会使用src/main/resources文件夹中的文件;如果找到了,则会使文件下的文件而跳过src/main/resources文件夹的查找。

4.1 测试专用配置

如果我们所有的单元测试都想依赖于H2数据库(这虽不是一个好的选择,但有时候在快速切换数据环境时很有效),则可以直接在src/test/resources新建application.properties,并在该文件中将数据数连接信息设置为H2:

spring.datasource.url=jdbc:h2:mem:myDb;DB_CLOSE_DELAY=-1
spring.datasource.username=root
spring.datasource.password=

spring.jpa.show-sql=true

此时当运行单元测试时,Spring将默认加载此文件做为应用的配置文件,进行在单元测试中启用了h2数据库:

@SpringBootTest
class StudentRepositoryTest {
    @Autowired
    StudentRepository studentRepository;

    /**
     * 由于本机并没有安装mysql(即使安装了,也没有exampleDb数据库)
     * 所以如果本方法被成功执行,则说明当前单元测试连接的为H2数据库
     */
    @Test
    void findAll() {
        this.studentRepository.findAll();
    }
}

4.2 自定义测试配置文件

如果我们仅仅是需要在部分的单元测试中启用H2数据库而非全部测试,那么单独定义一个测试配置文件是个不错的选择。比如在src/test/resources建立student.properites,专门用于测试对student数据表的操作:

注意:src/main/resources建立也是相同的。本节在起初位置已经介绍了:Spring进行单元测试在查找某个配置位置时会优先查找src/test/resources文件夹,如果找不到则还会查找src/main/resources文件夹。

url=jdbc:h2:mem:myDb;DB_CLOSE_DELAY=-1
# 若使用注释的值设置url,则在StudentJpaConfigTest测试时将尝试连接mysql数据库,进而会发生异常。
# 这说明了此设置文件在单元测试时生效了
# url=jdbc:mysql://localhost:3306/exampleDb
username=root
password=

接下下让我们创建一个使用@Configuration注解的类,并将其设置为:搜索student.properites文件并自动装配至Environment变量。并使用@PropertySource来绑定配置文件:

@Configuration
@PropertySource("classpath:student.properties")
public class StudentJpaConfig {

    @Autowired
    Environment environment;

    @Bean
    DataSource dataSource() {
        DataSourceBuilder dataSourceBuilder = DataSourceBuilder.create();
        dataSourceBuilder.url(this.environment.getProperty("url"));
        dataSourceBuilder.username(this.environment.getProperty("username"));
        dataSourceBuilder.password(this.environment.getProperty("password"));
        return dataSourceBuilder.build();
    }
}

DataSource bean表示使用当前方法的返回值来替找Spring中的默认DataSource。此时若StudentJpaConfig生效,则应用的数据源将切换为dataSource()方法的返回值,即H2内存数据库。

5. 验证

最后,在我们想启用内存数据库的单元测试上引入该配置类即实现指定某个单元测试连接的数据库为H2数据库的目的:

@SpringBootTest(classes★ = {
        SpringJpaTestInMemoryDatabaseApplication.class★,
        StudentJpaConfig.class★
})
class StudentJpaConfigTest {
    @Autowired
    StudentRepository studentRepository;

    @Test
    void dataSource() {
        this.studentRepository.findAll();
    }
}

运行测试:

2020-09-02 11:02:23.047 INFO 799 --- [ task-1] org.hibernate.dialect.Dialect : HHH000400: Using dialect: org.hibernate.dialect.H2Dialect
Hibernate: drop sequence if exists hibernate_sequence
Hibernate: create sequence hibernate_sequence start with 1 increment by 1
Hibernate: create table student (id bigint not null, name varchar(255), primary key (id))
Hibernate: select student0_.id as id1_0_, student0_.name as name2_0_ from student student0_

日志输出了当前的方言为H2Dialect以表明当前单元测试使用了H2数据库;日志同时输出了创建数据表以及查询数据表的SQL语句表时数据操作正常。

6. 总结

在单元测试的很多时候,一个体积小、速度快的数据库完全能够满足测试需求(比如我们新建某个数据仓库后,往往需要测试一个findAll方法是否发生异常),所以应该为这些测试应用速度更快、体积更小的H2内存数据库。

但同时由于H2内存数据库在一些细节上仍然与生产环境下的数据库有所差距,所以涉及到一些细节时,仍然需要生产环境下的数据库。这就要求:部分单元测试用生产环境数据库,而另外部分测试则仅需要H2数据库即可。

本文对单元测试中如何启用H2内存数据库进行介绍。

分类
Spring Data Testing

为spring应用单独配置测试环境数据库

1. 概述

很多Spring应用都需要关联数据库。而每次跑测试都对接真实的数据库有时候会让我们很头疼,所以如何在测试的环境中摒弃生产环境下的数据库而取而代之地使用一种更快、更小、对环境依赖程度更小的数据库便成为了急待解决的问题。

本文中将介绍几种为测试环境配置单独的数据库的方案。

注意:其实这里用『数据库(database)』并不正确,正确的说法应该是数据源(data source)。鉴于习惯,本文中使用了数据库来替待了数据源

2. MAVEN依赖

在正式编码之间,我们首先创建一个基本的Spring Boot Data项目,pom.xml文件如下:

<dependency>
	<groupId>org.springframework.boot</groupId>
	<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>

<dependency>
	<groupId>com.h2database</groupId>
	<artifactId>h2</artifactId>
	<scope>runtime</scope>
</dependency>
<dependency>
	<groupId>mysql</groupId>
	<artifactId>mysql-connector-java</artifactId>
	<scope>runtime</scope>
</dependency>

如上我们添加了Data JPA依赖以及H2数据库、mysql数据库依赖。

3. 项目配置

在Spring Boot项目中,默认的配置文件为:src/main/resources/application.properties。我们打开此文件并将项目的数据库配置为mysql:

spring.jpa.hibernate.ddl-auto=update
spring.datasource.url=jdbc:mysql://${MYSQL_HOST:localhost}:3306/db_example
spring.datasource.username=root

此时若运行本应用,则需要有一个服务于3306端口的本机数据库支持,用户名为root,密码为空,且存在db_example数据库。如果不存在满足以上条件的数据库,系统启动时便会发生一个异常。

4. 测试配置

与运行环境下生效的src/main/resources/application.properties文件对应,在测试文件夹中还可以存在(不存在的话手动建立即可)一个src/test/resources/application.properties文件,该文件只 在测试时起作用。在运行单元测试时,Spring首先查找src/test/resources/application.properties文件,如果存在则使用该文件做配置文件,如果不存在再去main目录下查找application.properties文件。

比如声明在测试中使用h2数据库,则可以在src/test/resources/application.properties文件中添加如下配置项:

spring.datasource.url=jdbc:h2:mem:db_example

此时,在运行测试时Spring应用便自动启用h2数据源且同时创建db_example数据库。所以即使没有满足生产环境配置下的数据库,测试也同样可以启动成功。

5. 自定义测试配置文件

除了直接在src/test/resources/application.properties文件中进行测试配置以外,还可以在单元测试中指定自定义配置文件。

比如新建src/main/resources/h2datasource.properties文件:

jdbc.url=jdbc:h2:mem:db_example
jdbc.username=root
jdbc.password=

接下来便可以根据该文件来创建一个Bean返回数据库信息了:

@Configuration
@PropertySource("classpath:h2datasource.properties")
@Profile("default")
public class H2DataSourceConfig {

    @Autowired
    Environment environment;

    /**
     * 定义数据源
     *
     * @return
     */
    @Bean
    DataSource dataSource() {
        DataSourceBuilder dataSourceBuilder = DataSourceBuilder.create();
        dataSourceBuilder.url(this.environment.getProperty("jdbc.url"));
        dataSourceBuilder.username(this.environment.getProperty("jdbc.user"));
        dataSourceBuilder.password(this.environment.getProperty("jdbc.password"));
        return dataSourceBuilder.build();
    }
}

单元测试时,可以在@SpringBootTest中指定该配置文件来启用该配置文件:

@SpringBootTest(classes = {SpringTestingSeparateDataSourceApplication.class,
        H2DataSourceConfig.class})
public class H2DataSourceConfigTest {
    @Autowired
    StudentRepository studentRepository;

    @Test
    void contextLoads() {
        this.studentRepository.findAll();
    }
}

当然了,我们同样可以在src/test/resources/建立与src/main/resources/中的同名文件。然后就像上一小节一样,一个用于测试环境,另一个用于生产环境。这本节中在@SpringBootTest中指定测试文件并不冲突。

注意:自定义配置文件并不属于本文的重点,你可以点击使用内存数据库进行独立测试来了解更多的内容。

6. 使用Spring Profiles

还可以在单元测试中指定特定的Profile情景,以达到在某个单元测试中启用测试数据库的目的:

@SpringBootTest(classes = {
        SpringTestingSeparateDataSourceApplication.class,
        H2ProfileJPAConfig.class
})
@ActiveProfiles("test")
class H2ProfileJPAConfigTest {
    @Autowired
    StudentRepository studentRepository;

    @Test
    void dataSource() {
        this.studentRepository.findAll();
    }
}

@ActiveProfiles("test")表明:运行该单元测试时,强行使用test情景。

除了可以在项目配置文件(application.properties)中声明profile情景以外,还可以在使用@Profile注解

@Configuration
@Profile("test")
public class H2ProfileJPAConfig {

    /**
     * 定义数据源
     * @return
     */
    @Bean
    @Profile("test")
    public DataSource dataSource() {
        DataSourceBuilder dataSourceBuilder = DataSourceBuilder.create();
        dataSourceBuilder.url("jdbc:h2:mem:db_example");
        dataSourceBuilder.username("root");
        dataSourceBuilder.password("");
        return dataSourceBuilder.build();
    }
}

上述两段代码结合在一起便实现了:当运行H2ProfileJPAConfigTest时强行使用test情景,近而启用了test情景下的h2内存数据库进行单元测试。

7. 总结

在测试中使用内存数据库将减小项目的配置难度,可以更快的运行测试。内存数据库与项目的依赖环境相同,从而不必为了测试项目而单独建立一个数据库。当我们同时负责多个项目,而每个项目生产环境的数据库版本都不相同时,使用内存数据库跑测试将事半功倍。同时由于内存数据库又小又轻,相较于搭建生产环境下使用的数据库而言,它还可以节省一些计算机资源。