かんがるーさんの日記

最近自分が興味をもったものを調べた時の手順等を書いています。今は Spring Boot をいじっています。

Spring Boot で書籍の貸出状況確認・貸出申請する Web アプリケーションを作る ( 番外編 )( Spring Boot を 1.2.7 → 1.2.8 へバージョンアップ )

概要

  • Spring Boot の 1.2.8 にバージョンアップします。1.3 系にはバージョンアップしません。
  • Spring IO platform を使用できていないことに気づいたので、使用するよう修正してみます。
  • バージョンが最新でないライブラリを最新にします。

参照したサイト・書籍

  1. Spring IO platform
    http://platform.spring.io/platform/

  2. Spring IO Platform Reference Guide
    http://docs.spring.io/platform/docs/1.1.5.RELEASE/reference/htmlsingle/

  3. Better dependency management for Gradle
    https://spring.io/blog/2015/02/23/better-dependency-management-for-gradle

  4. Class RuleChain
    http://junit.org/apidocs/org/junit/rules/RuleChain.html

  5. Gradle で BOM を使いたいときには Spring チームの出している Dependency management plugin を使うのがよさそう
    http://create-something.hatenadiary.jp/entry/2015/05/08/063000

目次

  1. feature/51-issue ブランチの作成
  2. Spring Boot を 1.2.7 → 1.2.8 へバージョンアップする
  3. Spring IO platform を使用しようとしたが。。。
  4. TestDataResource クラスの修正
  5. バージョンが最新でないライブラリを最新にする
  6. Push、Pull Request、マージ
  7. 次回は。。。

手順

feature/51-issue ブランチの作成

  1. feature/51-issue ブランチを作成します。

Spring Boot を 1.2.7 → 1.2.8 へバージョンアップする

  1. 最初に spring-boot-gradle-plugin に指定するバージョンと Spring Security のバージョンを変更します。build.gradle を リンク先のその1の内容 に変更します。

  2. Gradle projects View の左上にある「Refresh all Gradle projects」ボタンをクリックして更新します。

    Project View の External Libraries で Spring Boot が 1.2.8.RELEASE になっていることが確認できます。

    f:id:ksby:20160102131732p:plain

  3. clean タスク実行 → Rebuild Project 実行をします。

    「org.springframework.test.utilのorg.springframework.test.util.MatcherAssertionErrorsは非推奨になりました」というエラーメッセージが表示されました。

    f:id:ksby:20160102113611p:plain

  4. src/test/java/ksbysample/common/test の下の CustomModelResultMatchers.javaリンク先の内容 に変更します。

  5. 再度 Rebuild Project を実行してエラーが出ずに完了したので、build タスクを実行します。

    問題なく "BUILD SUCCESSFUL" が出力されました。

    f:id:ksby:20160102132307p:plain

  6. Project View から Run 'All Tests' with Coverage を実行してみます。

    テストは全て成功しました。

    f:id:ksby:20160102132844p:plain

  7. 一旦 commit します。

Spring IO platform を使用しようとしたが。。。

build.gradle に dependency-management-plugin を記述していたのですが io.spring.platform:platform-bom を記述していませんでした。io.spring.platform:platform-bom を記述し、Spring IO platform にバージョンの指定を任せるライブラリは dependencies タスクでバージョンを記載しないようにします。

  1. まずは io.spring.platform:platform-bom の記述だけ追加して問題ないか確認します。build.gradle を リンク先のその2の内容 に変更します。

  2. Gradle projects View の左上にある「Refresh all Gradle projects」ボタンをクリックして更新します。

    Project View の External Libraries で Spring Boot のバージョンは 1.2.8.RELEASE のままで特に変更はなさそうです。

    f:id:ksby:20160106084040p:plain

  3. clean タスク実行 → Rebuild Project 実行をしてみましたが、特にエラーは発生しませんでした。

  4. build タスクを実行します。が、10 個のテストでエラーが発生しました。原因を調査します。

    f:id:ksby:20160106085209p:plain

  5. 調査の結果、分かったのは以下の点でした。

    • エラーが発生しているのは全て @TestDataLoader アノテーションを付加してテストデータを投入しているものでした。
    • io.spring.platform:platform-bom を記述する前は TestDataResource によるバックアップ&初期データ投入 → TestDataLoaderResource によるテストデータ投入 という順番で処理されていたのですが、io.spring.platform:platform-bom を記述した後は TestDataLoaderResource → TestDataResource という以前と逆の順番で処理されていました。
    • 実は以前 自作したテスト用クラス ( src/test/java/ksbysample/common/test ) の使い方 を書いた時に、JUnit の Rule は処理順序は決まっておらず実行順序を制御したい場合には RuleChain を使用する必要があるという仕様に気づいていたのですが、なぜかうまく動いていたので無視していたんですよね。。。
    • io.spring.platform:platform-bom を記述する前と後で junit のバージョンがどう変わるのかも確認してみました。記述前は、 f:id:ksby:20160106141130p:plain と 4.12 でしたが、記述後は、 f:id:ksby:20160106141416p:plain と 4.11 でバージョンダウンしていました。4.12 がリリースされたのは 2014/12/4 なので 4.12 になっていて欲しかったのですが、4.12 になるのは Spring IO platform の 2.0系からのようです ( 2.0.1 の Reference Guide を見ると junit 4.12 でした )。
    • Spring Boot 1.2.8 の Appendix E. Dependency versions を見ると junit のバージョンは 4.12 なのですが、Spring IO Platform の 1.1.5 の Appendix A. Dependency versions を見ると junit のバージョンは 4.11 でした。
  6. 以下のように対応します。

    • junit 以外にもバージョンダウンしてしまうライブラリがあり Spring IO platform を使用するメリットが感じられないので build.gradle に io.spring.platform:platform-bom の記述を入れないことにします。Spring Boot の 1.3系を使用する時に改めて検証します。
    • TestDataResource と TestDataLoaderResource の2つに分けておくのは問題のようなので、TestDataResource にテストデータロードの機能も持たせるようにします。
  7. build.gradle に追加した dependencyManagement { ... } の記述を削除した後、Gradle projects View の左上にある「Refresh all Gradle projects」ボタンをクリックして更新して元に戻します。

TestDataResource クラスの修正

  1. 実装方式を決めます。

    • TestDataResource クラスは ExternalResource クラスの継承クラス、TestDataLoaderResource クラスは TestWatcher クラスの継承クラスである。
    • テストデータを指定する @TestDataLoader アノテーションについては特に不満を感じていないので、このまま使用したい。
    • そうすると TestDataResource クラスに TestDataLoaderResource クラスのテストデータロード機能を持たせるためにはテストメソッドに付加されたアノテーションを判別するための Description description が必要。
    • なぜ ExternalResource クラスの継承クラスの before メソッドには Description description がないのか ExternalResource クラスと TestWatcher クラスのソースを確認してみたところ、
      • ExternalResource クラスと TestWatcher クラスはどちらも TestRule インターフェースを実装した抽象クラスである。
      • TestRule インターフェースに定義されているメソッドは apply のみ。apply メソッドの引数には Description description がある。
      • ExternalResource クラスと TestWatcher クラスの apply メソッドの実装を比較してみたところ、ExternalResource.before と TestWatcher.starting、ExternalResource.after と TestWatcher.finished が実行タイミングが同じで、違いは引数 Description description の有無や例外発生時の処理が異なる程度であった。
    • であれば TestDataResource の継承元を ExternalResource → TestWatcher へ変更して、実装を一部修正すればよさそうです。
    • TestDataLoaderResource クラスは削除しますが、テストデータをロードするだけのクラスはあると便利なのでユーティリティクラスに変更します。
    • ユーティリティクラスのクラス名を TestDataLoader にしたいので、テストデータを指定するアノテーションを @TestDataLoader → @TestData へ変更します。
  2. src/test/java/ksbysample/common/test の下の TestDataLoader.java を選択してコンテキストメニューから「Refactor」-「Rename」を選択した後、「Rename」ダイアログでクラス名を "TestData" に変更して「OK」ボタンをクリックします。

    f:id:ksby:20160106175007p:plain

    「Rename Variables」ダイアログが表示されるので、上のリストのアイテムをチェックした後「OK」ボタンをクリックします。

    f:id:ksby:20160106175150p:plain

  3. 以下のソース内の @Rule @Autowired public TestDataLoaderResource testDataLoaderResource; の記述を削除します。

    • src/test/java/ksbysample/webapp/lending/listener/rabbitmq/InquiringStatusOfBookQueueListenerTest.java
    • src/test/java/ksbysample/webapp/lending/web/admin/library/AdminLibraryControllerTest.java
    • src/test/java/ksbysample/webapp/lending/web/admin/library/AdminLibraryServiceTest.java
    • src/test/java/ksbysample/webapp/lending/web/lendingapp/LendingappControllerTest.java

    作業としては IntelliJ IDEA で Ctrl+SHIFT+F を押して検索した後、検索結果を1つずつクリックして修正していったのですが、削除する毎に検索結果の該当行が "INVALID" という赤文字に変わり、どこまで作業したのかが分かるのはちょっと便利だなと思いました。

    f:id:ksby:20160106180034p:plain

  4. src/test/java/ksbysample/common/test の下の TestDataLoaderResource.java を TestDataLoader.java にリネームした後、リンク先の内容 に変更します。

  5. src/test/java/ksbysample/common/test の下の TestDataResource.javaリンク先の内容 に変更します。

  6. clean タスク実行 → Rebuild Project 実行 → build タスクを実行します。"BUILD SUCCESSFUL" が出力されることが確認できます。

    f:id:ksby:20160106194829p:plain

  7. Project View から Run 'All Tests' with Coverage を実行します。テストが全て成功しました。

    f:id:ksby:20160106195521p:plain

  8. 一旦 commit します。

バージョンが最新でないライブラリを最新にする

  1. バージョン番号を指定しているライブラリの最新版を jCenter ( https://bintray.com/bintray/jcenter ) のサイトで確認して、最新版に更新します。build.gradle を リンク先のその3の内容 に変更します。

  2. Gradle projects View の左上にある「Refresh all Gradle projects」ボタンをクリックして更新します。

  3. clean タスク実行 → Rebuild Project 実行 → build タスクを実行します。"BUILD SUCCESSFUL" が出力されることが確認できます。

    f:id:ksby:20160106202637p:plain

  4. Project View から Run 'All Tests' with Coverage を実行します。がこちらは3つテストが失敗しました。原因を調査します。

    f:id:ksby:20160106203341p:plain

  5. エラーメッセージが java.lang.IllegalStateException: JMockit wasn't properly initialized; please ensure that jmockit precedes junit in the runtime classpath, or use @RunWith(JMockit.class) と出力されており、これまで正常に動作していた JMockit が build.gradle 更新後に動作しなくなっているので、ライブラリの順序指定で JMockitJUnit より前になっていないだけだと思います。設定を修正します。

    1. IntelliJ IDEA のメイン画面から「File」-「Project Structure...」を選択します。
    2. 「Project Structure」ダイアログが表示されます。画面左側で「Project Settings」-「Modules」を選択します。
    3. 画面左側のリストで上下矢印ボタンを押して jmockitjunit の上に移動します。 f:id:ksby:20160106204628p:plain
    4. 「OK」ボタンをクリックしてダイアログを閉じます。
  6. 再度 Project View から Run 'All Tests' with Coverage を実行します。今度はテストが全て成功しました。

    f:id:ksby:20160106204805p:plain

  7. 一旦 commit します。

Push、Pull Request、マージ

  1. GitHub へ Push、feature/51-issue -> 1.0.x へ Pull Request、1.0.x でマージ、feature/51-issue ブランチを削除、をします。

次回は。。。

貸出承認画面 ( 承認者のみ ) の作成に進みます。

ソースコード

build.gradle

■その1

buildscript {
    ext {
        springBootVersion = '1.2.8.RELEASE'
    }
    repositories {
        jcenter()
        maven { url "http://repo.spring.io/repo/" }
    }
    dependencies {
        classpath("org.springframework.boot:spring-boot-gradle-plugin:${springBootVersion}")
        classpath("io.spring.gradle:dependency-management-plugin:0.5.3.RELEASE")
        // for Grgit
        classpath("org.ajoberstar:grgit:1.4.1")
        // Gradle Download Task
        classpath("de.undercouch:gradle-download-task:2.0.0")
    }
}

apply plugin: 'java'
apply plugin: 'eclipse'
apply plugin: 'idea'
apply plugin: 'spring-boot'
apply plugin: 'io.spring.dependency-management'
apply plugin: 'de.undercouch.download'
apply plugin: 'groovy'

sourceCompatibility = 1.8
targetCompatibility = 1.8

compileJava.options.compilerArgs = ['-Xlint:all']
compileTestGroovy.options.compilerArgs = ['-Xlint:all']
compileTestJava.options.compilerArgs = ['-Xlint:all']

// for Doma 2
// JavaクラスとSQLファイルの出力先ディレクトリを同じにする
processResources.destinationDir = compileJava.destinationDir
// コンパイルより前にSQLファイルを出力先ディレクトリにコピーするために依存関係を逆転する
compileJava.dependsOn processResources

jar {
    baseName = 'ksbysample-webapp-lending'
    version = '1.0.0-SNAPSHOT'
}

eclipse {
    classpath {
        containers.remove('org.eclipse.jdt.launching.JRE_CONTAINER')
        containers 'org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.8'
    }
}

idea {
    module {
        inheritOutputDirs = false
        outputDir = file("$buildDir/classes/main/")
    }
}

configurations {
    domaGenRuntime
}

repositories {
    jcenter()
}

dependencies {
    def jdbcDriver = "org.postgresql:postgresql:9.4-1204-jdbc41"

    // spring-boot-gradle-plugin によりバージョン番号が自動で設定されるもの
    // Appendix E. Dependency versions ( http://docs.spring.io/spring-boot/docs/current/reference/html/appendix-dependency-versions.html ) 参照
    compile("org.springframework.boot:spring-boot-starter-web")
    compile("org.springframework.boot:spring-boot-starter-thymeleaf")
    compile("org.thymeleaf.extras:thymeleaf-extras-springsecurity3")
    compile("org.springframework.boot:spring-boot-starter-data-jpa")
    compile("org.springframework.boot:spring-boot-starter-velocity")
    compile("org.springframework.boot:spring-boot-starter-mail")
    compile("org.springframework.boot:spring-boot-starter-security")
    compile("org.springframework.boot:spring-boot-starter-redis")
    compile("org.springframework.boot:spring-boot-starter-amqp")
    compile("org.codehaus.janino:janino")
    compile("com.fasterxml.jackson.datatype:jackson-datatype-jsr310")
    compile("com.fasterxml.jackson.dataformat:jackson-dataformat-xml")
    testCompile("org.springframework.boot:spring-boot-starter-test")
    // (ここから) gradle でテストを実行した場合に spring-security-test-4.0.3.RELEASE.jar しか classpath に指定されず
    // テストが失敗したため、3.2.9.RELEASE を明記している
    testCompile("org.springframework.security:spring-security-core:3.2.9.RELEASE")
    testCompile("org.springframework.security:spring-security-web:3.2.9.RELEASE")
    // (ここまで) ------------------------------------------------------------------------------------------------------
    testCompile("org.springframework.security:spring-security-test:4.0.3.RELEASE")
    testCompile("org.yaml:snakeyaml")
    testCompile("org.codehaus.groovy:groovy-all")
    testCompile("org.spockframework:spock-core")
    testCompile("org.spockframework:spock-spring")

    // spring-boot-gradle-plugin によりバージョン番号が自動で設定されないもの
    compile("${jdbcDriver}")
    compile("org.seasar.doma:doma:2.5.0")
    compile("org.bgee.log4jdbc-log4j2:log4jdbc-log4j2-jdbc4.1:1.16")
    compile("org.apache.commons:commons-lang3:3.4")
    compile("org.projectlombok:lombok:1.16.6")
    compile("com.google.guava:guava:18.0")
    compile("org.springframework.session:spring-session:1.0.1.RELEASE")
    compile("org.simpleframework:simple-xml:2.7.1")
    compile("com.univocity:univocity-parsers:1.5.6")
    compile("org.thymeleaf.extras:thymeleaf-extras-java8time:2.1.0.RELEASE")
    testCompile("org.dbunit:dbunit:2.5.1")
    testCompile("com.icegreen:greenmail:1.4.1")
    testCompile("org.assertj:assertj-core:3.2.0")
    testCompile("com.jayway.jsonpath:json-path:2.0.0")
    testCompile("org.jmockit:jmockit:1.19")

    // for Doma-Gen
    domaGenRuntime("org.seasar.doma:doma-gen:2.5.0")
    domaGenRuntime("${jdbcDriver}")
}
  • springBootVersion = '1.2.7.RELEASE'springBootVersion = '1.2.8.RELEASE' に変更します。
  • dependencies タスクの以下の点を変更します。
    • testCompile("org.springframework.security:spring-security-core:3.2.8.RELEASE")testCompile("org.springframework.security:spring-security-core:3.2.9.RELEASE") に変更します。
    • testCompile("org.springframework.security:spring-security-web:3.2.8.RELEASE")testCompile("org.springframework.security:spring-security-web:3.2.9.RELEASE") に変更します。
    • testCompile("org.springframework.security:spring-security-test:4.0.2.RELEASE") は 4.0.3.RELEASE には変更しません。 変更するとエラーが出てテストが失敗するためです。

■その2

repositories {
    jcenter()
}

dependencyManagement {
    imports {
        mavenBom 'io.spring.platform:platform-bom:1.1.5.RELEASE'
    }
}

dependencies {
    def jdbcDriver = "org.postgresql:postgresql:9.4-1204-jdbc41"

  • repositories の下に dependencyManagement { ... } を追加します。Spring Boot 1.2.8 に対応する io.spring.platform:platform-bom のバージョンは 1.1.5 なので 1.1.5.RELEASE を指定します。

■その3

dependencies {
    def jdbcDriver = "org.postgresql:postgresql:9.4-1204-jdbc41"

    // spring-boot-gradle-plugin によりバージョン番号が自動で設定されるもの
    // Appendix E. Dependency versions ( http://docs.spring.io/spring-boot/docs/current/reference/html/appendix-dependency-versions.html ) 参照
    compile("org.springframework.boot:spring-boot-starter-web")
    compile("org.springframework.boot:spring-boot-starter-thymeleaf")
    compile("org.thymeleaf.extras:thymeleaf-extras-springsecurity3")
    compile("org.springframework.boot:spring-boot-starter-data-jpa")
    compile("org.springframework.boot:spring-boot-starter-velocity")
    compile("org.springframework.boot:spring-boot-starter-mail")
    compile("org.springframework.boot:spring-boot-starter-security")
    compile("org.springframework.boot:spring-boot-starter-redis")
    compile("org.springframework.boot:spring-boot-starter-amqp")
    compile("org.codehaus.janino:janino")
    compile("com.fasterxml.jackson.datatype:jackson-datatype-jsr310")
    compile("com.fasterxml.jackson.dataformat:jackson-dataformat-xml")
    testCompile("org.springframework.boot:spring-boot-starter-test")
    // (ここから) gradle でテストを実行した場合に spring-security-test-4.0.3.RELEASE.jar しか classpath に指定されず
    // テストが失敗したため、3.2.9.RELEASE を明記している
    testCompile("org.springframework.security:spring-security-core:3.2.9.RELEASE")
    testCompile("org.springframework.security:spring-security-web:3.2.9.RELEASE")
    // (ここまで) ------------------------------------------------------------------------------------------------------
    testCompile("org.springframework.security:spring-security-test:4.0.2.RELEASE")
    testCompile("org.yaml:snakeyaml")
    testCompile("org.codehaus.groovy:groovy-all")
    testCompile("org.spockframework:spock-core")
    testCompile("org.spockframework:spock-spring")

    // spring-boot-gradle-plugin によりバージョン番号が自動で設定されないもの
    compile("${jdbcDriver}")
    compile("org.seasar.doma:doma:2.6.0")
    compile("org.bgee.log4jdbc-log4j2:log4jdbc-log4j2-jdbc4.1:1.16")
    compile("org.apache.commons:commons-lang3:3.4")
    compile("org.projectlombok:lombok:1.16.6")
    compile("com.google.guava:guava:19.0")
    compile("org.springframework.session:spring-session:1.0.1.RELEASE")
    compile("org.simpleframework:simple-xml:2.7.1")
    compile("com.univocity:univocity-parsers:1.5.6")
    compile("org.thymeleaf.extras:thymeleaf-extras-java8time:2.1.0.RELEASE")
    testCompile("org.dbunit:dbunit:2.5.1")
    testCompile("com.icegreen:greenmail:1.4.1")
    testCompile("org.assertj:assertj-core:3.2.0")
    testCompile("com.jayway.jsonpath:json-path:2.1.0")
    testCompile("org.jmockit:jmockit:1.21")

    // for Doma-Gen
    domaGenRuntime("org.seasar.doma:doma-gen:2.6.0")
    domaGenRuntime("${jdbcDriver}")
}
  • dependencies タスクの以下のライブラリのバージョンを変更します。
    • compile("org.seasar.doma:doma:2.5.0")compile("org.seasar.doma:doma:2.6.0") へ変更します。
    • compile("com.google.guava:guava:18.0")compile("com.google.guava:guava:19.0") へ変更します。
    • testCompile("com.jayway.jsonpath:json-path:2.0.0")testCompile("com.jayway.jsonpath:json-path:2.1.0") へ変更しま す。
    • testCompile("org.jmockit:jmockit:1.19")testCompile("org.jmockit:jmockit:1.21") へ変更します。
    • domaGenRuntime("org.seasar.doma:doma-gen:2.5.0")domaGenRuntime("org.seasar.doma:doma-gen:2.6.0") へ変更します。

CustomModelResultMatchers.java

package ksbysample.common.test;

import org.hamcrest.Matcher;
import org.springframework.expression.EvaluationContext;
import org.springframework.expression.Expression;
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;
import org.springframework.test.web.servlet.ResultMatcher;
import org.springframework.test.web.servlet.result.ModelResultMatchers;
import org.springframework.ui.ModelMap;

import java.util.regex.Pattern;

import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.notNullValue;

public class CustomModelResultMatchers extends ModelResultMatchers {

    public static CustomModelResultMatchers modelEx() {
        return new CustomModelResultMatchers();
    }

    @SuppressWarnings("unchecked")
    public <T> ResultMatcher property(final String nameAndProperty, final Matcher<T> matcher) {
        return mvcResult -> {
            // <インスタンス名>.<プロパティ名> ( 例: page.number ) の形式の文字列を
            // インスタンス名とプロパティ名に分割する
            Pattern p = Pattern.compile("^(\\S+?)\\.(\\S+)$");
            java.util.regex.Matcher m = p.matcher(nameAndProperty);
            assertThat(m.find(), is(true));
            String name = m.group(1);
            String property = m.group(2);

            // プロパティの値を取得してチェックする
            ModelMap modelMap = mvcResult.getModelAndView().getModelMap();
            Object object = modelMap.get(name);
            assertThat(object, is(notNullValue()));
            EvaluationContext context = new StandardEvaluationContext(object);
            ExpressionParser parser = new SpelExpressionParser();
            Expression exp = parser.parseExpression(property);
            Object value = exp.getValue(context);
            assertThat((T) value, matcher);
        };
    }

}
  • import static org.springframework.test.util.MatcherAssertionErrors.assertThat;import static org.hamcrest.MatcherAssert.assertThat; に変更します。なぜ hamcrest の assertThat を使ってなかったのだろうとか、なぜ今頃になって警告が出るようになったのだろうとか、いろいろ疑問です。。。

TestDataLoader.java

package ksbysample.common.test;

import org.dbunit.database.DatabaseConnection;
import org.dbunit.database.IDatabaseConnection;
import org.dbunit.dataset.IDataSet;
import org.dbunit.dataset.ReplacementDataSet;
import org.dbunit.dataset.csv.CsvDataSet;
import org.dbunit.operation.DatabaseOperation;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import javax.sql.DataSource;
import java.io.File;

@Component
public class TestDataLoader {
    
    @Autowired
    private DataSource dataSource;

    public void load(String csvDir) {
        try {
            IDatabaseConnection conn = null;
            try {
                conn = new DatabaseConnection(dataSource.getConnection());

                IDataSet dataSet = new CsvDataSet(new File(csvDir));
                ReplacementDataSet replacementDataset = new ReplacementDataSet(dataSet);
                replacementDataset.addReplacementObject("[null]", null);
                DatabaseOperation.CLEAN_INSERT.execute(conn, replacementDataset);
            } finally {
                if (conn != null) conn.close();
            }
        }
        catch (Exception e) {
            e.printStackTrace();
        }
    }

}

TestDataResource.java

package ksbysample.common.test;

import org.dbunit.database.DatabaseConnection;
import org.dbunit.database.IDatabaseConnection;
import org.dbunit.database.QueryDataSet;
import org.dbunit.dataset.DefaultDataSet;
import org.dbunit.dataset.DefaultTable;
import org.dbunit.dataset.IDataSet;
import org.dbunit.dataset.ReplacementDataSet;
import org.dbunit.dataset.csv.CsvDataSet;
import org.dbunit.dataset.xml.FlatXmlDataSet;
import org.dbunit.dataset.xml.FlatXmlDataSetBuilder;
import org.dbunit.operation.DatabaseOperation;
import org.junit.rules.ExternalResource;
import org.junit.rules.TestWatcher;
import org.junit.runner.Description;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import javax.sql.DataSource;
import java.io.File;
import java.io.FileOutputStream;
import java.lang.annotation.Annotation;
import java.nio.file.Files;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;

@Component
public class TestDataResource extends TestWatcher {

    private final String TESTDATA_DIR = "src/test/resources/testdata/base";
    private final String BACKUP_FILE_NAME = "ksbylending_backup";
    private final List<String> BACKUP_TABLES = Arrays.asList(
            "user_info"
            , "user_role"
            , "library_forsearch"
            , "lending_app"
            , "lending_book"
    );

    @Autowired
    private DataSource dataSource;

    @Autowired
    private TestDataLoader testDataLoader;
    
    private File backupFile;
    
    @Override
    protected void starting(Description description) {
        IDatabaseConnection conn = null;
        try {
            try {
                conn = new DatabaseConnection(dataSource.getConnection());

                // バックアップを取得する
                QueryDataSet partialDataSet = new QueryDataSet(conn);
                for (String backupTable : BACKUP_TABLES) {
                    partialDataSet.addTable(backupTable);
                }
                ReplacementDataSet replacementDatasetBackup = new ReplacementDataSet(partialDataSet);
                replacementDatasetBackup.addReplacementObject("", "[null]");
                backupFile = File.createTempFile(BACKUP_FILE_NAME, "xml");
                try (FileOutputStream fos = new FileOutputStream(backupFile)) {
                    FlatXmlDataSet.write(replacementDatasetBackup, fos);
                }

                // テストデータに入れ替える
                IDataSet dataSet = new CsvDataSet(new File(TESTDATA_DIR));
                ReplacementDataSet replacementDataset = new ReplacementDataSet(dataSet);
                replacementDataset.addReplacementObject("[null]", null);
                DatabaseOperation.CLEAN_INSERT.execute(conn, replacementDataset);

                // テストメソッドに @TestData アノテーションが付加されている場合には、
                // アノテーションで指定されたテストデータをロードする
                Collection<Annotation> annotationList = description.getAnnotations();
                for (Annotation annotation : annotationList) {
                    if (annotation instanceof TestData) {
                        TestData testData = (TestData)annotation;
                        testDataLoader.load(testData.value());
                    }
                }
            } finally {
                if (conn != null) conn.close();
            }
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    @Override
    protected void finished(Description description) {
        try {
            IDatabaseConnection conn = null;
            try {
                conn = new DatabaseConnection(dataSource.getConnection());

                // バックアップからリストアする
                if (backupFile != null) {
                    IDataSet dataSet = new FlatXmlDataSetBuilder().build(backupFile);
                    ReplacementDataSet replacementDatasetRestore = new ReplacementDataSet(dataSet);
                    replacementDatasetRestore.addReplacementObject("[null]", null);
                    DatabaseOperation.CLEAN_INSERT.execute(conn, replacementDatasetRestore);
                }
            } finally {
                if (backupFile != null) {
                    Files.delete(backupFile.toPath());
                    backupFile = null;
                }
                try {
                    if (conn != null) conn.close();
                } catch (Exception ignored) {
                }
            }
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

}
  • extends ExternalResourceextends TestWatcher へ変更します。
  • @Autowired private TestDataLoader testDataLoader; を追加します。
  • protected void before() throws Exception {protected void starting(Description description) { へ変更します。
  • starting メソッドに @TestData アノテーションで指定されたテストデータをロードする処理を追加します。
  • starting メソッド内の処理を try { ... } catch (Exception e) { throw new RuntimeException(e); } で囲みます。
  • protected void after() {protected void finished(Description description) { へ変更します。
  • finished メソッド内の e.printStackTrace();throw new RuntimeException(e); へ変更します。

履歴

2016/01/06
初版発行。