各バージョン
訳あってSpring
のバージョンが3.2.8.RELEASE
ですが、4系の最新版でも問題ないです。
その場合、Spring Test DBUnitのバージョンは、1.3.0
が良いです。
-
Java
1.8.0 update 92 -
Spring Framework
3.2.8.RELEASE -
Spring Test DBUnit
1.0.1 -
DbUnit
2.5.2 -
H2 Database
1.4.191 -
JUnit
4.12
背景
Spring
+ DbUnit
+ Spring Test DBUnit
を使ってDB関連クラスの単体テストを行う場合に、
各開発者の環境やCI環境においてそれぞれの環境でDBを用意するのは大変なので、
インメモリなH2 Database
を採用することは少なくないと思っています。
<bean id="dataSource"
class="org.h2.jdbcx.JdbcDataSource">
<property name="URL" value="jdbc:h2:mem:sample;MODE=MySQL"/>
<property name="user" value="user"/>
<property name="password" value="password"/>
</bean>
<jdbc:initialize-database data-source="dataSource" ignore-failures="DROPS">
<jdbc:script location="classpath:schema.sql" />
</jdbc:initialize-database>
package jp.ijufumi.test;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import jp.ijufumi.test.support.DatabaseInitTestExecutionListener;
import com.github.springtestdbunit.DbUnitTestExecutionListener;
import com.github.springtestdbunit.annotation.DbUnitConfiguration;
import org.junit.runner.RunWith;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.TestExecutionListeners;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.context.support.DependencyInjectionTestExecutionListener;
import org.springframework.test.context.support.DirtiesContextTestExecutionListener;
import org.springframework.test.context.transaction.TransactionConfiguration;
import org.springframework.test.context.transaction.TransactionalTestExecutionListener;
import org.springframework.transaction.annotation.Transactional;
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath*:META-INF/spring/applicationContext*.xml")
@Transactional
@TransactionConfiguration
@TestExecutionListeners({ DependencyInjectionTestExecutionListener.class,
DirtiesContextTestExecutionListener.class,
TransactionalTestExecutionListener.class,
DbUnitTestExecutionListener.class})
public class SampleTest {
@Test
public void test001() {
}
@Test
public void test002() {
}
}
その際に、上記のような設定になること多くて、テストを実行するたびに全テーブルを作成してしまい、
あまり効率的ではないです。
実現方法
そこで、次のようなクラス、アノテーションを作成することで、テスト毎に必要なテーブルのみ用意することができます。
<bean id="dataSource"
class="org.h2.jdbcx.JdbcDataSource">
<property name="URL" value="jdbc:h2:mem:sample;MODE=MySQL"/>
<property name="user" value="user"/>
<property name="password" value="password"/>
</bean>
package jp.ijufumi.test.support;
import javax.sql.DataSource;
import jp.ijufumi.test.annotation.DbSchemes;
import com.github.springtestdbunit.annotation.DbUnitConfiguration;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.io.ClassPathResource;
import org.springframework.jdbc.datasource.init.DatabasePopulatorUtils;
import org.springframework.jdbc.datasource.init.ResourceDatabasePopulator;
import org.springframework.test.context.TestContext;
import org.springframework.test.context.support.AbstractTestExecutionListener;
public class DatabaseInitTestExecutionListener extends AbstractTestExecutionListener {
private Logger logger = LoggerFactory.getLogger(getClass());
private static final String BASE_PATH = "data/db/schema/";
private static final String FILE_SUFFIX = ".sql";
@Override
public void beforeTestMethod(TestContext testContext) throws Exception {
logger.debug("DB Schema setup for {}", testContext.getTestMethod().getName());
DbSchemes dbSchemes = testContext.getTestMethod().getAnnotation(DbSchemes.class);
if (dbSchemes == null) {
logger.debug("@DbSchemes is not exists.");
return;
}
String dataSourceName = "dataSource";
DbUnitConfiguration configuration = testContext.getTestClass().getAnnotation(DbUnitConfiguration.class);
if (configuration != null && StringUtils.isNotEmpty(configuration.databaseConnection()))
{
dataSourceName = configuration.databaseConnection();
}
DataSource dataSource = (DataSource) testContext.getApplicationContext().getBean(dataSourceName);
ResourceDatabasePopulator databasePopulator = new ResourceDatabasePopulator();
for (String s : dbSchemes.value())
{
logger.debug("Create table : {}", s);
databasePopulator.addScript(new ClassPathResource(BASE_PATH + s + FILE_SUFFIX));
}
DatabasePopulatorUtils.execute(databasePopulator, dataSource);
}
}
package jp.ijufumi.test.annotation;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
@Inherited
@Documented
public @interface DbSchemes {
String[] value();
}
package jp.ijufumi.test;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import jp.ijufumi.test.support.DatabaseInitTestExecutionListener;
import com.github.springtestdbunit.DbUnitTestExecutionListener;
import com.github.springtestdbunit.annotation.DbUnitConfiguration;
import org.junit.runner.RunWith;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.TestExecutionListeners;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.context.support.DependencyInjectionTestExecutionListener;
import org.springframework.test.context.support.DirtiesContextTestExecutionListener;
import org.springframework.test.context.transaction.TransactionConfiguration;
import org.springframework.test.context.transaction.TransactionalTestExecutionListener;
import org.springframework.transaction.annotation.Transactional;
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath*:META-INF/spring/applicationContext*.xml")
@Transactional
@TransactionConfiguration
@TestExecutionListeners({ DependencyInjectionTestExecutionListener.class,
DirtiesContextTestExecutionListener.class,
TransactionalTestExecutionListener.class,
DatabaseInitTestExecutionListener.class,
DbUnitTestExecutionListener.class})
public class SampleTest {
@Test
@DbSchemas("table1")
public void test001() {
}
@Test
@DbSchemas({"table1", "table2"})
public void test002() {
}
}
で、以下のようなDDLを用意します。
格納場所は、{project.dir}/src/test/resources/data/db/schema/SampleTest
で、拡張子は.sql
です。
この辺は、DatabaseInitTestExecutionListener
の定数で定義しているので、使う人によって変更していけばいいのかと思います。
create table if not exists table1 (
id varchar(10) not null,
name varchar(100),
create_date datetime,
primary key (id)
);
create table if not exists table1 (
id varchar(10) not null,
name varchar(100),
category varchar(3),
create_date datetime,
primary key (id)
);
所感
今回は、必要なテーブルを用意するということが目的でしたが、
Spring Test
のExecutionListener
の仕組みはすごく便利で
今後色々使っていきたいなと思いました。
お願い
もし、私のやり方よりもっと効率のいいやり方あったらコメント下さい。
コメントいただけたら随時反映していきたいと思っています。