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()); } }
关键改动说明
容器生命周期优化:
- 使用静态
PostgreSQLContainer实例,无需try-with-resources,Testcontainers会在所有测试完成后自动停止并销毁容器。 - 指定具体Postgres镜像版本(如
postgres:15),避免最新版本的兼容性问题。
- 使用静态
数据源与DAO初始化:
- 在
@BeforeEach中初始化数据源和DAO,确保每个测试方法使用独立的新鲜实例。 - 调整Hikari连接池配置,测试环境设置最小连接池大小,减少资源消耗。
- 在
数据库初始化规范:
- 添加
initCourseTable方法,用IF NOT EXISTS保证表结构可重复创建,避免报错。 - 插入数据使用
executeUpdate,符合DML语句的执行规范。
- 添加
测试断言修正:
- 修正容器运行状态的断言逻辑,验证JDBC URL非空且有效。
进阶建议
- 集成数据库迁移工具:如果表结构复杂,建议使用Flyway或Liquibase,在
@BeforeAll中执行迁移脚本,替代手动建表语句。 - 使用Testcontainers JUnit 5扩展:添加
@Testcontainers注解配合@Container,自动管理容器生命周期,无需手动调用start()。 - 数据隔离策略:在
@AfterEach中执行TRUNCATE TABLE courses或删除测试数据,确保每个测试的独立性,避免数据污染。
内容的提问来源于stack exchange,提问作者Berder
相关产品推荐
相关产品推荐

