Я отвечаю на это больше для моей собственной справки, но здесь идет. Ответ предполагает наличие базы данных SQL Server для разработчика.
Базовый подход
Используйте DBUnit для хранения XML-файла с известным состоянием. Вы можете извлечь этот файл после того, как вы настроили БД, или вы можете создать его с нуля. Поместите этот файл в свой контроль версий вместе со скриптами, которые вызывают DBUnit для заполнения им БД.
В своих тестах вызывайте вышеупомянутые сценарии, используя @Before
.
Ускорение 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. В моих тестах это работает в 25% времени, которое занимает DBUnit. Если (и только если) это является основным фактором продолжительности вашего теста, стоит изучить этот подход.
Следующие классы показывают реализацию, использующую Spring JDBC, 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();
}
}
}