在H2的RUNSCRIPT完成之前,JUnit测试开始

时间:2015-07-21 18:42:59

标签: java database maven junit h2

我们有一个使用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对象的代码片段:

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脚本的片段:

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)
);

2 个答案:

答案 0 :(得分:3)

我怀疑这是一场竞赛。根据{{​​3}},为连接到数据库的每个客户端执行脚本。由于在重新创建表之前总是丢弃Weather表,因此当测试A已经运行且第二个客户端B连接到数据库时,可能会发生这样的情况。在A的鼻子下面。 B可能是另一个并行运行的测试,也可能是同一测试中的第二个线程。

如果是这种情况,您可以尝试使用RunScript方法中的manualCreateDataSource()工具,而不是JDBC连接网址中的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);

此外,您需要通过向getDs()添加synchronized来保证ds线程安全,或者更好地通过静态初始化实例变量private static java.sql.DataSource ds = manualCreateDataSource(); public static DataSource getDs() { return ds; }

{{1}}

答案 1 :(得分: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