Warning: file_get_contents(/data/phpspider/zhask/data//catemap/9/java/323.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
Java 在测试数据库操作时,将数据库重置为已知状态的最佳方法是什么?_Java_Database_Testing_Junit - Fatal编程技术网

Java 在测试数据库操作时,将数据库重置为已知状态的最佳方法是什么?

Java 在测试数据库操作时,将数据库重置为已知状态的最佳方法是什么?,java,database,testing,junit,Java,Database,Testing,Junit,我正在使用JUnit为一些在测试数据库上操作的方法编写测试 每次@Test之后,我需要将数据库重置为原始状态。我想知道最好的方法是什么 实体管理器中是否有某种方法?或者我应该手动或使用SQL语句删除所有内容?删除并重新创建整个数据库是否更好?可以在测试之间重置数据库,甚至用预定义的测试数据填充数据库。最简单的方法就是在每次测试后回滚所有更改。这需要一个事务性RDBMS和一个定制的测试运行程序或类似的程序,将每个测试包装到它自己的事务中。Spring's正是这样做的。我过去使用的一种技术是从头开始

我正在使用JUnit为一些在测试数据库上操作的方法编写测试

每次
@Test
之后,我需要将数据库重置为原始状态。我想知道最好的方法是什么


实体管理器中是否有某种方法?或者我应该手动或使用SQL语句删除所有内容?删除并重新创建整个数据库是否更好?

可以在测试之间重置数据库,甚至用预定义的测试数据填充数据库。

最简单的方法就是在每次测试后回滚所有更改。这需要一个事务性RDBMS和一个定制的测试运行程序或类似的程序,将每个测试包装到它自己的事务中。Spring's正是这样做的。

我过去使用的一种技术是从头开始重新创建数据库,只需从标准的“测试数据库”复制数据库,然后在测试中使用它

此技术在以下情况下有效:

  • 您的模式变化不大(否则要保持一致会很痛苦)
  • 您正在使用类似hibernate的东西,它相当独立于数据库
  • 这有以下优点:

  • 它使用管理自己事务的代码。我的集成测试在junit下运行。例如,当我测试一个批处理过程时,我从junit调用batch.main(),并在测试前后测试内容。我不想更改测试代码中的事务处理
  • 它相当快。如果文件足够小,那么速度就不是问题
  • 它使在ci服务器上运行集成测试变得容易。将使用代码检入数据库文件。无需启动并运行真正的数据库
  • 还有以下缺点:

  • 测试数据库文件需要与真实数据库一起维护。如果您一直在添加列,这可能会很痛苦
  • 有代码来管理JDBCURL,因为每次测试它们都会更改
  • 我使用Oracle作为生产/集成数据库,使用hsqldb作为测试数据库。它工作得很好。hsqldb是单个文件,因此易于复制

    因此,在@Before中,使用hsqldb将文件复制到一个位置,如target/it/database/name\u of_test.script。这是在测试中发现的

    在@After中,您可以删除文件(或者干脆离开它,谁在乎呢)。对于hsqldb,您还需要关闭,以便删除该文件

    您还可以使用扩展自ExternalResource的@Rule,这是管理资源的更好方法


    另一个技巧是,如果使用maven或类似的工具,可以在target中创建数据库。我使用target/it。这样,当我执行和mvn清理时,数据库的副本将被删除。对于我的批处理,我实际上也将所有其他属性文件等复制到此目录中,因此我也不会看到任何文件出现在奇怪的地方。

    我回答这个问题更多是为了我自己的参考,但这里是。答案假设每个开发人员都有一个SQL Server数据库

    基本方法
  • 使用DBUnit存储已知状态的XML文件。您可以在设置数据库后提取此文件,也可以从头开始创建它。将此文件与调用DBUnit以使用它填充DB的脚本一起放入版本控制中

  • 在测试中,在
  • 之前使用
    @调用上述脚本

    加速1 一旦这种方法起作用,调整方法以加快速度。下面是一种SQL Server数据库的方法

    在DBUnit之前,完全清除数据库:

    EXEC sp_msforeachtable 'ALTER TABLE ? NOCHECK CONSTRAINT ALL';
    EXEC sp_MSforeachtable 'ALTER TABLE ? DISABLE TRIGGER ALL';
    EXEC sp_MSForEachTable 'SET QUOTED_IDENTIFIER ON SET ANSI_NULLS ON DELETE FROM ?';
    
    在DBUnit之后,恢复约束

    EXEC sp_MSforeachtable 'ALTER TABLE ? CHECK CONSTRAINT ALL';
    EXEC sp_MSforeachtable 'ALTER TABLE ? ENABLE TRIGGER ALL';
    
    加速2 使用SQL Server的还原功能。在我的测试中,它的运行时间是DBUnit的25%。如果(且仅当)这是测试持续时间中的一个主要因素,那么值得研究这种方法

    下面的类展示了使用SpringJDBC、JTDS和CDI注入的实现。这是为容器内测试而设计的,在容器内测试中,可能需要停止容器自身与数据库的连接

    import java.io.File;
    import java.sql.SQLException;
    
    import javax.inject.Inject;
    import javax.sql.DataSource;
    
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    import org.springframework.jdbc.core.JdbcTemplate;
    
    /**
     * Allows the DB to be reset quickly using SQL restore, at the price of
     * additional complexity. Recommended to vanilla DBUnit unless speed is
     * necessary.
     * 
     * @author aocathain
     * 
     */
    @SuppressWarnings({ "PMD.SignatureDeclareThrowsException" })
    public abstract class DbResetterSO {
    
        protected final Logger logger = LoggerFactory.getLogger(getClass());
    
        /**
         * Deliberately created in the target dir, so that on mvn clean, it is
         * deleted and will be recreated.
         */
        private final File backupFile = new File(
                "target\\test-classes\\db-backup.bak");
    
        @Inject
        private OtherDbConnections otherDbConnections;
    
        /**
         * Backs up the database, if a backup doesn't exist.
         * 
         * @param masterDataSource
         *            a datasource with sufficient rights to do RESTORE DATABASE. It
         *            must not be connected to the database being restored, so
         *            should have db master as its default db.
         */
        public void backup(final DataSource masterDataSource) throws Exception {
    
            final JdbcTemplate masterJdbcTemplate = new JdbcTemplate(
                    masterDataSource);
    
            if (backupFile.exists()) {
                logger.debug("File {} already exists, not backing up", backupFile);
            } else {
                otherDbConnections.start();
    
                setupDbWithDbUnit();
    
                otherDbConnections.stop();
                logger.debug("Backing up");
                masterJdbcTemplate.execute("BACKUP DATABASE [" + getDbName()
                        + "] TO DISK ='" + backupFile.getAbsolutePath() + "'");
                logger.debug("Finished backing up");
                otherDbConnections.start();
            }
    
        }
    
        /**
         * Restores the database
         * 
         * @param masterDataSource
         *            a datasource with sufficient rights to do RESTORE DATABASE. It
         *            must not be connected to the database being restored, so
         *            should have db master as its default db.
         */
        public void restore(final DataSource masterDataSource) throws SQLException {
            final JdbcTemplate masterJdbcTemplate = new JdbcTemplate(
                    masterDataSource);
    
            if (!backupFile.exists()) {
                throw new IllegalStateException(backupFile.getAbsolutePath()
                        + " must have been created already");
            }
            otherDbConnections.stop();
    
            logger.debug("Setting to single user");
    
            masterJdbcTemplate.execute("ALTER DATABASE [" + getDbName()
                    + "] SET SINGLE_USER WITH ROLLBACK IMMEDIATE;");
    
            logger.info("Restoring");
    
            masterJdbcTemplate.execute("RESTORE DATABASE [" + getDbName()
                    + "] FROM DISK ='" + backupFile.getAbsolutePath()
                    + "' WITH REPLACE");
    
            logger.debug("Setting to multi user");
    
            masterJdbcTemplate.execute("ALTER DATABASE [" + getDbName()
                    + "] SET MULTI_USER;");
    
            otherDbConnections.start();
        }
    
        /**
         * @return Name of the DB on the SQL server instance
         */
        protected abstract String getDbName();
    
        /**
         * Sets up the DB to the required known state. Can be slow, since it's only
         * run once, during the initial backup. Can use the DB connections from otherDbConnections.
         */
        protected abstract void setupDbWithDbUnit() throws Exception;
    }
    
    
    import java.sql.SQLException;
    
    /**
     * To SQL RESTORE the db, all other connections to that DB must be stopped. Implementations of this interface must
     * have control of all other connections.
     * 
     * @author aocathain
     * 
     */
    public interface OtherDbConnections
    {
    
        /**
         * Restarts all connections
         */
        void start() throws SQLException;
    
        /**
         * Stops all connections
         */
        void stop() throws SQLException;
    
    }
    
    
    
    import java.sql.Connection;
    import java.sql.SQLException;
    
    import javax.annotation.PostConstruct;
    import javax.annotation.PreDestroy;
    import javax.enterprise.inject.Produces;
    import javax.inject.Named;
    import javax.inject.Singleton;
    import javax.sql.DataSource;
    
    import net.sourceforge.jtds.jdbcx.JtdsDataSource;
    
    import org.springframework.jdbc.datasource.DelegatingDataSource;
    import org.springframework.jdbc.datasource.SingleConnectionDataSource;
    
    /**
     * Implements OtherDbConnections for the DbResetter and provides the DataSource during in-container tests.
     * 
     * @author aocathain
     * 
     */
    @Singleton
    @SuppressWarnings({ "PMD.AvoidUsingVolatile" })
    public abstract class ResettableDataSourceProviderSO implements OtherDbConnections
    {
    
        private volatile Connection connection;
        private volatile SingleConnectionDataSource scds;
        private final DelegatingDataSource dgds = new DelegatingDataSource();
    
        @Produces
        @Named("in-container-ds")
        public DataSource resettableDatasource() throws SQLException
        {
            return dgds;
        }
    
        @Override
        @PostConstruct
        public void start() throws SQLException
        {
            final JtdsDataSource ds = new JtdsDataSource();
    
            ds.setServerName("localhost");
            ds.setDatabaseName(dbName());
            connection = ds.getConnection(username(), password());
    
            scds = new SingleConnectionDataSource(connection, true);
            dgds.setTargetDataSource(scds);
    
        }
    
        protected abstract String password();
    
        protected abstract String username();
    
        protected abstract String dbName();
    
        @Override
        @PreDestroy
        public void stop() throws SQLException
        {
            if (null != connection)
            {
                scds.destroy();
                connection.close();
            }
    
        }
    }
    

    使用DBUnit XML数据集的一个大问题是,您将数据库模式记录在与代码库没有直接链接的XML文件中。重构成了一场噩梦。如果向表中添加一列,可能需要手动编辑多个文件中的几十个XML片段。哪个DBMS?PostgreSQL?神谕日食JPA,hsqldb.+1。但是,这确实有一个缺点,即您无法测试提交是否有效,因为您依赖于回滚。