かんがるーさんの日記

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

Spring Boot 2.0.x の Web アプリを 2.1.x へバージョンアップする ( その8 )( JUnit 5 へバージョンアップ。。。したいが @RunWith(Enclosed) + @Unroll + useJUnitPlatform() を組み合わせるとテストが終わらない )

概要

記事一覧はこちらです。

Spring Boot 2.0.x の Web アプリを 2.1.x へバージョンアップする ( その7 )( Gradle を 5.2 → 5.2.1 へ、Spring Boot を 2.1.2 → 2.1.3 へバージョンアップする ) の続きです。

  • 今回の手順で確認できるのは以下の内容です。
    • @SpringBootTest アノテーションを付与すると JUnit 5 のアノテーションである @ExtendWith(SpringExtension.class) が自動で付与されるのですが、`[compileJava, compileTestGroovy, compileTestJava].options.compilerArgs = ["-Xlint:all,..."]' を指定していると JUnit 5 のモジュールが依存関係にないために警告が出るので、これを機会に JUnit 5 へバージョンアップします。。。と思いましたが、Spock と組み合わせた時にテストが終わらない時があるという内容です。

参照したサイト・書籍

  1. Spring Boot Reference Guide - 50.6 Testing
    https://docs.spring.io/spring-boot/docs/2.1.3.RELEASE/reference/htmlsingle/#boot-features-kotlin-testing

  2. JUnit 5
    https://junit.org/junit5/

  3. JUnit 5 User Guide
    https://junit.org/junit5/docs/current/user-guide/

  4. What’s New in JUnit 5.4
    https://medium.com/@BillyKorando/whats-new-in-junit-5-4-7ce6870a9caa

  5. Using JUnit 5 in IntelliJ IDEA
    https://blog.jetbrains.com/idea/2016/08/using-junit-5-in-intellij-idea/

  6. IntelliJ IDEA 2017.3: JUnit support
    https://blog.jetbrains.com/idea/2017/11/intellij-idea-2017-3-junit-support/

  7. Is Spock still needed in the time of JUnit 5?
    https://speakerdeck.com/szpak/is-spock-still-needed-in-the-time-of-junit-5

  8. Gradle Goodness: Running a Single Test
    http://mrhaki.blogspot.com/2013/05/gradle-goodness-running-single-test.html

目次

  1. 方針
  2. build.gradle を変更して JUnit 5 のモジュールを依存関係に追加する
  3. 動作確認する。。。が、いつまで経っても終わらない。。。
  4. 原因を調べる
  5. 続く。。。

手順

方針

今回は以下の方針で進めます。

  • 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 に以下の行を追加します。
    • testCompile("org.junit.jupiter:junit-jupiter")
    • testRuntime("org.junit.platform:junit-platform-launcher")
    • testRuntime("org.junit.vintage:junit-vintage-engine")
      • JUnit 5 の環境で JUnit 4 のテストを実行する時に必要なライブラリです。JUnit 4 のテストを実行しないなら不要です。
  • test タスクに useJUnitPlatform() を追加します。

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

動作確認する。。。が、いつまで経っても終わらない。。。

clean タスク実行 → Rebuild Project 実行 → build タスクを実行してみます。。。が、test タスクが 20分経過しても終わりません。

f:id:ksby:20190225004511p:plain

何が起きているのか確認するために gradlew build --debug を実行してみると、ksbysample.webapp.lending.SampleHelperTest のテストが STARTED が出た後に終了していませんでした。org.gradle.process.internal.health.memory.MemoryManager と org.gradle.cache.internal.DefaultFileLockManager のログが延々と出続けていました。

f:id:ksby:20190225011051p:plain

原因を調べる

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()
}

プロジェクトは以下の構成にします。

f:id:ksby:20190226005752p:plain

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 タスクは先程と同様に終わりません。

f:id:ksby:20190226010827p:plain

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"
        }

    }

}

f:id:ksby:20190226011333p:plain

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"
    }

}

f:id:ksby:20190226012014p:plain

src/test/groovy/com/example/demo/SampleUtilsTest.groovy を @RunWith(Enclosed)@Unroll の両方を使う実装に戻してから、build.gradle の test タスクから useJUnitPlatform() を削除しても test タスクは終わります。

test {
    // for JUnit 5
    // useJUnitPlatform()
}

f:id:ksby:20190226012649p:plain

続く。。。

@RunWith(Enclosed)@Unroll を組み合わせる方法はよく使うので使えるようにしておきたいところですが useJUnitPlatform() を書くとテストが終了せず、かと言って useJUnitPlatform() を書かないと今度は JUnit 5 でテストを書いても実行できません。テストを Spock だけで書いてもいいのですが JUnit 5 でも書ける環境にしたいところです。Web で調べても解決策と思われる方法が見当たりません。

時間がかかったので、ここで一旦区切ります。たぶん次は解決編!だといいなあ。。。

履歴

2019/02/26
初版発行。