Spring Boot 2.0.x の Web アプリを 2.1.x へバージョンアップする ( その8 )( JUnit 5 へバージョンアップ。。。したいが @RunWith(Enclosed) + @Unroll + useJUnitPlatform() を組み合わせるとテストが終わらない )
概要
記事一覧はこちらです。
- 今回の手順で確認できるのは以下の内容です。
- @SpringBootTest アノテーションを付与すると JUnit 5 のアノテーションである @ExtendWith(SpringExtension.class) が自動で付与されるのですが、`[compileJava, compileTestGroovy, compileTestJava].options.compilerArgs = ["-Xlint:all,..."]' を指定していると JUnit 5 のモジュールが依存関係にないために警告が出るので、これを機会に JUnit 5 へバージョンアップします。。。と思いましたが、Spock と組み合わせた時にテストが終わらない時があるという内容です。
参照したサイト・書籍
Spring Boot Reference Guide - 50.6 Testing
https://docs.spring.io/spring-boot/docs/2.1.3.RELEASE/reference/htmlsingle/#boot-features-kotlin-testingJUnit 5 User Guide
https://junit.org/junit5/docs/current/user-guide/What’s New in JUnit 5.4
https://medium.com/@BillyKorando/whats-new-in-junit-5-4-7ce6870a9caaUsing JUnit 5 in IntelliJ IDEA
https://blog.jetbrains.com/idea/2016/08/using-junit-5-in-intellij-idea/IntelliJ IDEA 2017.3: JUnit support
https://blog.jetbrains.com/idea/2017/11/intellij-idea-2017-3-junit-support/Is Spock still needed in the time of JUnit 5?
https://speakerdeck.com/szpak/is-spock-still-needed-in-the-time-of-junit-5Gradle Goodness: Running a Single Test
http://mrhaki.blogspot.com/2013/05/gradle-goodness-running-single-test.html
目次
手順
方針
今回は以下の方針で進めます。
- JUnit 5 のモジュールを依存関係に追加するが、JUnit 4 のモジュールも依存関係に追加したままにする(削除はしない)。
- JUnit 4 で書かれたテストは現状維持。まだ JUnit 5 では書き直さない。
org.junit.vintage:junit-vintage-engine
を依存関係に追加すれば JUnit 5 の環境上でも JUnit 4 のテストを実行できるらしいので、org.junit.vintage:junit-vintage-engine
を依存関係に追加してからテストを実行して成功するのかを確認してみる。- ここまで問題が出なければ、JUnit 4 で書いたテストを JUnit 5 の書き方に変更する。
build.gradle を変更して JUnit 5 のモジュールを依存関係に追加する
build.gradle を以下のように変更します。
dependencyManagement { imports { // mavenBom は以下の URL のものを使用する // https://repo.spring.io/release/org/springframework/boot/spring-boot-starter-parent/2.1.2.RELEASE/ // bomProperty に指定可能な property は以下の URL の BOM に記述がある // https://repo.spring.io/release/org/springframework/boot/spring-boot-dependencies/2.1.2.RELEASE/spring-boot-dependencies-2.1.2.RELEASE.pom mavenBom(org.springframework.boot.gradle.plugin.SpringBootPlugin.BOM_COORDINATES) { // Spring Boot の BOM に定義されているバージョンから変更する場合には、ここに以下のように記述する // bomProperty "thymeleaf.version", "3.0.9.RELEASE" } mavenBom("org.junit:junit-bom:5.4.0") } } dependencies { .......... // for JUnit 5 // junit-jupiter で junit-jupiter-api, junit-jupiter-params, junit-jupiter-engine の3つが依存関係に追加される testCompile("org.junit.jupiter:junit-jupiter") testRuntime("org.junit.platform:junit-platform-launcher") testRuntime("org.junit.vintage:junit-vintage-engine") // for Error Prone ( http://errorprone.info/ ) .......... } .......... test { // -ea -Dspring.profiles.active=unittest -Dfile.encoding=UTF-8 -Dsun.nio.cs.map=x-windows-iso2022jp/ISO-2022-JP jvmArgs = [ "-ea", "-Dspring.profiles.active=unittest", "-Dfile.encoding=UTF-8", "-Dsun.nio.cs.map=x-windows-iso2022jp/ISO-2022-JP" ] // for JUnit 5 useJUnitPlatform() }
- JUnit 5 では BOM が提供されていますので、dependencyManagement block に
mavenBom("org.junit:junit-bom:5.4.0")
を追加します。Spring Boot の BOM に JUnit 5 のライブラリが記述されているので BOM を追加しなくてもいいのですが、最新バージョンを使用するよう設定したいので別に記述することにします。 - dependencies block に以下の行を追加します。
- test タスクに
useJUnitPlatform()
を追加します。
Gradle Tool Window の左上にある「Refresh all Gradle projects」ボタンをクリックして更新します。
動作確認する。。。が、いつまで経っても終わらない。。。
clean タスク実行 → Rebuild Project 実行 → build タスクを実行してみます。。。が、test タスクが 20分経過しても終わりません。
何が起きているのか確認するために gradlew build --debug
を実行してみると、ksbysample.webapp.lending.SampleHelperTest のテストが STARTED
が出た後に終了していませんでした。org.gradle.process.internal.health.memory.MemoryManager と org.gradle.cache.internal.DefaultFileLockManager のログが延々と出続けていました。
原因を調べる
ksbysample.webapp.lending.SampleHelperTest は @RunWith(Enclosed)
アノテーションを付与したクラスの中に Spock のテストクラスと PowerMock を使用した JUnit 4 のテストクラスがあるのですが、コメントアウトしたりして調べたところ、
@RunWith(Enclosed)
アノテーションが付与されたクラスの中に Spock のテストクラスを作成している。- Spock のテストクラスで
@Unroll
アノテーションを付与したテストメソッドを作成している。 org.junit.vintage:junit-vintage-engine
を依存関係に追加して test タスクにuseJUnitPlatform()
を記述し、JUnit 5 の環境下で Spock のテストクラスを実行している。
という条件が揃うとテストが終わらないという原因でした。
demo プロジェクトを作り直して確認してみます。build.gradle を JUnit 5 を使うようにします。
buildscript { ext { group "com.example" version "1.0.0-RELEASE" } repositories { mavenCentral() maven { url "https://repo.spring.io/release/" } maven { url "https://plugins.gradle.org/m2/" } } } plugins { id "java" id "eclipse" id "idea" id "org.springframework.boot" version "2.1.3.RELEASE" id "io.spring.dependency-management" version "1.0.6.RELEASE" id "groovy" } sourceCompatibility = 1.8 targetCompatibility = 1.8 [compileJava, compileTestGroovy, compileTestJava]*.options*.encoding = "UTF-8" [compileJava, compileTestGroovy, compileTestJava]*.options*.compilerArgs = [ "-Xlint:all,-options,-processing,-path" ] idea { module { inheritOutputDirs = false outputDir = file("$buildDir/classes/main/") } } repositories { mavenCentral() } dependencyManagement { imports { mavenBom(org.springframework.boot.gradle.plugin.SpringBootPlugin.BOM_COORDINATES) mavenBom("org.junit:junit-bom:5.4.0") } } dependencies { def spockVersion = "1.2-groovy-2.5" implementation("org.springframework.boot:spring-boot-starter-web") runtimeOnly("org.springframework.boot:spring-boot-devtools") implementation("org.apache.commons:commons-lang3") testImplementation("org.springframework.boot:spring-boot-starter-test") testImplementation("org.spockframework:spock-core:${spockVersion}") testImplementation("org.spockframework:spock-spring:${spockVersion}") // for JUnit 5 testCompile("org.junit.jupiter:junit-jupiter") testRuntime("org.junit.platform:junit-platform-launcher") testRuntime("org.junit.vintage:junit-vintage-engine") } test { // for JUnit 5 useJUnitPlatform() }
プロジェクトは以下の構成にします。
src/main/java/com/example/demo/SampleUtils.java は String 型の引数2つを受け取り結合して返す join メソッドを実装し、
package com.example.demo; import org.apache.commons.lang3.StringUtils; public class SampleUtils { public static String join(String a, String b) { return StringUtils.join(a, b); } }
そのテストクラスとして src/test/groovy/com/example/demo/SampleUtilsTest.groovy を @RunWith(Enclosed)
、@Unroll
アノテーションを使用して実装します。
package com.example.demo import org.junit.experimental.runners.Enclosed import org.junit.runner.RunWith import spock.lang.Specification import spock.lang.Unroll @RunWith(Enclosed) class SampleUtilsTest { static class 正常処理のテスト extends Specification { @Unroll def "xxx"() { expect: SampleUtils.join(a, b) == result where: a | b || result "a" | "b" || "ab" } } }
これで build タスクを実行すると test タスクは先程と同様に終わりません。
src/test/groovy/com/example/demo/SampleUtilsTest.groovy を @Unroll
を使用しないように変更すると test タスクは終わります。
package com.example.demo import org.junit.experimental.runners.Enclosed import org.junit.runner.RunWith import spock.lang.Specification @RunWith(Enclosed) class SampleUtilsTest { static class 正常処理のテスト extends Specification { def "xxx"() { expect: SampleUtils.join("a", "b") == "ab" } } }
src/test/groovy/com/example/demo/SampleUtilsTest.groovy を @RunWith(Enclosed)
を使用しないように変更しても test タスクは終わります。
package com.example.demo import spock.lang.Specification import spock.lang.Unroll class SampleUtilsTest extends Specification { @Unroll def "xxx"() { expect: SampleUtils.join(a, b) == result where: a | b || result "a" | "b" || "ab" } }
src/test/groovy/com/example/demo/SampleUtilsTest.groovy を @RunWith(Enclosed)
、@Unroll
の両方を使う実装に戻してから、build.gradle の test タスクから useJUnitPlatform()
を削除しても test タスクは終わります。
test { // for JUnit 5 // useJUnitPlatform() }
続く。。。
@RunWith(Enclosed)
と @Unroll
を組み合わせる方法はよく使うので使えるようにしておきたいところですが useJUnitPlatform()
を書くとテストが終了せず、かと言って useJUnitPlatform()
を書かないと今度は JUnit 5 でテストを書いても実行できません。テストを Spock だけで書いてもいいのですが JUnit 5 でも書ける環境にしたいところです。Web で調べても解決策と思われる方法が見当たりません。
時間がかかったので、ここで一旦区切ります。たぶん次は解決編!だといいなあ。。。
履歴
2019/02/26
初版発行。