【问题标题】:JUnit test starts before H2´s RUNSCRIPT finishesJUnit 测试在 H2 的 RUNSCRIPT 完成之前开始
【发布时间】:2015-10-11 09:14:12
【问题描述】:

我们有一个使用 Spring 和 MAVEN 的 Java 项目。在这个项目中,我们使用内存中的 H2 数据库对我们的 DAO/Repository 层执行多项测试。

经过几次测试,但并非总是如此,我们收到以下错误:

org.h2.jdbc.JdbcSQLException: Table "WEATHER" not found; SQL statement:

如果您单独执行 JUnit 测试,它永远不会失败。错误出现的时间没有规律。

我怀疑下面关于 URL 连接的 RUNSCRIPT 语句没有完成,当单元测试开始时,即执行是异步执行的。

这里是连接声明:

String jdbcUrl = "jdbc:h2:mem:WeatherAPI;MODE=MySQL;DB_CLOSE_ON_EXIT=TRUE;TRACE_LEVEL_SYSTEM_OUT=1;INIT=runscript from 'src/test/resources/sql/weatherapi.sql'"

这个想法是每次测试都会重置数据库。

这里是获取DataSource对象的sn-p代码:

private static java.sql.DataSource ds = null;

public static DataSource getDs() {
    if(this.ds==null) {
        try {
            this.ds = manualCreateDataSource();
        } catch (Exception e) {
            logger.error("Could not initialize Datasource", e);
            throw new RuntimeException("Could not initialize Datasource");
        }
    }

    return this.ds;
}

public static DataSource manualCreateDataSource() {

    String driverClass = "org.h2.jdbcx.JdbcDataSource";
    String jdbcUrl = "jdbc:h2:mem:WeatherAPI;MODE=MySQL;DB_CLOSE_ON_EXIT=TRUE;TRACE_LEVEL_SYSTEM_OUT=1;INIT=runscript from 'src/test/resources/sql/weatherapi.sql'";
    int maxPoolSize = 20;
    int minPoolSize = 5;
    int unreturnedConnectionTimeout = 10;
    int idleConnectionTestPeriod = 200;
    int maxIdleTime = 1000;
    int maxStatementsPerConnection = 5;

    ComboPooledDataSource ds = new ComboPooledDataSource();
    ds.setJdbcUrl(jdbcUrl);
    ds.setMaxPoolSize(maxPoolSize);
    ds.setMinPoolSize(minPoolSize);
    ds.setInitialPoolSize(minPoolSize);
    ds.setUnreturnedConnectionTimeout(unreturnedConnectionTimeout);
    ds.setIdleConnectionTestPeriod(idleConnectionTestPeriod);
    ds.setMaxIdleTime(maxIdleTime);
    ds.setMaxStatementsPerConnection(maxStatementsPerConnection);

    try {
        ds.setDriverClass(driverClass);
    } catch (PropertyVetoException e) {
        logger.error("error setting driver class", e);
    }

    return ds;
}

这里是 weatherapi.sql 脚本的 sn-p:

CREATE SCHEMA IF NOT EXISTS `WeatherAPI`;
USE `WeatherAPI`;

DROP TABLE IF EXISTS `Weather`;
CREATE TABLE IF NOT EXISTS `Weather` (
    id int(11) NOT NULL AUTO_INCREMENT,
    location char(3) NOT NULL,
    period varchar(8) NOT NULL,
    duration char DEFAULT NULL,
    payload TEXT,
    created timestamp NULL DEFAULT NULL,
    lastmodified timestamp NULL DEFAULT NULL,
    version int(11) NOT NULL,  PRIMARY KEY (id)
);

【问题讨论】:

  • 你可能想发mcve
  • 以后我会更加努力地在沙盒上重现这个问题:-)

标签: java database maven junit h2


【解决方案1】:

hzpz 的回答实际上帮助我了解正在发生的事情:竞争条件。我输入的问题没有指定我使用的是 maven(对此我深表歉意),我发现 maven surefire 插件正在分叉测试,因此实际上出现了竞争条件。我决定以这种方式关闭分叉并配置 maven-surefire 插件:

<plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-surefire-plugin</artifactId>
    <configuration>
        <forkCount>1</forkCount>
        <reuseForks>false</reuseForks>
    </configuration>
</plugin>

有几个关于分叉的问题,但没有一个将 H2 的 RUNSCRIPT 与竞争条件联系起来。

这里有关于surefire插件的更多细节:

Maven Surefire Plugin - Class loading and forking

【讨论】:

    【解决方案2】:

    我怀疑这是一个竞争条件。根据documentation,该脚本针对连接到数据库的每个客户端执行。由于您总是在重新创建之前删除 Weather 表,因此当测试 A 已经在运行并且第二个客户端 B 连接到数据库时,该表可能会被丢弃在 @987654325 的鼻子下方@。 B 可能是另一个并行运行的测试,也可能是同一测试中的第二个线程。

    如果是这种情况,您可以尝试在manualCreateDataSource() 方法中使用RunScript 工具,而不是JDBC 连接URL 中的INIT 参数:

    String jdbcUrl = "jdbc:h2:mem:WeatherAPI;MODE=MySQL;DB_CLOSE_ON_EXIT=TRUE;TRACE_LEVEL_SYSTEM_OUT=1;"
    RunScript.execute(jdbcUrl, sa, "", "src/test/resources/sql/weatherapi.sql", null, false);
    

    此外,您需要通过添加synchronized 或更好地静态初始化实例变量ds 来使getDs() 成为线程安全的:

    private static java.sql.DataSource ds = manualCreateDataSource();
    
    public static DataSource getDs() {
        return ds;
    }
    

    【讨论】:

      猜你喜欢
      • 2020-01-31
      • 2018-11-23
      • 1970-01-01
      • 2020-01-02
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多