DBUnitというツールを利用すると、データベースのデータ検索・追加・更新・削除のテストを実施できるが、その際、テスト前にテストデータを設定する@DatabaseSetupアノテーションや、テスト後のデータを検証する@ExpectedDatabaseアノテーションを利用すると便利である。
今回は、JUnit4でDBUnitの@DatabaseSetupや@ExpectedDatabaseというアノテーションを利用したテストを行うサンプルプログラムを作成してみたので、共有する。
前提条件
下記記事の実装が完了していること。
作成したサンプルプログラムの内容
作成したサンプルプログラムの構成は以下の通り。
なお、上記の赤枠は、前提条件のプログラムから追加・変更したプログラムである。
build.gradleの内容は以下の通りで、DBUnitやMyBatisのテストを行うためのライブラリを追加している。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 | plugins { id 'org.springframework.boot' version '2.1.7.RELEASE' id 'java' } apply plugin: 'io.spring.dependency-management' group = 'com.example' version = '0.0.1-SNAPSHOT' sourceCompatibility = '1.8' repositories { mavenCentral() } dependencies { implementation 'org.springframework.boot:spring-boot-starter-thymeleaf' implementation 'org.springframework.boot:spring-boot-starter-web' testImplementation 'org.springframework.boot:spring-boot-starter-test' compileOnly 'org.projectlombok:lombok:1.18.10' annotationProcessor 'org.projectlombok:lombok:1.18.10' compile files('lib/ojdbc6.jar') implementation 'org.mybatis.spring.boot:mybatis-spring-boot-starter:2.1.1' // DBUnitについての設定を追加 testCompile group: 'org.dbunit', name: 'dbunit', version: '2.6.0' testCompile group: 'com.github.springtestdbunit', name: 'spring-test-dbunit', version: '1.3.0' // MyBatisのテストを行うための設定を追加 testImplementation 'org.mybatis.spring.boot:mybatis-spring-boot-starter-test:2.1.3' } |
ユーザーデータエンティティのクラスは以下の通りで、@NoArgsConstructorや@AllArgsConstructorアノテーションを付与している。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 | package com.example.demo.mapper; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; /** * ユーザーデータテーブル(user_data)アクセス用エンティティ */ @Data // @NoArgsConstructorアノテーションで引数のない // コンストラクタを、@AllArgsConstructorアノテーションで // 全てのフィールドを引数に持つコンストラクタを定義している @NoArgsConstructor @AllArgsConstructor public class UserData { /** ID */ private long id; /** 名前 */ private String name; /** 生年月日_年 */ private int birthY; /** 生年月日_月 */ private int birthM; /** 生年月日_日 */ private int birthD; /** 性別 */ private String sex; /** メモ */ private String memo; /** 性別(文字列) */ private String sex_value; } |
ユーザーデータテーブルにアクセスするMapperは以下の通りで、findAllメソッド・createメソッドを追加している。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 | package com.example.demo.mapper; import org.apache.ibatis.annotations.Mapper; import java.util.List; @Mapper public interface UserDataMapper { /** * 指定したIDをもつユーザーデータテーブル(user_data)のデータを取得する * @param id ID * @return ユーザーデータテーブル(user_data)の指定したIDのデータ */ UserData findById(Long id); /** * 指定したユーザーデータテーブル(user_data)のデータを更新する * @param userData ユーザーデータテーブル(user_data)の更新データ */ void update(UserData userData); /** * ユーザーデータテーブル(user_data)の全データを取得する * @return ユーザーデータテーブル(user_data)の全データ */ List<UserData> findAll(); /** * 指定したユーザーデータテーブル(user_data)のデータを追加する * @param userData ユーザーデータテーブル(user_data)の追加データ */ void create(UserData userData); } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 | <?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> <mapper namespace="com.example.demo.mapper.UserDataMapper"> <resultMap id="userDataResultMap" type="com.example.demo.mapper.UserData" > <id column="id" property="id" jdbcType="BIGINT" /> <result column="name" property="name" jdbcType="VARCHAR" /> <result column="birthY" property="birthY" jdbcType="VARCHAR" /> <result column="birthM" property="birthM" jdbcType="VARCHAR" /> <result column="birthD" property="birthD" jdbcType="VARCHAR" /> <result column="sex" property="sex" jdbcType="VARCHAR" /> <result column="memo" property="memo" jdbcType="VARCHAR" /> <result column="sex_value" property="sex_value" jdbcType="VARCHAR" /> </resultMap> <select id="findById" parameterType="java.lang.Long" resultMap="userDataResultMap"> SELECT id , name , birth_year as birthY , birth_month as birthM , birth_day as birthD , sex , memo , CASE sex WHEN '1' THEN '男' WHEN '2' THEN '女' ELSE '' END AS sex_value FROM USER_DATA WHERE id = #{id} </select> <update id="update" parameterType="com.example.demo.mapper.UserData"> UPDATE USER_DATA SET name = #{name}, birth_year = #{birthY} , birth_month = #{birthM}, birth_day = #{birthD} , sex = #{sex}, memo = #{memo,jdbcType=VARCHAR} WHERE id = #{id} </update> <select id="findAll" resultMap="userDataResultMap"> SELECT id , name , birth_year as birthY , birth_month as birthM , birth_day as birthD , sex , memo , CASE sex WHEN '1' THEN '男' WHEN '2' THEN '女' ELSE '' END AS sex_value FROM USER_DATA </select> <insert id="create" parameterType="com.example.demo.mapper.UserData"> INSERT INTO USER_DATA ( id , name , birth_year , birth_month , birth_day , sex , memo ) VALUES ( #{id} , #{name} , #{birthY} , #{birthM} , #{birthD} , #{sex} , #{memo,jdbcType=VARCHAR} ) </insert> </mapper> |
UserDataTest1クラスの内容は以下の通りで、@DatabaseSetupアノテーションの検証を行っている。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 | package com.example.demo.mapper; import com.github.springtestdbunit.DbUnitTestExecutionListener; import com.github.springtestdbunit.TransactionDbUnitTestExecutionListener; import com.github.springtestdbunit.annotation.DatabaseSetup; import com.github.springtestdbunit.annotation.DbUnitConfiguration; import org.junit.Before; import org.junit.Test; import org.junit.After; import org.junit.runner.RunWith; import org.mybatis.spring.boot.test.autoconfigure.MybatisTest; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase; import org.springframework.test.context.TestExecutionListeners; import org.springframework.test.context.junit4.SpringRunner; import org.springframework.test.context.support.DependencyInjectionTestExecutionListener; import org.springframework.test.context.support.DirtiesContextTestExecutionListener; import java.util.ArrayList; import java.util.List; import static org.junit.Assert.assertEquals; // JUnit4ベースでMyBatisのテストを実行する @RunWith(SpringRunner.class) @MybatisTest // デフォルトのDBでなく、実際のDBを利用する @AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE) // テストを実行する際のリスナーを定義する // DependencyInjectionTestExecutionListenerとDirtiesContextTestExecutionListenerで、 // SpringのDI機能を利用できるようにし、TransactionDbUnitTestExecutionListenerと // DbUnitTestExecutionListenerで、トランザクション管理やDBデータの設定・検証・後処理が // できるようにしている @TestExecutionListeners({DependencyInjectionTestExecutionListener.class , DirtiesContextTestExecutionListener.class , TransactionDbUnitTestExecutionListener.class , DbUnitTestExecutionListener.class}) // ExcelのデータをDBに設定する際、DemoXlsDataSetLoaderクラスを利用し、 // DB接続する際、(DemoTestDbConfigクラスの)dbUnitDatabaseConnectionという // データソースコネクションファクトリを利用する @DbUnitConfiguration(dataSetLoader = DemoXlsDataSetLoader.class , databaseConnection = {"dbUnitDatabaseConnection"}) public class UserDataTest1 { /** * ユーザーデータテーブル(user_data)へアクセスするマッパー */ @Autowired private UserDataMapper userDataMapper; /** * 各テストメソッドを実行する前に行う処理を定義する. */ @Before public void beforeTest() { System.out.println(); System.out.println("*** UserDataTest1クラス テスト結果 start ***"); } /** * テストを実行する前に、databaseSetupTest.xlsxに定義したデータを読み込み、 * 取得したデータを検証する. */ @Test @DatabaseSetup("/com/example/demo/xls/databaseSetupTest.xlsx") public void userDataTest() { // @DatabaseSetupアノテーションで設定されたデータを確認 System.out.println("*** ユーザーデータテーブル(user_data)のデータ ***"); List<UserData> userDataList = userDataMapper.findAll(); for (UserData userData : userDataList) { System.out.println(userData); } assertEquals(expectedUserDataList(), userDataList); } /** * 予想されるテスト結果のデータを定義する. * @return 予想されるテスト結果のデータ */ private List<UserData> expectedUserDataList() { List<UserData> userDataList = new ArrayList<>(); UserData userData1 = new UserData(1, "テスト プリン1" , 2012, 2, 10, "1", null, "男"); UserData userData2 = new UserData(2, "テスト プリン2" , 2013, 3, 15, "2", null, "女"); UserData userData3 = new UserData(3, "テスト プリン3" , 2015, 4, 21, "2", "テスト3", "女"); userDataList.add(userData1); userDataList.add(userData2); userDataList.add(userData3); return userDataList; } /** * 各テストメソッドを実行した後に行う処理を定義する. */ @After public void afterTestClass() { System.out.println("*** UserDataTest1クラス テスト結果 end ***"); System.out.println(); } } |
UserDataTest2クラスの内容は以下の通りで、@DatabaseSetupと@ExpectedDatabaseのアノテーションの検証を行っている。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 | package com.example.demo.mapper; import com.github.springtestdbunit.DbUnitTestExecutionListener; import com.github.springtestdbunit.TransactionDbUnitTestExecutionListener; import com.github.springtestdbunit.annotation.DatabaseSetup; import com.github.springtestdbunit.annotation.DbUnitConfiguration; import com.github.springtestdbunit.annotation.ExpectedDatabase; import com.github.springtestdbunit.assertion.DatabaseAssertionMode; import org.junit.After; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.mybatis.spring.boot.test.autoconfigure.MybatisTest; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase; import org.springframework.test.context.TestExecutionListeners; import org.springframework.test.context.junit4.SpringRunner; import org.springframework.test.context.support.DependencyInjectionTestExecutionListener; import org.springframework.test.context.support.DirtiesContextTestExecutionListener; import java.util.List; @RunWith(SpringRunner.class) @MybatisTest @AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE) @TestExecutionListeners({DependencyInjectionTestExecutionListener.class , DirtiesContextTestExecutionListener.class , TransactionDbUnitTestExecutionListener.class , DbUnitTestExecutionListener.class}) @DbUnitConfiguration(dataSetLoader = DemoXlsDataSetLoader.class , databaseConnection = {"dbUnitDatabaseConnection"}) public class UserDataTest2 { /** * ユーザーデータテーブル(user_data)へアクセスするマッパー */ @Autowired private UserDataMapper userDataMapper; /** * 各テストメソッドを実行する前に行う処理を定義する. */ @Before public void beforeTest() { System.out.println(); System.out.println("*** UserDataTest2クラス テスト結果 start ***"); } /** * テストを実行した後で、expectedDatabaseTest.xlsxに定義したデータが * 追加されることを検証する. */ // @ExpectedDatabaseアノテーションで、assertionMode属性に // 「DatabaseAssertionMode.NON_STRICT_UNORDERED」を指定することで、 // expectedDatabaseTest.xlsxに記載のあるテーブル・カラムのみを、行の順序を無視して // 検証できるようにしている @Test @DatabaseSetup("/com/example/demo/xls/databaseSetupTest.xlsx") @ExpectedDatabase(value="/com/example/demo/xls/expectedDatabaseTest.xlsx" , assertionMode = DatabaseAssertionMode.NON_STRICT_UNORDERED) public void userDataTest() { UserData userDataAdd = new UserData(4, "テスト プリン4" , 2016, 5, 6, "1", "テスト4", "男"); userDataMapper.create(userDataAdd); System.out.println("*** ユーザーデータテーブル(user_data)のデータ ***"); List<UserData> userDataList = userDataMapper.findAll(); for (UserData userData : userDataList) { System.out.println(userData); } } /** * 各テストメソッドを実行した後に行う処理を定義する. */ @After public void afterTestClass() { System.out.println("*** UserDataTest2クラス テスト結果 end ***"); System.out.println(); } } |
UserDataTest1クラス・UserDataTest2クラスの@DatabaseSetupアノテーションで読み込んでいるエクセルデータの内容は、以下の通り。
UserDataTest1クラス・UserDataTest2クラスの@ExpectedDatabaseアノテーションで検証しているエクセルデータの内容は、以下の通り。
UserDataTest1クラス・UserDataTest2クラスで呼ばれる、DB接続定義を行うクラスの内容は、以下の通り。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 | package com.example.demo.mapper; import com.github.springtestdbunit.bean.DatabaseConfigBean; import com.github.springtestdbunit.bean.DatabaseDataSourceConnectionFactoryBean; import org.dbunit.ext.oracle.Oracle10DataTypeFactory; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.boot.autoconfigure.jdbc.DataSourceProperties; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Primary; import org.springframework.context.annotation.PropertySource; import org.springframework.jdbc.datasource.DataSourceTransactionManager; import org.springframework.transaction.PlatformTransactionManager; import javax.sql.DataSource; // JUnit4ベースでMyBatisのテストを実行する際のDB接続定義 // @MybatisTestアノテーションでテストを実行する際、 // Spring Bootアプリケーションが起動するようにする。 // その際、com.example.demo.mapperフォルダ下のインスタンス // (UserDataMapper)のみがDIで使用できるようになる。 @SpringBootApplication @PropertySource(value = {"classpath:test.properties"}) public class DemoTestDbConfig { /** * Oracleのデータソースプロパティ(UT)を生成する. * @return Oracleのデータソースプロパティ(UT) */ @Bean(name = {"datasourceOraProperties"}) @Primary @ConfigurationProperties(prefix = "spring.datasourceut") public DataSourceProperties datasourceOraProperties() { return new DataSourceProperties(); } /** * Oracleのデータソース(UT)を生成する. * @param properties Oracleのデータソースプロパティ(UT) * @return Oracleのデータソース(UT) */ @Bean(name = {"dataSourceOra"}) @Primary public DataSource datasourceOra( @Qualifier("datasourceOraProperties") DataSourceProperties properties) { return properties.initializeDataSourceBuilder().build(); } /** * Oracleのトランザクションマネージャ(UT)を生成する. * @param dataSourceOra Oracleのデータソース(UT) * @return Oracleのトランザクションマネージャ(UT) */ @Bean(name = {"txManagerOra"}) @Primary public PlatformTransactionManager txManagerOra( @Qualifier("dataSourceOra") DataSource dataSourceOra) { return new DataSourceTransactionManager(dataSourceOra); } /** * DB接続設定(UT)を生成する. * @return DB接続設定(UT) */ @Bean(name = {"dbUnitDatabaseConfig"}) public DatabaseConfigBean dbUnitDatabaseConfig() { DatabaseConfigBean bean = new DatabaseConfigBean(); bean.setAllowEmptyFields(true); bean.setDatatypeFactory(new Oracle10DataTypeFactory()); return bean; } /** * データソースコネクションファクトリ(UT)を生成する. * @param dbUnitDatabaseConfig DB接続設定(UT) * @param dataSourceOra Oracleのデータソース(UT) * @return データソースコネクションファクトリ(UT) */ @Bean(name = {"dbUnitDatabaseConnection"}) public DatabaseDataSourceConnectionFactoryBean dbUnitDatabaseConnection( @Qualifier("dbUnitDatabaseConfig") DatabaseConfigBean dbUnitDatabaseConfig , @Qualifier("dataSourceOra") DataSource dataSourceOra) { DatabaseDataSourceConnectionFactoryBean bean = new DatabaseDataSourceConnectionFactoryBean(dataSourceOra); bean.setDatabaseConfig(dbUnitDatabaseConfig); bean.setSchema("USER01"); return bean; } } |
また、上記クラスで読み込むDB接続は、以下のプロパティファイルから読み込んでいる。
1 2 3 4 5 | # DB接続先 spring.datasourceut.url=jdbc:oracle:thin:@localhost:1521:xe spring.datasourceut.username=USER01 spring.datasourceut.password=USER01 spring.datasourceut.driverClassName=oracle.jdbc.driver.OracleDriver |
UserDataTest1クラス・UserDataTest2クラスで呼ばれる、エクセルデータをDBに設定する定義を行うクラスの内容は、以下の通り。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 | package com.example.demo.mapper; import com.github.springtestdbunit.dataset.AbstractDataSetLoader; import org.dbunit.dataset.DataSetException; import org.dbunit.dataset.IDataSet; import org.dbunit.dataset.ReplacementDataSet; import org.dbunit.dataset.excel.XlsDataSet; import org.springframework.core.io.Resource; import java.io.IOException; import java.io.InputStream; public class DemoXlsDataSetLoader extends AbstractDataSetLoader { /** * ExcelのデータをDBに格納する際の変換仕様を定義する. * @param resource リソースオブジェクト * @return 変換後データセット * @throws IOException 入出力例外 * @throws DataSetException データセット例外 */ @Override protected IDataSet createDataSet(Resource resource) throws IOException , DataSetException { try (InputStream inputStream = resource.getInputStream()) { XlsDataSet xlsDataSet = new XlsDataSet(inputStream); ReplacementDataSet replacementDataSet = new ReplacementDataSet(xlsDataSet); // Excel上で「(NULL)」という記載は、DB上ではNULL値に変換する replacementDataSet.addReplacementObject("(NULL)", null); return replacementDataSet; } } } |
その他のソースコード内容は、以下のサイトを参照のこと。
https://github.com/purin-it/java/tree/master/junit4-dbunit-annotation/demo
サンプルプログラムの実行結果
サンプルプログラムの実行結果は、以下の通り。
2) UserDataTest1クラスを実行した時のログ出力内容は、以下の通りで、databaseSetupTest.xlsxのデータがDBに設定されたことが確認できる。
3) UserDataTest2クラスを実行した時のログ出力内容は、以下の通りで、テスト実行後にexpectedDatabaseTest.xlsxのデータがDBに設定されていることが確認できる。
4) テスト実行後のデータは、以下の通りで、実際のDBデータは更新されずロールバックされることが確認できる。
要点まとめ
- DBUnitというツールを利用してDB回りのテストを行う際、テスト前にテストデータを設定する@DatabaseSetupアノテーションや、テスト後のデータを検証する@ExpectedDatabaseアノテーションを利用すると便利である。