问题描述
我有一个 Java Spring 启动项目,大量使用数据库 (Postgres) 作为它的存储库/数据。它是基本的 MVC 项目,控制器都是 REST 控制器。项目运行良好(服务已启动,能够通过 REST 客户端调用服务等)。
现在,我正在向其中添加单元测试。我对 Spring boot 很陌生,主要是单元测试部分。由于 CI/CD(构建管道),我无法使用持久/外部数据库进行测试。因此我需要使用内存数据库。
初始运行(主类)在项目启动时运行一堆数据库查询以建立缓存。所以我需要 postgres DB 进行测试(使用了很多 DB 函数)。
基本上,我需要使用 Testcontainers (postgresql)。我正在编写一个非常基本的测试来掌握它。
我存储了 schema.sql 和 data.sql(仅用于测试)。
src
|
main
test
|
resources
|
application-test.properties
schema.sql
data.sql
相关的pom.xml
<properties>
<java.version>11</java.version>
<testcontainers.version>1.15.1</testcontainers.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jdbc</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-cache</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.9.1</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</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>
<dependency>
<groupId>org.postgresql</groupId>
<artifactId>postgresql</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.11.2</version>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.11</version>
</dependency>
<dependency>
<groupId>commons-beanutils</groupId>
<artifactId>commons-beanutils</artifactId>
<version>1.9.4</version>
</dependency>
<dependency>
<groupId>org.json</groupId>
<artifactId>json</artifactId>
<version>20201115</version>
</dependency>
<dependency>
<groupId>org.testcontainers</groupId>
<artifactId>testcontainers</artifactId>
<version>${testcontainers.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.testcontainers</groupId>
<artifactId>localstack</artifactId>
<version>${testcontainers.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.testcontainers</groupId>
<artifactId>junit-jupiter</artifactId>
<version>${testcontainers.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.testcontainers</groupId>
<artifactId>postgresql</artifactId>
<version>${testcontainers.version}</version>
<scope>test</scope>
</dependency>
</dependencies>
我的测试班:
@Testcontainers
@sql(scripts = {"file:src/test/resources/schema.sql","file:src/test/resources/data.sql"})
class ApplicationTests {
@Container
static PostgresqlContainer<?> postgresqlContainer = new PostgresqlContainer<>("postgres:12")
.withUsername("testcontainers")
.withPassword("testcontainers")
.withDatabaseName("tescontainers");
@Test
void testPostgresqlModule() throws sqlException {
try (Connection connection = DriverManager
.getConnection(postgresqlContainer.getJdbcUrl(),"testcontainers","testcontainers");
PreparedStatement preparedStatement = connection.prepareStatement("SELECT * FROM table_from_schema")) {
try (ResultSet resultSet = preparedStatement.executeQuery()) {
while (resultSet.next()) {
System.out.println(resultSet.getString("column1"));
}
}
}
}
}
我只是想测试数据库。
但是,当我运行测试时,它没有说
org.postgresql.util.PsqlException: ERROR: relation "table_from_schema" does not exist
我尝试调试它,即在我的测试 (testPostgresqlModule) 中停止。我可以用 Postgres 看到 docker 组件。
$ docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
fc3ff1e04ceb postgres:12 "docker-entrypoint.s…" 16 seconds ago Up 15 seconds
但是当我登录它并运行 psql 时,我看到创建了数据库(tescontainers),但是它没有任何架构(表/函数)。
tescontainers=# \dt
Did not find any relations.
tescontainers=#
基本上,我的文件没有运行。
类级别的@sql 注释是否与我的情况一样不适用于 Testcontainers 初始化?
这里需要什么才能运行我的两个初始脚本?
我尝试使用 .withInitScript,它可以运行。但是,我有很多数据要初始化并且文件太大(并且会增长),因此我将 DDL(架构)和插入(数据)分开。现在,我的问题是如何使用“withInitScript”运行多个初始化文件(schema.sql、data.sql)?所以我尝试了@sql 注释,但它似乎不起作用。
---更新/编辑----
为了清楚说明上下文,我在下面寻找。如果有人可以指导吗?
- 所有配置文件 (dev/ist/uat/prod) 都应使用各自的持久性数据库(来自其应用程序。env.properties)。
- 仅用于测试,我需要内存数据库,但不能使用 H2(和类似的),因为我有很多与数据库相关的测试并且需要 Postgres(函数等)。因此,尝试使用测试容器。
- 当应用程序启动时,它会从相应的 DB(基于 env)中获取一些数据来准备初始缓存,其他方法将在为任何其余调用提供服务时使用它。因此,对于 Test(仅)我需要一个包含所有架构/数据(我可以通过 sql 文件提供)的新内存数据库,以便在测试时启动应该使用该测试数据库并且相应的测试将基于该初始数据进行。
- 所以我需要一种方法来在测试运行时启动测试数据库(内存/测试容器),并传递多个 sql 文件以初始化测试数据库(在任何测试运行之前)。知道什么是最好的方法吗?
解决方法
我猜你错过了以下内容
<dependency>
<groupId>org.postgresql</groupId>
<artifactId>postgresql</artifactId>
<scope>test</scope>
</dependency>
测试依赖。 (或者完全删除 <scope/>
。)
Table not found 错误通常表示架构和模型与可用驱动程序不匹配。
此外,您的测试类似乎与 spring-boot 无关。你如何运行它?您可以从自动配置中受益,如下所述:https://www.baeldung.com/spring-boot-testcontainers-integration-test (但我猜你不喜欢那样;))
,错误告诉你不存在这样的表
org.postgresql.util.PSQLException: ERROR: relation "table_from_schema" does not exist
与数据库的连接似乎已建立,但您尝试的查询无效。
我快速浏览了一下,从我所看到的情况来看,标准 postgres 架构中不存在表 table_from_schema
。
不过 select * from pg_tables;
可能有效。尝试访问不同的表,看看这是否能暂时为您解决问题,以便您确认问题是从不存在的表名中选择的,而不是更模糊的名称。
如果可以,您可以在 docker 容器启动并使用数据库客户端连接后放置一个断点。连接后,您可以尝试手动检查数据库,在将 SQL 命令复制粘贴到代码库之前对其进行测试。
如果您希望 SQL 脚本在您的 docker 容器中运行,您需要使用容器的卷映射将 test/resources
目录挂载到容器的 /docker-entrypoint-initdb.d
目录。然后将运行任何 *.sh
和 *.sql
脚本。您可能需要重组您的 sql 脚本,以便您有一个根脚本来调用较低目录中的脚本。如果两者之间有任何订购要求,则尤其如此。
参见https://hub.docker.com/_/postgres“初始化脚本”
使用 testcontainers 安装到您的容器中非常简单:
String pathToFile = "<project-root-dir>/src/test/resources";
new GenericContainer(...)
.withFileSystemBind(pathToFile,"/docker-entrypoint-initdb.d",BindMode.READ_ONLY)