5.3 使用 Spring 和 Testcontainers 测试数据持久化
在前面的章节中,我们通过针对容器中的 PostgreSQL 数据库进行开发,为应用程序添加了数据持久化功能,这与生产环境中使用的技术相同。这是迈向 15 要素方法论推荐的环境一致性的重要一步。保持所有环境尽可能相似可以提高项目的质量。
数据源是环境差异的主要原因之一。在本地开发时使用内存数据库(如 H2 或 HSQL)是常见做法,但这会影响应用程序的可预测性和健壮性。即使所有关系型数据库都使用 SQL 语言,Spring Data JDBC 提供通用抽象,但每个供应商都有自己的方言和独特功能,因此在生产环境中使用与开发和测试相同的数据库至关重要。
Docker 使在本地使用实际数据库设置和开发应用程序变得更容易,正如您在 PostgreSQL 中体验的那样。类似地,Testcontainers(一个用于测试的 Java 库)使在集成测试中使用后端服务作为容器变得简单。
本节将展示如何使用 @DataJdbcTest 注解为数据持久化层编写切片测试,以及如何使用 @SpringBootTest 注解在集成测试中包含数据库。在两种情况下,都将依赖 Testcontainers 针对实际的 PostgreSQL 数据库运行自动化测试。
5.3.1 为 PostgreSQL 配置 Testcontainers
Testcontainers(https://testcontainers.org)是一个用于测试的 Java 库。它支持 JUnit,提供轻量级、一次性的容器,如数据库、消息代理和 Web 服务器。它非常适合实现与生产中使用的实际后端服务的集成测试。结果是更可靠和稳定的测试,从而带来更高质量的应用程序并有利于持续交付实践。
您可以使用 Testcontainers 配置轻量级 PostgreSQL 容器,并在涉及数据持久化层的自动化测试中使用它。
首先,需要在 Catalog Service 项目的 build.gradle 文件中添加 Testcontainers PostgreSQL 模块的依赖。添加新依赖后记得刷新或重新导入 Gradle 依赖。
代码清单 5.16 在 Catalog Service 中添加 Testcontainers 依赖
ext {
...
set('testcontainersVersion', "1.17.3")
}
dependencies {
...
testImplementation 'org.testcontainers:postgresql'
}
dependencyManagement {
imports {
...
mavenBom "org.testcontainers:testcontainers-bom:${testcontainersVersion}"
}
}
运行测试时,我们希望应用程序使用 Testcontainers 提供的 PostgreSQL 实例,而不是之前通过 spring.datasource.url 属性配置的实例。我们可以在 src/test/resources 下创建的新 application-integration.yml 文件中覆盖该值。启用 integration 配置文件时,此文件中定义的任何属性将优先于主配置文件中的属性。
在 src/test/resources 中创建新的 application-integration.yml 文件,并添加以下配置。
代码清单 5.17 使用 Testcontainers 提供的 PostgreSQL 数据源
spring:
datasource:
url: jdbc:tc:postgresql:14.4:///
5.3.2 使用 @DataJdbcTest 和 Testcontainers 测试数据持久化
Spring Boot 提供了一些测试切片,可以只加载应用程序的特定部分。@DataJdbcTest 注解会自动配置内存数据库、实体和仓库的扫描,以及 JDBC 相关的组件。默认情况下,它使用嵌入式数据库,但我们可以覆盖它以使用 Testcontainers。
创建一个新的 DataPersistenceTests 类,并添加以下代码:
代码清单 5.18 使用 @DataJdbcTest 和 Testcontainers 测试数据持久化
package com.polarbookshop.catalogservice.data;
import com.polarbookshop.catalogservice.domain.Book;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.data.jdbc.DataJdbcTest;
import org.springframework.test.context.ActiveProfiles;
import org.testcontainers.junit.jupiter.Container;
import org.testcontainers.junit.jupiter.Testcontainers;
import static org.assertj.core.api.Assertions.assertThat;
@DataJdbcTest
@Testcontainers
@ActiveProfiles("integration")
class DataPersistenceTests {
@Container
static PostgreSQLContainer<?> postgresql = new PostgreSQLContainer<>("postgres:14.4");
@Autowired
private BookRepository bookRepository;
@Test
void shouldCreateAndRetrieveBook() {
var book = Book.of("1234567891", "Northern Lights", "Lyra Silverstar", 9.90);
var savedBook = bookRepository.save(book);
var retrievedBook = bookRepository.findByIsbn(savedBook.isbn());
assertThat(retrievedBook).isPresent();
assertThat(retrievedBook.get().title()).isEqualTo("Northern Lights");
}
}
5.3.3 使用 @SpringBootTest 和 Testcontainers 进行集成测试
@SpringBootTest 注解加载完整的应用程序上下文,更适合测试应用程序的各个层如何协同工作。我们可以结合使用 @SpringBootTest 和 Testcontainers 来针对实际的 PostgreSQL 数据库运行集成测试。
代码清单 5.19 使用 @SpringBootTest 和 Testcontainers 进行集成测试
package com.polarbookshop.catalogservice;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.ActiveProfiles;
import org.testcontainers.junit.jupiter.Container;
import org.testcontainers.junit.jupiter.Testcontainers;
import static org.assertj.core.api.Assertions.assertThat;
@SpringBootTest
@Testcontainers
@ActiveProfiles("integration")
class CatalogServiceApplicationTests {
@Container
static PostgreSQLContainer<?> postgresql = new PostgreSQLContainer<>("postgres:14.4");
@Autowired
private BookRepository bookRepository;
@Test
void contextLoads() {
assertThat(bookRepository).isNotNull();
}
}
通过使用 Testcontainers,我们确保集成测试针对与生产环境相同的数据库技术运行,提高了测试的可靠性和应用程序的质量。