如何在单个方法中实现类似 @BeforeEach 的系统属性重置逻辑

千辰大大_8174

千辰大大_8174

2026-07-10

274人浏览

原创

本文介绍如何在junit测试中避免静态系统属性污染,通过重构 datasource 类和测试基类,确保每次测试前都能独立、可重复地初始化 h2 内存数据库,而非复用 postgres 连接。

本文介绍如何在junit测试中避免静态系统属性污染,通过重构 datasource 类和测试基类,确保每次测试前都能独立、可重复地初始化 h2 内存数据库,而非复用 postgres 连接。

问题根源在于 DataSource 类严重依赖静态字段(config 和 ds)及全局 System.setProperty(),导致 @BeforeEach 无法生效:一旦 ds 被初始化为 PostgreSQL 数据源,后续测试即使修改了系统属性,getDataSource() 仍直接返回已缓存的 HikariDataSource 实例,跳过重新配置逻辑。

要真正实现“每次测试前重置环境”,必须打破静态单例与系统属性的强耦合。以下是推荐的重构方案:

✅ 1. 解耦 DataSource 初始化逻辑(移除静态单例)

将 DataSource 改为非静态、可实例化、可配置的类,支持传入连接参数:

Phidata
Phidata

Phidata是一款用于构建多模态 Agent 与工作流的开源 AI 框架。

下载
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 初始化。

✅ 2. 测试基类改为实例化 + @BeforeEach 驱动

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(),行为完全隔离。

✅ 3. DAO 测试示例(验证隔离性)

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", "alice@example.com"));

            // 查询验证
            Optional<user> found = userDao.findById(conn, 1L);
            assertTrue(found.isPresent());
            assertEquals("alice", found.get().getName());
        }
    }
}</user>

? 总结

  • ❌ 不要依赖 System.setProperty() + 静态单例:它违背测试隔离原则,@BeforeEach 对静态字段无效;
  • ✅ 用构造注入替代全局配置:让 DataSource 成为可重复创建、可预测的测试友好的组件;
  • ✅ 每个测试拥有专属数据库实例:通过 UUID 生成唯一 H2 数据库名,配合 DB_CLOSE_DELAY,既保证隔离又避免资源泄漏;
  • ✅ 生产与测试使用相同数据库类型(如 H2 兼容模式)更可靠:若必须用 PostgreSQL 做集成测试,建议单独运行,而非混入单元测试流程。

该方案不仅解决了当前问题,还显著提升了代码可测试性、可维护性与可扩展性。

PHP速学视频免费教程(入门到精通)
PHP速学视频免费教程(入门到精通)

PHP怎么学习?PHP怎么入门?PHP在哪学?PHP怎么学才快?不用担心,这里为大家提供了PHP速学教程(入门到精通),有需要的小伙伴保存下载就能学习啦!

下载

相关标签:

本站声明:本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn

相关专题

更多
spring框架介绍
spring框架介绍

本专题整合了spring框架相关内容,想了解更多详细内容,请阅读专题下面的文章。

2025.08.06

2251

22

Java Spring Security 与认证授权
Java Spring Security 与认证授权

本专题系统讲解 Java Spring Security 框架在认证与授权中的应用,涵盖用户身份验证、权限控制、JWT与OAuth2实现、跨站请求伪造(CSRF)防护、会话管理与安全漏洞防范。通过实际项目案例,帮助学习者掌握如何 使用 Spring Security 实现高安全性认证与授权机制,提升 Web 应用的安全性与用户数据保护。

2026.01.26

437

25

软件测试常用工具
软件测试常用工具

软件测试常用工具有Selenium、JUnit、Appium、JMeter、LoadRunner、Postman、TestNG、LoadUI、SoapUI、Cucumber和Robot Framework等等。测试人员可以根据具体的测试需求和技术栈选择适合的工具,提高测试效率和准确性 。

2023.10.13

4190

8

java测试工具有哪些
java测试工具有哪些

java测试工具有JUnit、TestNG、Mockito、Selenium、Apache JMeter和Cucumber。php还给大家带来了java有关的教程,欢迎大家前来学习阅读,希望对大家能有所帮助。

2023.10.23

2581

10

Java 单元测试
Java 单元测试

本专题聚焦 Java 在软件测试与持续集成流程中的实战应用,系统讲解 JUnit 单元测试框架、Mock 数据、集成测试、代码覆盖率分析、Maven 测试配置、CI/CD 流水线搭建(Jenkins、GitHub Actions)等关键内容。通过实战案例(如企业级项目自动化测试、持续交付流程搭建),帮助学习者掌握 Java 项目质量保障与自动化交付的完整体系。

2025.10.24

1006

26

LLVM自定义Pass怎么写
LLVM自定义Pass怎么写

本专题聚焦LLVM自定义Pass开发,整理Pass类结构、run()方法、PreservedAnalyses、CMake构建、插件注册、-load-pass-plugin加载和测试用例编写流程。

2026.09.30

0

10

LLVM RISC-V参数配置教程
LLVM RISC-V参数配置教程

本专题介绍LLVM对RISC-V基础ISA和扩展的支持方式,涵盖RV32、RV64、标准扩展、实验性扩展、厂商扩展、-menable-experimental-extensions和版本差异。

2026.09.30

0

14

LLVM IR中间表示入门指南
LLVM IR中间表示入门指南

本专题整理LLVM IR的核心概念,包括中间表示作用、模块结构、函数、基本块、SSA形式、类型系统和常见语法,帮助新手理解LLVM编译流程中的关键层。

2026.09.30

0

12

PDF转图片方法
PDF转图片方法

需要把 PDF 页面用于上传、预览、分享或图片归档时,PDF 转图片方法专题整理 JPG/PNG 格式选择、逐页导出、清晰度设置、批量下载和结果检查等流程,帮助用户稳定完成 PDF 图片化处理。

2026.09.30

0

26

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

精品课程

更多
热门推荐
/
最新课程
phpStudy极速入门视频教程
phpStudy极速入门视频教程

共6课时 | 54.6万人学习

独孤九贱(4)_PHP视频教程
独孤九贱(4)_PHP视频教程

共89课时 | 133.4万人学习