本文介绍如何在junit测试中避免静态系统属性污染,通过重构 datasource 类和测试基类,确保每次测试前都能独立、可重复地初始化 h2 内存数据库,而非复用 postgres 连接。
本文介绍如何在junit测试中避免静态系统属性污染,通过重构 datasource 类和测试基类,确保每次测试前都能独立、可重复地初始化 h2 内存数据库,而非复用 postgres 连接。
问题根源在于 DataSource 类严重依赖静态字段(config 和 ds)及全局 System.setProperty(),导致 @BeforeEach 无法生效:一旦 ds 被初始化为 PostgreSQL 数据源,后续测试即使修改了系统属性,getDataSource() 仍直接返回已缓存的 HikariDataSource 实例,跳过重新配置逻辑。
要真正实现“每次测试前重置环境”,必须打破静态单例与系统属性的强耦合。以下是推荐的重构方案:
将 DataSource 改为非静态、可实例化、可配置的类,支持传入连接参数:
public class DataSource { private final HikariConfig config; private final HikariDataSource ds; // 构造时即完成配置与初始化(无延迟加载副作用) public DataSource(String jdbcUrl, String username, String password, String liquibaseFile) { this.config = new HikariConfig(); this.config.setJdbcUrl(jdbcUrl); this.config.setUsername(username); this.config.setPassword(password); this.ds = new HikariDataSource(config); initDatabase(liquibaseFile); } private void initDatabase(String liquibaseFile) { try (Connection conn = ds.getConnection()) { DatabaseConnection connection = new JdbcConnection(conn); Database database = DatabaseFactory.getInstance().findCorrectDatabaseImplementation(connection); Liquibase liquibase = new Liquibase( liquibaseFile, new ClassLoaderResourceAccessor(), database ); liquibase.update(new Contexts()); } catch (SQLException | LiquibaseException e) { throw new RuntimeException("Failed to initialize test database", e); } } public HikariDataSource getDataSource() { return ds; } public Connection getConnection() throws SQLException { return ds.getConnection(); }}
? 关键改进:
- 消除 static 字段,避免跨测试污染;
- 构造函数强制注入所有依赖参数,明确职责边界;
- initDatabase() 在构造时执行,确保每次新建 DataSource 实例都对应一次完整、隔离的 DB 初始化。
public class Base { protected DataSource dataSource; @BeforeEach void init() { // 每次测试前创建全新 DataSource(H2 内存库) String jdbcUrl = "jdbc:h2:mem:test_" + UUID.randomUUID() + ";DB_CLOSE_DELAY=-1"; dataSource = new DataSource( jdbcUrl, "sa", "", "liquibase_test.xml" ); } // 可选:提供便捷获取连接的方法 protected Connection getConnection() throws SQLException { return dataSource.getConnection(); }}
⚠️ 注意事项:
- DB_CLOSE_DELAY=-1 确保 H2 内存库在 JVM 退出前不自动关闭,适配多测试场景;
- 不再使用 System.setProperty(),彻底规避全局状态干扰;
- 所有 DAO 测试继承 Base 后,可直接使用 dataSource 或 getConnection(),行为完全隔离。
class UserDaoTest extends Base { private UserDao userDao; @BeforeEach void setUp() { this.userDao = new UserDao(dataSource.getDataSource()); // 使用当前测试专属数据源 } @Test void shouldInsertUserAndFindById() throws SQLException { try (Connection conn = getConnection()) { // 插入测试数据 userDao.insert(conn, new User("alice", "[email protected]")); // 查询验证 Optional<User> found = userDao.findById(conn, 1L); assertTrue(found.isPresent()); assertEquals("alice", found.get().getName()); } }}
该方案不仅解决了当前问题,还显著提升了代码可测试性、可维护性与可扩展性。