使用 testcontainers postgresql 进行 Spring 启动测试

Posted

技术标签:

【中文标题】使用 testcontainers postgresql 进行 Spring 启动测试【英文标题】:Spring boot test using testcontainers postgresql 【发布时间】:2021-08-26 22:12:24 【问题描述】:

我有一个 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=# \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(函数等)。所以,试试 Testcontainers。 当应用程序启动时,它会从各自的 DB(基于 env)中获取一些数据以准备初始缓存,并且其他方法将在为任何休息调用提供服务时使用它。因此,对于 Test (仅)我需要一个包含所有模式/数据(我可以通过 SQL 文件提供)的新内存数据库,以便在测试时启动应该使用该测试数据库并且相应的测试将基于该初始数据进行。 所以我需要一种方法来在测试运行时调出测试数据库(内存中/测试容器),并传递多个 SQL 文件以初始化测试数据库(在任何测试运行之前)。知道什么是最好的方法吗?

【问题讨论】:

【参考方案1】:

错误是告诉你不存在这样的表

org.postgresql.util.PSQLException: ERROR: relation "table_from_schema" does not exist

与数据库的连接似乎已建立,但您尝试的查询不起作用。

我快速浏览了一下,发现表 table_from_schema 不存在于标准 postgres 架构中。

但是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)

见https://www.testcontainers.org/features/files/

【讨论】:

谢谢。如果我使用 withInitScript(Testcontainers 初始化),则代码可以正常工作(相同的 SQL,只是合并在一个文件中)。但是当我将同一个文件拆分为模式和数据并使用 SQL 注释(没有 withInitScript)时,会创建数据库(测试容器),但模式和数据没有运行。当我检查没有创建架构时,我在 docker 中进行了调试。基本上,我正在尝试将 SQL 注释与 Testcontainers 一起使用,在运行任何测试之前用少量 SQL 文件初始化我的数据库(由 testcontainers 创建)。 你可以像github.com/hyperledger/blockchain-explorer/blob/…一样初始化你的postgres数据库【参考方案2】:

我猜你错过了以下内容

<dependency>
    <groupId>org.postgresql</groupId>
    <artifactId>postgresql</artifactId>
    <scope>test</scope>
</dependency>

测试依赖。 (或者完全删除&lt;scope/&gt;。)

Table not found 错误通常表示架构和模型与可用驱动程序不匹配。

而且你的测试类似乎是独立于 spring-boot 的。你如何运行它?您可以从自动配置中受益,如下所述:https://www.baeldung.com/spring-boot-testcontainers-integration-test (但我猜你不喜欢那样;))

【讨论】:

谢谢,我有这个依赖。如果我将相同的 SQL 与 withInitScript(没有 SQL 注释)一起使用,它就会运行。但是,我需要将架构和数据分开,因为这两个文件都很大并且还在增长(函数/表和数据)。所以我尝试使用 SQL 注释(因为 withInitScript 不接受多个文件)。这就是这里没有运行的内容,即为 SQL 注释提供的文件在创建容器和 DB 时不会运行。因为这只是了解测试容器使用的基础,否则我将使用 spring-boot 测试,并且链接肯定会帮助我。谢谢。 基本上,我正在寻找追随者。 1. 所有配置文件(dev/ist/uat/prod)都应该使用它们各自的持久性数据库(来自它们的 application..properties)。 2.仅用于测试,我需要内存数据库,但不能使用 H2(和类似的),因为我有很多与数据库相关的测试并且需要 Postgres(函数等)。因此,尝试测试容器。 3. 当应用程序启动时,它从 DB 中获取一些数据以准备初始缓存,并且其他方法在服务任何休息调用时使用它。因此,对于 Test (only) 我需要一个包含所有 schema/data 的新内存数据库,所以启动(测试)使用它。

以上是关于使用 testcontainers postgresql 进行 Spring 启动测试的主要内容,如果未能解决你的问题,请参考以下文章

将测试容器与另一个数据库驱动程序一起使用

Postgres docker-compose 使用测试容器不起作用

如何使用 Testcontainers 发送信号?

使用 testcontainers 测试 kafka 和 spark

如何强制 Testcontainers 使用特定的 docker 镜像?

SpringBoot 集成测试 Sybase 和 Testcontainers