LoginSignup
35
41

More than 5 years have passed since last update.

Eclipse を使って JUnit5 を導入したときのメモ

Posted at

はじめに

JUnit5 がリリースされてから半年以上経過しましたが、やっと Eclipse で JUnit5 を使ってみたのでその時のメモです。

環境

  • JUnit
    • JUnit Platform: 1.2.0
    • JUnit Jupiter: 5.2.0
    • JUnit Vintage: 5.2.0
  • Java
    • 8
  • Eclipse
    • Pleiades All in One 4.7.3a

What is JUnit5?

JUnit5 = JUnit Platform + JUnit Jupiter + JUnit Vintage

本家にはこの式で記載されていますが、 JUnit5 のフレームワーク上で JUnit4 を実行しないのであれば JUnit Vintage の依存は不要です。
詳しくは後述する依存関係をご覧ください。

依存関係

JUnit5 関連すべての依存関係は 本家 に記載されています。

dependency-diagram

もう少し省略した図はこんな感じです。

component-diagram-summary.png

各コンポーネントの簡単な説明は下記の通りです。

コンポーネント 説明
junit-platform-launcher JVM 上でテストを実行するための基盤となるコンポーネント群
junit-jupiter-engine プラットフォーム上で JUnit5 ベースのテストを実行するためのコンポーネント群
junit-jupiter-api JUnit5 ベースのテストを書くためのコンポーネント群
junit-jupiter-params JUnit5 ベースのパラメータライズドなテストを書くためのコンポーネント群
junit-vintage-engine プラットフォーム上で JUnit3 または JUnit4 ベースのテストを実行するためのコンポーネント群

それぞれが推移的に依存しているため、特にバージョンを気にしなければ下記の依存関係だけで JUnit5 ベースのテストを実行できます。

// http://mvnrepository.com/artifact/org.junit.jupiter/junit-jupiter-engine
dependency group: 'org.junit.jupiter', name: 'junit-jupiter-engine', version: "5.2.0"
// https://mvnrepository.com/artifact/org.junit.platform/junit-platform-launcher
dependency group: 'org.junit.platform', name: 'junit-platform-launcher', version: "1.2.0"

ただし、依存するバージョンが必ずしも統一されていない可能性もあるので gradlew dependencies などで確認することをお勧めします。

Gradle

依存するすべてのコンポーネントを指定した場合の build.gradle はこんな感じです。

build.gradle
buildscript {
  ext {
    junit5MinorVersion = '2.0'
  }
}
dependencyManagement {
  dependencies {
    // http://mvnrepository.com/artifact/org.junit.jupiter/junit-jupiter-engine
    dependency group: 'org.junit.jupiter', name: 'junit-jupiter-engine', version: "5.${junit5MinorVersion}"
    // http://mvnrepository.com/artifact/org.junit.jupiter/junit-jupiter-params
    dependency group: 'org.junit.jupiter', name: 'junit-jupiter-params', version: "5.${junit5MinorVersion}"
    // https://mvnrepository.com/artifact/org.junit.jupiter/junit-jupiter-api
    dependency group: 'org.junit.jupiter', name: 'junit-jupiter-api', version: "5.${junit5MinorVersion}"
    // https://mvnrepository.com/artifact/org.junit.vintage/junit-vintage-engine
    dependency group: 'org.junit.vintage', name: 'junit-vintage-engine', version: "5.${junit5MinorVersion}"
    // https://mvnrepository.com/artifact/org.junit.platform/junit-platform-launcher
    dependency group: 'org.junit.platform', name: 'junit-platform-launcher', version: "1.${junit5MinorVersion}"
  }
}
dependencies {
  testCompile group: 'org.junit.jupiter', name: 'junit-jupiter-engine'
  testCompile group: 'org.junit.jupiter', name: 'junit-jupiter-params'
  testCompile group: 'org.junit.jupiter', name: 'junit-jupiter-api'
  testCompile group: 'org.junit.vintage', name: 'junit-vintage-engine'
  testCompile group: 'org.junit.platform', name: 'junit-platform-launcher'
}

JUnit5 のアノテーション

JUnit4 からの主なアノテーション

主要なアノテーションの変更はこちら。
JUnit5 のよく使うアノテーション一覧は こちら

Annotation Junit5 Junit4 備考
@Test org.junit.jupiter.api.Test org.junit.Test
@BeforeEach org.junit.jupiter.api.BeforeEach org.junit.Before 各テストの開始前に実行
@AfterEach org.junit.jupiter.api.AfterEach org.junit.After 各テストの終了後に実行
@BeforeAll org.junit.jupiter.api.BeforeAll org.junit.BeforeClass 全テストの開始前に 1 度だけ実行
@AfterAll org.junit.jupiter.api.AfterAll org.junit.AfterClass 全テストの終了後に 1 度だけ実行
@Disabled org.junit.jupiter.api.Disabled org.junit.Ignore テストの無効化。クラス名にもメソッド名にも使用可能

その他、便利そうなアノテーション

@DisplayName

  • org.junit.jupiter.api.DisplayName
  • 絵文字でも顔文字でも使えます
  • だけど HTML でレポート出力したときに文字化けしたし、実際仕事で絵文字を使うことは無さそう?
  • テストクラスやテストメソッド名に直接日本語を使うことが許されないプロジェクトでは「待ってました!」機能
  • メソッド名にもクラス名にも使えます

@Tag

  • org.junit.jupiter.api.Tag
  • JUnit4 時代なら org.junit.experimental.categories.Category
  • ビルドツールなどからの実行時にフィルタリングが可能
  • クラスに付与したタグはメソッドやネストしたクラスに継承される
  • 下記例であれば @Tag("fast") が付いたテストクラス・及びメソッドと、 @Tag("slow")@Tag("feature") 両方が付いたテストクラス・及びメソッドが実行対象となる
build.gradle
...
test {
  useJUnitPlatform {
    includeTags 'fast', 'slow & feature'
    includeEngines 'junit-jupiter'
  }
}
...

@EnabledOnOs / @DisabledOnOs

  • 実行中の OS に応じてテストの実行を制御可能
@EnabledOnOs(WINDOWS) // Windows 環境でしか実行されない
@DisabledOnOs(WINDOWS) // Windows 環境では実行されない

@RepeatedTest

  • 対象テストメソッドを指定回数繰り返す
  • {displayName}, {currentRepetition}, {totalRepetitions} を使うことで表示内容を変更可能
    • デフォルトは repetition 1 of 10 のように出力される

@EnabledIf / @DisabledIf

  • スクリプトベースで実行制御が可能
  • JavaScript や Groovy などで記述可能

その他、実験的な API 一覧

詳しくは下記参照。
http://junit.org/junit5/docs/current/user-guide/#api-evolution-experimental-apis

Standard Test Class

ここまでの話を踏まえた基本形のテストクラスを示します。

StandardJUnit5Test.java
package learning.junit5;

import static org.assertj.core.api.Assertions.*;
import static org.junit.jupiter.api.condition.OS.*;

import java.util.Map;

import org.assertj.core.util.Maps;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.RepeatedTest;
import org.junit.jupiter.api.RepetitionInfo;
import org.junit.jupiter.api.Tag;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.TestInfo;
import org.junit.jupiter.api.TestReporter;
import org.junit.jupiter.api.condition.DisabledOnOs;
import org.junit.jupiter.api.condition.EnabledOnOs;

// class が public 宣言である必要がなくなりました!!
@Tag("base")
class StandardJUnit5Test {

    StandardJUnit5Test() {
        // do something...
    }

    // 事前処理・事後処理のメソッドも public 宣言なくても OK
    @BeforeAll
    static void beforeAll() throws Exception {
        // 全テストの開始前に 1 度だけ実行
    }

    @AfterAll
    static void afterAll() throws Exception {
        // 全テストの終了後に 1 度だけ実行
    }

    @BeforeEach
    void before() throws Exception {
        // 各テストの開始前に実行
    }

    @AfterEach
    void after() throws Exception {
        // 各テストの終了後に実行
    }

    // もちろんテストメソッドも public 宣言なしでも OK
    @Test
    @DisplayName("2 * 3 = 6 であること😆")
    void test() throws Exception {
        assertThat(2 * 3).isEqualTo(6);
    }

    @Disabled
    @Test
    @DisplayName("無効化されたテスト😑")
    void disabledTest() throws Exception {
        fail("実行されないはず!!");
    }

    @Test
    @EnabledOnOs(WINDOWS)
    void enabledOnWindows() throws Exception {
        // Windows 環境でしか実行されない
    }

    @Test
    @DisabledOnOs(WINDOWS)
    void disabledOnWindows() throws Exception {
        // Windows 環境では実行されない
    }

    @DisplayName("(」・ω・)」ウー")
    @Nested
    class InnerTest {

        @Test
        @DisplayName("(/・ω・)/ニャー")
        void test() throws Exception {
            assertThat("InnerTest").startsWith("Inner").endsWith("Test").hasSize(9);
        }
    }
}

※JUnit5 から追加された検証用メソッドはいくつかありますが、そこは他の記事にお任せします。
※個人的に AssertJ 推しなのでここは割愛させていただいます🙇🙇🙇

JUnit5 で出来るようになった点

コンストラクタとテストメソッドに引数を受け取れるようになった

org.junit.jupiter.api.extension.ParameterResolver は実行時に動的にパラメータをバインドさせるための API を定義。
junit-jupiter-engine に組み込まれている Resolver は下記 3 つ。

  • org.junit.jupiter.engine.extension.TestInfoParameterResolver
    • 引数に使用できるのは org.junit.jupiter.api.TestInfo
    • テストのメタ情報を取得可能(メソッド情報、Tag など)
@Tag("TestInfo")
@Test
void receivedTestInfo(TestInfo info) throws Exception {
    assertThat(info.getTestMethod().get().getName()).isEqualTo("receivedTestInfo");
    assertThat(info.getTestClass().get()).isEqualTo(StandardJUnit5Test.class);
    assertThat(info.getTags()).containsOnly("TestInfo", "base");
    assertThat(info.getDisplayName()).isEqualTo("receivedTestInfo(TestInfo)");
}
  • org.junit.jupiter.engine.extension.TestReporterParameterResolver
    • 引数に使用できるのは org.junit.jupiter.api.TestReporter
    • publishEntry で設定した内容が stdout or stderr に出力される
@Test
void receivedTestReporter(TestReporter reporter) throws Exception {
    reporter.publishEntry("名前", "テストタロウ");
    Map<String, String> map = Maps.newHashMap("Foo", "Bar");
    reporter.publishEntry(map);
}

// 標準出力に下記が出力される
TestIdentifier [receivedTestReporter(TestReporter)]
ReportEntry [timestamp = 2018-06-30T11:54:45.075, 名前 = 'テストタロウ']
TestIdentifier [receivedTestReporter(TestReporter)]
ReportEntry [timestamp = 2018-06-30T11:54:45.080, Foo = 'Bar']
  • org.junit.jupiter.engine.extension.RepetitionInfoParameterResolver
    • 引数に使用できるのは org.junit.jupiter.api.RepetitionInfo
    • @RepeatedTest 使用時にのみ現在の繰り返し回数や合計回数を取得可能
    • @RepeatedTest 以外で使用すると実行時例外が発生します
@RepeatedTest(6)
void receivedRepetitionInfo(RepetitionInfo repetition) throws Exception {
    assertThat(repetition.getCurrentRepetition()).isBetween(1, 6);
    assertThat(repetition.getTotalRepetitions()).isEqualTo(6);
}

Extension Model

  • JUnit4 時代の Runner, @Rule, @ClassRule の競合する拡張ポイントが Extension API に統一(といっても Extension 自体はマーカーインターフェースに過ぎない)
  • JUnit5 では @ExtendWith で指定。Java のサービスローダー指定も可能
  • @ExtendWith の指定はメソッド、クラスどちらでも OK
  • 複数指定することも可能
  • テスト実行前後などに Intercept することも可能(Callback)

SpringFramework 5.x から JUnit5 をサポート

くわしくはこちらの記事をご確認ください。
Spring Framework 5.0 Test関連の主な変更点

JUnit5 で出来なくなった点

  • QuickJUnit プラグインの Ctrl + 0 が反応しなくなった
    • JUnit4 系の @Test を対象としているからかな🤔
    • Eclipse キーバインド設定を変更すれば何とか対応可能😇

おわりに

JUnit5 にはこの他にも Parameterized TestsDynamic Tests など、新たに追加された機能もありますが、それらについては(きっと)追々。。。

触ってみた感じ、JUnit4 を既に使っているプロジェクトで、既存のテストを置き換えてまで JUnit5 を使う理由はそこまで多く感じませんでした。
あと、もちろんですが 1 つのプロダクトの中で JUnit4 と JUnit5 が混在するのはあまり得策ではないかと。
後から見た人が「JUnit4 と JUnit5 のどちらで書くのが正解なのか😢?」と混乱させてしまうので。

今あるプロダクトで既に JUnit4 のテストコードが存在するなら JUnit4 を踏襲したほうが良いと思います。
テストコードがそもそも存在しない(レガシーな)プロダクトで、これからテストコードを追加しようと考えているなら JUnit5 を導入するのが良いかなー🤔

と、ここまで書いたけどあんまり Eclipse は関係ない内容になってしもうた。
でも Eclipse 4.7.2 以前だと Eclipse 上で JUnit5 が実行できなかったので、それが改善されてまぁよかった。

Enjoy JUnit5 Life😘!!

35
41
2

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
35
41