You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

非Spring环境下Testcontainers数据库测试实现求助

非Spring环境下用Testcontainers测试PostgreSQL数据库操作

问题描述

作业要求禁止使用Spring,目前正在学习Docker与Testcontainers。项目需要和数据库交互,已经通过代码创建了PostgreSQLContainer,但不知道怎么编写测试用例验证数据库的增、改、查操作。已实现三个数据库操作方法:添加事务、更新余额、打印审计数据,但找不到非Spring环境下的Testcontainers使用示例,特此求助。

已实现的方法代码

添加事务方法

@Override
public void addTransaction(TypeOfTransaction type, int amount) {
    String sql = """
            INSERT INTO entities."Transaction" (type, amount)
            VALUES (?, ?)
            """;
    try (Connection connection = ConnectionManager.open();
         PreparedStatement preparedStatement = connection.prepareStatement(sql)) {
        preparedStatement.setString(1, String.valueOf(type));
        preparedStatement.setInt(2, amount);
        preparedStatement.executeUpdate();
    } catch (SQLException e) {
        throw new RuntimeException(e);
    }
}

更新余额方法

@Override
public void minusBalance(String login, int amount) {
    String sql = """
            UPDATE entities."Client"
            SET balance = balance - ?
            WHERE login = ?
            """;
    try (Connection connection = ConnectionManager.open();
         PreparedStatement preparedStatement = connection.prepareStatement(sql)) {
        preparedStatement.setInt(1, amount);
        preparedStatement.setString(2, login);
        preparedStatement.executeUpdate();
    } catch (SQLException e) {
        throw new RuntimeException(e);
    }
}

查询打印审计数据方法

@Override
public void printAudit(String login) {
    String sql = """
            SELECT action
            FROM "Client_Audit"
            WHERE client_login = ?
            """;
    try (Connection connection = ConnectionManager.open();
         PreparedStatement preparedStatement = connection.prepareStatement(sql)) {
        preparedStatement.setString(1, login);
        ResultSet resultSet = preparedStatement.executeQuery();
        while (resultSet.next()) {
            System.out.println(resultSet.getString("action"));
        }
    } catch (SQLException e) {
        throw new RuntimeException(e);
    }
}

解决方案

1. 依赖配置(Maven)

确保测试依赖包含Testcontainers PostgreSQL模块和JUnit 5:

<dependencies>
    <!-- Testcontainers PostgreSQL -->
    <dependency>
        <groupId>org.testcontainers</groupId>
        <artifactId>postgresql</artifactId>
        <version>1.19.0</version>
        <scope>test</scope>
    </dependency>
    <!-- Testcontainers JUnit 5 集成 -->
    <dependency>
        <groupId>org.testcontainers</groupId>
        <artifactId>junit-jupiter</artifactId>
        <version>1.19.0</version>
        <scope>test</scope>
    </dependency>
    <!-- JUnit 5 -->
    <dependency>
        <groupId>org.junit.jupiter</groupId>
        <artifactId>junit-jupiter-api</artifactId>
        <version>5.10.0</version>
        <scope>test</scope>
    </dependency>
    <dependency>
        <groupId>org.junit.jupiter</groupId>
        <artifactId>junit-jupiter-engine</artifactId>
        <version>5.10.0</version>
        <scope>test</scope>
    </dependency>
</dependencies>

2. 调整ConnectionManager(支持测试配置)

如果你的ConnectionManager是硬编码连接参数,改成可动态配置的,方便测试时连接到容器数据库:

public class ConnectionManager {
    private static String url;
    private static String username;
    private static String password;

    // 供测试设置连接参数
    public static void setConnectionParams(String dbUrl, String dbUsername, String dbPassword) {
        url = dbUrl;
        username = dbUsername;
        password = dbPassword;
    }

    public static Connection open() throws SQLException {
        return DriverManager.getConnection(url, username, password);
    }
}

3. 编写测试类

使用JUnit 5 + Testcontainers管理容器,每个测试用例在干净的数据库环境中运行:

import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.testcontainers.containers.PostgreSQLContainer;
import org.testcontainers.junit.jupiter.Container;
import org.testcontainers.junit.jupiter.Testcontainers;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;

import static org.junit.jupiter.api.Assertions.*;

@Testcontainers
public class DatabaseOperationsTest {

    // 初始化PostgreSQL容器,指定镜像版本
    @Container
    private static final PostgreSQLContainer<?> postgresContainer = new PostgreSQLContainer<>("postgres:15-alpine")
            .withDatabaseName("test_db")
            .withUsername("test_user")
            .withPassword("test_pass");

    // 业务类实例
    private TransactionService transactionService;
    private ClientService clientService;
    private AuditService auditService;

    @BeforeAll
    static void initContainerConnection() {
        postgresContainer.start();
        // 配置ConnectionManager连接到测试容器
        ConnectionManager.setConnectionParams(
                postgresContainer.getJdbcUrl(),
                postgresContainer.getUsername(),
                postgresContainer.getPassword()
        );
    }

    @AfterAll
    static void stopContainer() {
        postgresContainer.stop();
    }

    @BeforeEach
    void setUpTestEnvironment() throws SQLException {
        // 初始化业务类
        transactionService = new TransactionServiceImpl();
        clientService = new ClientServiceImpl();
        auditService = new AuditServiceImpl();

        // 创建测试所需的表结构
        try (Connection conn = ConnectionManager.open()) {
            // 创建schemas和表
            executeSql(conn, """
                    CREATE SCHEMA IF NOT EXISTS entities;
                    CREATE TABLE IF NOT EXISTS entities."Transaction" (
                        id SERIAL PRIMARY KEY,
                        type VARCHAR(255) NOT NULL,
                        amount INT NOT NULL
                    );
                    CREATE TABLE IF NOT EXISTS entities."Client" (
                        id SERIAL PRIMARY KEY,
                        login VARCHAR(255) UNIQUE NOT NULL,
                        balance INT DEFAULT 0
                    );
                    CREATE TABLE IF NOT EXISTS "Client_Audit" (
                        id SERIAL PRIMARY KEY,
                        client_login VARCHAR(255) NOT NULL,
                        action VARCHAR(255) NOT NULL
                    );
                    """);

            // 插入测试用的客户端数据
            executeSql(conn, """
                    INSERT INTO entities."Client" (login, balance) VALUES ('test_login', 1000);
                    """);
        }
    }

    // 辅助方法:执行SQL语句
    private void executeSql(Connection conn, String sql) throws SQLException {
        try (PreparedStatement stmt = conn.prepareStatement(sql)) {
            stmt.executeUpdate();
        }
    }

    // 测试添加事务
    @Test
    void addTransaction_ShouldInsertData() throws SQLException {
        transactionService.addTransaction(TypeOfTransaction.DEPOSIT, 500);

        // 验证数据插入
        try (Connection conn = ConnectionManager.open()) {
            ResultSet rs = conn.prepareStatement("SELECT type, amount FROM entities.\"Transaction\"")
                    .executeQuery();
            assertTrue(rs.next());
            assertEquals(TypeOfTransaction.DEPOSIT.name(), rs.getString("type"));
            assertEquals(500, rs.getInt("amount"));
            assertFalse(rs.next());
        }
    }

    // 测试更新余额
    @Test
    void minusBalance_ShouldUpdateClientBalance() throws SQLException {
        clientService.minusBalance("test_login", 200);

        // 验证余额更新
        try (Connection conn = ConnectionManager.open()) {
            ResultSet rs = conn.prepareStatement("SELECT balance FROM entities.\"Client\" WHERE login = 'test_login'")
                    .executeQuery();
            assertTrue(rs.next());
            assertEquals(800, rs.getInt("balance"));
        }
    }

    // 测试打印审计数据
    @Test
    void printAudit_ShouldRetrieveAuditData() throws SQLException {
        // 先插入一条审计数据模拟业务操作
        executeSql(ConnectionManager.open(), """
                INSERT INTO "Client_Audit" (client_login, action) VALUES ('test_login', 'BALANCE_UPDATED');
                """);

        // 方式1:验证数据库中存在数据
        try (Connection conn = ConnectionManager.open()) {
            ResultSet rs = conn.prepareStatement("SELECT action FROM \"Client_Audit\" WHERE client_login = 'test_login'")
                    .executeQuery();
            assertTrue(rs.next());
            assertEquals("BALANCE_UPDATED", rs.getString("action"));
        }

        // 方式2:捕获控制台输出验证(可选)
        // ByteArrayOutputStream out = new ByteArrayOutputStream();
        // System.setOut(new PrintStream(out));
        // auditService.printAudit("test_login");
        // assertEquals("BALANCE_UPDATED\n", out.toString());
        // System.setOut(System.out);
    }
}

关键说明

  • @Testcontainers:启用Testcontainers与JUnit 5的集成,自动管理容器生命周期。
  • @Container:标记的容器实例会在测试类启动时启动,测试结束后停止,复用容器提升效率。
  • @BeforeEach:每次测试前初始化表结构和测试数据,保证测试用例之间的隔离性。
  • 测试用例中直接查询数据库验证操作结果,确保业务方法的正确性。

内容的提问来源于stack exchange,提问作者elSasen

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.08 11:30:26