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

Docker+Testcontainers环境下JUnit 5测试代码修复请求

问题分析与代码重构

原代码的核心问题

  • 类静态container变量未被正确赋值:@BeforeAll中创建的是局部container实例,类级别的静态变量仍为null,后续调用会触发空指针异常。
  • 容器被提前关闭:使用try-with-resources包裹容器,启动后会立即自动关闭,导致测试执行时容器已停止运行。
  • 数据源与DAO初始化时机错误:CourseDaoImpl初始化时ds为null,未关联容器的数据库连接。
  • 缺少表结构初始化:直接插入数据前未创建courses表,会触发SQL语法错误。
  • 测试断言逻辑错误:assertEquals("", url)验证空字符串,与实际预期完全矛盾。

重构后的代码

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.utility.DockerImageName;
import com.zaxxer.hikari.HikariConfig;
import com.zaxxer.hikari.HikariDataSource;
import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.SQLException;
import java.sql.Statement;
import static org.junit.jupiter.api.Assertions.*;

class CourseDaoImplTest {

    // 静态容器实例,Testcontainers自动管理生命周期
    private static final PostgreSQLContainer<?> container = new PostgreSQLContainer<>(DockerImageName.parse("postgres:15"))
            .withDatabaseName("schoolconsoleapp")
            .withUsername("postgres")
            .withPassword("user");

    private DataSource ds;
    private CourseDaoImpl courseDao;

    @BeforeAll
    public static void setupContainer() {
        // 启动容器,无需手动关闭,Testcontainers会在测试结束后自动清理
        container.start();
    }

    @BeforeEach
    public void setup() throws SQLException {
        // 初始化数据源
        ds = createDataSource(container);
        // 初始化DAO实例
        courseDao = new CourseDaoImpl(ds);
        // 创建表结构(若未使用迁移工具,手动执行建表语句)
        initCourseTable();
        // 插入测试数据
        insertTestCourseData();
    }

    private DataSource createDataSource(PostgreSQLContainer<?> container) {
        HikariConfig hikariConfig = new HikariConfig();
        hikariConfig.setJdbcUrl(container.getJdbcUrl());
        hikariConfig.setUsername(container.getUsername());
        hikariConfig.setPassword(container.getPassword());
        hikariConfig.setMaximumPoolSize(1); // 测试环境无需大连接池

        return new HikariDataSource(hikariConfig);
    }

    private void initCourseTable() throws SQLException {
        String createTableSql = """
                CREATE TABLE IF NOT EXISTS courses (
                    id SERIAL PRIMARY KEY,
                    course_name VARCHAR(100) NOT NULL,
                    course_description TEXT
                )
                """;
        try (Connection conn = ds.getConnection();
             Statement stmt = conn.createStatement()) {
            stmt.execute(createTableSql);
        }
    }

    private void insertTestCourseData() throws SQLException {
        String insertSql = "INSERT INTO courses(course_name, course_description) VALUES ('German', 'Learn german language')";
        try (Connection conn = ds.getConnection();
             Statement stmt = conn.createStatement()) {
            stmt.executeUpdate(insertSql); // DML语句用executeUpdate而非executeQuery
        }
    }

    @Test
    public void shouldReturnTrue_whenContainerIsRunning() {
        assertTrue(container.isRunning());
        assertNotNull(container.getJdbcUrl());
        assertFalse(container.getJdbcUrl().isEmpty());
    }

    // 示例DAO测试方法:验证按名称查询课程
    @Test
    public void shouldRetrieveCourseByName() throws SQLException {
        var course = courseDao.findByName("German");
        assertNotNull(course);
        assertEquals("Learn german language", course.getDescription());
    }
}

关键改动说明

  1. 容器生命周期优化:

    • 使用静态PostgreSQLContainer实例,无需try-with-resources,Testcontainers会在所有测试完成后自动停止并销毁容器。
    • 指定具体Postgres镜像版本(如postgres:15),避免最新版本的兼容性问题。
  2. 数据源与DAO初始化:

    • 在@BeforeEach中初始化数据源和DAO,确保每个测试方法使用独立的新鲜实例。
    • 调整Hikari连接池配置,测试环境设置最小连接池大小,减少资源消耗。
  3. 数据库初始化规范:

    • 添加initCourseTable方法,用IF NOT EXISTS保证表结构可重复创建,避免报错。
    • 插入数据使用executeUpdate,符合DML语句的执行规范。
  4. 测试断言修正:

    • 修正容器运行状态的断言逻辑,验证JDBC URL非空且有效。

进阶建议

  • 集成数据库迁移工具:如果表结构复杂,建议使用Flyway或Liquibase,在@BeforeAll中执行迁移脚本,替代手动建表语句。
  • 使用Testcontainers JUnit 5扩展:添加@Testcontainers注解配合@Container,自动管理容器生命周期,无需手动调用start()。
  • 数据隔离策略:在@AfterEach中执行TRUNCATE TABLE courses或删除测试数据,确保每个测试的独立性,避免数据污染。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.19 10:17:52