LoginSignup
23
26

More than 5 years have passed since last update.

Spring Test DBUnit でテスト毎にテーブルを用意する方法

Last updated at Posted at 2016-05-25

各バージョン

訳あって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を採用することは少なくないと思っています。

application.xml
    <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>
SampleTest.java
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() {
   }
}

その際に、上記のような設定になること多くて、テストを実行するたびに全テーブルを作成してしまい、
あまり効率的ではないです。

実現方法

そこで、次のようなクラス、アノテーションを作成することで、テスト毎に必要なテーブルのみ用意することができます。

application.xml
    <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>
DatabaseInitTestExecutionListener.java
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);
    }
}
DbSchemas.java
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();
}

SampleTest.java
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の定数で定義しているので、使う人によって変更していけばいいのかと思います。

table1.sql
create table if not exists table1 (
   id varchar(10) not null,
   name varchar(100),
   create_date datetime,
   primary key (id)
);
table2.sql
create table if not exists table1 (
   id varchar(10) not null,
   name varchar(100),
   category varchar(3),
   create_date datetime,
   primary key (id)
);

所感

今回は、必要なテーブルを用意するということが目的でしたが、
Spring TestExecutionListenerの仕組みはすごく便利で
今後色々使っていきたいなと思いました。

お願い

もし、私のやり方よりもっと効率のいいやり方あったらコメント下さい。
コメントいただけたら随時反映していきたいと思っています。

23
26
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
23
26