Quarkus多个单元测试共享同一个PostgresSQLContainer

问题背景

小码在给项目功能添加单元测试之后,集中运行所有单元测试,为了不污染数据库数据,就在docker中初始化pg数据库,并且使用@QuarkusTestResource(value = PostgresTestResource.class, restrictToAnnotatedClass = true)这个注解进行数据隔离。但是这个注解每次只能隔离一个测试类,也就是说当同时运行多个单元测试类的时候就要在docker中初始化多个pg数据库,大大降低了速度。 下图为7个测试类的执行时间:

执行时间为2sec638ms。

改进方案

要使多个单元测试类共享同一个 PostgreSQLContainer,可以使用@QuarkusTestResource配置单例的PostgreSQLContainer实例。

第一步,将PostgresSQLContainer的生命周期管理改为单例,修改注解中PostgresTestResource类。

Java 复制代码
package net.ximatai.muyun.test.testcontainers;

import io.quarkus.test.common.QuarkusTestResourceLifecycleManager;
import org.testcontainers.containers.PostgreSQLContainer;

import java.util.Map;

public class PostgresTestResource implements QuarkusTestResourceLifecycleManager {

    private static final String POSTGRES_IMAGE = "postgres:17-alpine";

    // 使用静态变量确保所有测试类共享一个实例
    private static final PostgreSQLContainer<?> POSTGRES = new PostgreSQLContainer<>(POSTGRES_IMAGE);

    @Override
    public Map<String, String> start() {
        if (!POSTGRES.isRunning()) {
            POSTGRES.start();
        }
        return Map.of(
            "quarkus.datasource.jdbc.url", POSTGRES.getJdbcUrl(),
            "quarkus.datasource.username", POSTGRES.getUsername(),
            "quarkus.datasource.password", POSTGRES.getPassword()
        );
    }

    @Override
    public void stop() {
        // 不停止容器,让其他测试类可以继续使用
    }
}

第二步,删除注解中restrictToAnnotatedClass = true,让所有测试类共享这个 PostgresTestResource.

Java 复制代码
@QuarkusTest
@QuarkusTestResource(PostgresTestResource.class)
public class TestCreateDailyReport {
    // 测试代码保持不变
}

第三步,如果需要清理数据库,可以在测试之间进行数据清理

为了保证测试之间不会因为数据库残留数据而产生干扰,你可以在测试方法中或 @BeforeEach 方法中添加数据清理逻辑:

import org.junit.jupiter.api.BeforeEach;

@BeforeEach public void cleanDatabase() { // 使用 noticeController 或者直接通过 JDBC 执行清理操作 // 示例:清空数据库表 TestCreateDailyReport.deleteAll(); // 假设有这样的方法 }

第四步,确保容器的复用跨越多个测试类

Testcontainers 支持通过 reuse 属性在本地共享容器(需要在 ~/.testcontainers.properties 中配置,ios不需要)。如果你希望跨多个测试会话共享容器,可以启用 reuse:

ini 复制代码
// ~/.testcontainers.properties
testcontainers.reuse.enable=true

并在容器初始化时设置 withReuse(true):

Java 复制代码
private static final PostgreSQLContainer<?> POSTGRES = 
    new PostgreSQLContainer<>(POSTGRES_IMAGE).withReuse(true);

测试结果

经过以上改进,所有单元测试类将共享一个 PostgreSQL 容器,避免重复初始化,显著提高测试运行速度。同时,通过数据清理逻辑,确保测试之间的隔离性。7个单元测试的总运行时间为1sec175ms。

执行效率提升了125%!

补充

在 Quarkus 中,@QuarkusTestResource 注解用于指定一个测试资源类(如数据库或其他外部依赖)来为单元测试提供必要的支持。

@QuarkusTestResource 的作用 这个注解的主要功能是:

  1. 在运行测试之前,启动并初始化指定的测试资源。
  2. 在测试运行完成后,清理并关闭这些资源。

参数解释

value 参数

value 指定了一个实现了 QuarkusTestResourceLifecycleManager 接口的类。

例如,这里是 PostgresTestResource.class,它可能会负责启动一个 PostgreSQL 数据库实例,并提供测试环境所需的连接信息。

restrictToAnnotatedClass 参数

restrictToAnnotatedClass 设置为 true 时:

  • 测试资源 仅对当前注解了 @QuarkusTestResource 的测试类 可见。
  • 换句话说,如果你有多个测试类,但其中只有一个类(例如 TestClassA)注解了 @QuarkusTestResource(value = PostgresTestResource.class, restrictToAnnotatedClass = true),那么这个测试资源 只会在 TestClassA 中被加载和使用

restrictToAnnotatedClass = false(默认值)时:

  • 测试资源将会对所有使用 @QuarkusTest 注解的测试类可用,并在整个测试生命周期内加载。

使用场景

  • restrictToAnnotatedClass = true:

    • 当测试资源不需要被其他测试类共享,或者可能与其他测试类产生冲突时使用。
    • 有助于减少资源浪费,因为只会在需要的测试类中加载。
  • restrictToAnnotatedClass = false:

    • 当多个测试类需要共享同一个资源时使用。例如,共享一个数据库实例以测试多个不同的功能。

示例代码

less 复制代码
@QuarkusTest
@QuarkusTestResource(value = PostgresTestResource.class, restrictToAnnotatedClass = true)
public class TestClassA {
    // 测试逻辑
}

@QuarkusTest
public class TestClassB {
    // 不会使用 PostgresTestResource
}

在上面的例子中:

  • PostgresTestResource 仅会在 TestClassA 中被加载和使用。
  • TestClassB 不会受到影响。
相关推荐
lang201509281 分钟前
Spring Boot 入门:5分钟搭建Hello World
java·spring boot·后端
间彧1 小时前
Windows Server,如何使用WSFC+nginx实现集群故障转移
后端
间彧1 小时前
Nginx + Keepalived 实现高可用集群(Linux下)
后端
间彧1 小时前
在Kubernetes中如何部署高可用的Nginx Ingress Controller?
后端
间彧1 小时前
Ribbon负载均衡器和Nginx负载均衡器有什么区别
后端
间彧2 小时前
Nacos详解与项目实战
后端
间彧2 小时前
nginx、网关Gateway、Nacos、多个服务实例之间的数据链路详解
后端
间彧2 小时前
Nacos与Eureka在性能上有哪些具体差异?
后端
间彧2 小时前
详解Nacos健康状态监测机制
后端
间彧2 小时前
如何利用Nacos实现配置的灰度发布?
后端