LoginSignup
16
20

More than 5 years have passed since last update.

拙訳 JUnit 5 User Guide

Last updated at Posted at 2016-12-22

この記事はJava Advent Calendar 2016の 22 日目です。昨日はcactaceaeさんの「MySQL on Fusion-IOのパフォーマンスを引き出すバッチパターン」でした。

タイトルの通り、JUnit 5 Users Guideの拙訳です。
最初はJUnit 5を使って何か面白い記事が書けないかと考えていましたが、なかなか記事を書く時間を取れなかったため、ユーザガイドの中途半端な翻訳で場を凌ぐことにしました。始めの3章しか書けていませんが、近いうちに最後まで仕上げたいと思います。
所々「こう書いた方が読み易いかなー?」と考えながら訳していったため、原文とは対応が取れていないところがあります。ご容赦ください。
また、「これは英語のままの方がわかり易いかも…?」と思いながらも日本語に無理やり翻訳した部分が多々ありますので、改善のアドバイスなどいただけると幸いです。

1. 概要

このドキュメントの目的は、以下のようなターゲットに対して包括的なリファレンスを提供することです。

  • テストを記述するプログラマ
  • エクステンションの作者
  • エンジンの作成者(e.g. ビルドツールやIDEのベンダ)

1.1. JUnit 5とは

以前のバージョンのJUnitとは異なり、JUnit 5は3つのサブプロジェクト内のモジュールにより構成されています。

JUnit 5 = JUnit Platform + JUnit Jupiter + JUnit Vintage

JUnit PlatformはJVM上でテスティングフレームワークを起動するための基礎を提供します。また同様に、このプラットフォーム上で実行されるテスティングフレームワークの開発のためのTestEngineAPIを定義します。さらに、コマンドラインからプラットフォームを起動し、GradleとMavenのプラグインを構築するConsoleLauncherと、プラットフォーム上のTestEngineを実行するためのJUnit 4ベースのRunnerを提供します。

JUnit Jupiterには、JUnit 5においてテストやエクステンションを記述するための新しいプログラミングモデルおよびエクステンションモデルが含まれています。このJupiterサブプロジェクトは、プラットフォーム上でJupiterにより記述されたテストを実行するためのTestEngineを提供します。

JUnit Vintageはプラットフォーム上でJUnit 3およびJUnit 4により記述されたテストを実行するためのTestEngineを提供します。

1.2. サポートされるJavaのバージョン

JUnit 5の利用には、Java 8のランタイムが必要です。しかし、以前のバージョンのJDKでコンパイルしたテストコードもそのまま利用することができます。

2. インストール

最終リリースおよびマイルストーンは、Maven Centralにデプロイされています。
スナップショットは、Sonatypeのスナップショットリポジトリ/org/junit配下にデプロイされています。

2.1. Dependencyメタデータ

2.1.1. JUnit Platform

  • Group ID: org.junit.platform
  • Version: 1.0.0-M3
  • Artifact IDs:
    • junit-platform-commons
    • junit-platform-console
    • junit-platform-engine
    • junit-platform-gradle-plugin
    • junit-platform-launcher
    • junit-platform-runner
    • junit-platform-surefire-provider

2.1.2. JUnit Jupiter

  • Group ID: org.junit.jupiter
  • Version: 5.0.0-M3
  • Artifact IDs:
    • junit-jupiter-api
    • junit-jupiter-engine

2.1.3. JUnit

  • Group ID: org.junit.vintage
  • Version: 4.12.0-M3
  • Artifact ID: junit-vintage-engine

2.2. JUnit Jupiterのサンプルプロジェクト

junit5-samplesリポジトリには、JUnit JupiterおよびJUnit Vintageによるサンプルプロジェクトがホストされています。build.gradleおよびpom.xmlはそれぞれ以下のプロジェクトにあります。

3. テストを記述する(Writing Tests)

最初のテストケース
import static org.junit.jupiter.api.Assertions.assertEquals;

import org.junit.jupiter.api.Test;

class FirstJUnit5Tests {

    @Test
    void myFirstTest() {
        assertEquals(2, 1 + 1);
    }

}

3.1. アノテーション

JUnit Jupiterでは、テストの設定やフレームワークの拡張に以下のアノテーションの利用できます。
すべての主要なアノテーションはjunit-jupiter-apiモジュールのorg.junit.jupiter.apiパッケージ内にあります。

Annotation Description
@Test テストメソッドであることを示します。JUnit 4の@Testアノテーションと異なり、このアノテーションは属性を持ちません。JUnit Jupiterでのテスト拡張以降、それらに対しては専用のアノテーションがあります。
@TestFactory 動的テストのファクトリメソッドであることを示します。
@DisplayName テストクラス、テストメソッドのためのカスタム表示名(custom display name)を宣言します。
@BeforeEach クラス内の各@Testメソッドの実行前に実行されるべきメソッドであることを示します。JUnit 4の@Beforeを引き継いでいます。
@AfterEach クラス内の各@Testメソッドの実行後に実行されるべきメソッドであることを示します。JUnit 4の@Afterを引き継いでいます。
@BeforeAll クラス内のすべての@Testメソッドの実行前に実行されるべきメソッドであることを示します。このメソッドはstaticでなければなりません。JUnit 4の@BeforeClassを引き継いでいます。
@AfterAll クラス内のすべての@Testメソッドの実行後に実行されるべきメソッドであることを示します。このメソッドはstaticでなければなりません。JUnit 4の@AfterClassを引き継いでいます。
@Nested ネストされた非staticなテストクラスであることを示します。Java言語の制約のため、@BeforeAllメソッドと@AfterAllメソッドは@Nestedテストクラスでは使用できません。
@Tag テストのフィルタリングをするためのタグの宣言に利用します。メソッド、クラスの両方に利用できます。TestNGのtest groupや、JUnit 4のCategoriesに類似しています。
@Disabled テストクラスやテストメソッドが動作しないようにするために利用します。JUnit 4の@Ignoreと似ています。
@ExtendWith カスタム拡張(custom extension)を登録するために利用します。

3.2. 標準的なテストクラス

標準的なテストケース
import static org.junit.jupiter.api.Assertions.fail;

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.Test;

class StandardTests {

    @BeforeAll
    static void initAll() {
    }

    @BeforeEach
    void init() {
    }

    @Test
    void succeedingTest() {
    }

    @Test
    void failingTest() {
        fail("a failing test");
    }

    @Test
    @Disabled("for demonstration purposes")
    void skippedTest() {
        // not executed
    }

    @AfterEach
    void tearDown() {
    }

    @AfterAll
    static void tearDownAll() {
    }

}

Note: テストクラス、テストメソッドはpublicである必要はありません。

3.3 表示名(Display Names)

テストクラスおよびテストメソッドは、Runnerやレポートで表示するためのカスタム表示名を宣言することができます。カスタム表示名には、空白文字(space)、特殊文字、絵文字を利用することができます。

カスタム表示名サンプル
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;

// カスタム表示名に空白文字を利用できることを示すために英語のままにしています。
@DisplayName("A special test case")
class DisplayNameDemo {

    @Test
    @DisplayName("Custom test name containing spaces")
    void testWithDisplayNameContainingSpaces() {
    }

    @Test
    @DisplayName("╯°□°)╯")
    void testWithDisplayNameContainingSpecialCharacters() {
    }

    @Test
    @DisplayName("😱")
    void testWithDisplayNameContainingEmoji() {
    }

}

3.4. アサーション(Assertions)

JUnit Jupiterには、JUnit 4の持つ多くのアサーションメソッドが付属しています。それに加えて、(Java SE 8以降で利用できる)ラムダ式を利用するために役立つものがいくつか追加されています。
すべてのJUnit Jupiterアサーションは、org.junit.jupiter.Assersionsクラスのstaticメソッドです。

アサーション利用例
import static org.junit.jupiter.api.Assertions.assertAll;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.junit.jupiter.api.Assertions.expectThrows;

import org.junit.jupiter.api.Test;

class AssertionsDemo {

    @Test
    void standardAssertions() {
        assertEquals(2, 2);
        assertEquals(4, 4, "この最後のパラメータは、オプションのアサーションメッセージです。");
        assertTrue(2 == 2, () -> "アサーションメッセージは遅延評価することができます。"
                + "−−−複雑なメッセージを無駄に構成することを回避するために。");
    }

    @Test
    void groupedAssertions() {
        // グループ化されたアサーションは、すべてのアサーションが実行され、
        // 失敗した場合は同時にレポートされる
        assertAll("address",
            () -> assertEquals("John", address.getFirstName()),
            () -> assertEquals("User", address.getLastName())
        );
    }

    @Test
    void exceptionTesting() {
        Throwable exception = expectThrows(IllegalArgumentException.class, () -> {
            throw new IllegalArgumentException("何かしらのメッセージ");
        });
        assertEquals("何かしらのメッセージ", exception.getMessage());
    }

}

3.5. アサンプション(Assumptions)

JUnit Jupiterには、JUnit 4が提供するassumptionメソッドのサブセットが付属しており、ラムダ式で使用するのに適しています。
すべてのJUnit Jupiterのassumptionは、org.junit.jupiter.Assumptionsクラスのstaticメソッドです。

アサンプションの利用例
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assumptions.assumeTrue;
import static org.junit.jupiter.api.Assumptions.assumingThat;

import org.junit.jupiter.api.Test;

public class AssumptionsDemo {

    @Test
    void testOnlyOnCiServer() {
        assumeTrue("CI".equals(System.getenv("ENV")));
        // 残りのテスト
    }

    @Test
    void testOnlyOnDeveloperWorkstation() {
        assumeTrue("DEV".equals(System.getenv("ENV")),
            () -> "Aborting test: 開発者のコンピュータではありません。");
        // 残りのテスト
    }

    @Test
    void testInAllEnvironments() {
        assumingThat("CI".equals(System.getenv("ENV")),
            () -> {
                // この中のアサーションは、CIサーバのみで実行される
                assertEquals(2, 2);
            });

        // この中のアサーションは、すべての環境で実行される
        assertEquals("a string", "a string");
    }

}

3.6. 動作しないテスト(Disabling Test)

以下は動作しないテストケースです。

実行されないテストクラス
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;

@Disabled
class DisabledClassDemo {
    @Test
    void testWillBeSkipped() {
    }
}

以下のテストケースは、動作しないテストメソッドを持ちます。

実行されないテストメソッドを持つテストクラス
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;

class DisabledTestsDemo {

    @Disabled
    @Test
    void testWillBeSkipped() {
    }

    @Test
    void testWillBeExecuted() {
    }
}

3.7. タグ付けおよびフィルタリング

テストクラスおよびテストメソッドはダグ付けすることができる。これらのタグは、テストの発見や実行のためのフィルタとして利用できる。

タグ付けの例
import org.junit.jupiter.api.Tag;
import org.junit.jupiter.api.Test;

// 複数のタグを付けることが可能
@Tag("fast")
@Tag("model")
class TaggingDemo {

    @Test
    @Tag("taxes")
    void testingTaxCalculation() {
    }

}

3.8. ネストされたテスト(Nested Tests)

ネストされたテストは、テスト作成者に対してテストグループ同士の関係のためのより高い表現能力を与える。
以下はちょっとした例(elaborate example)です。

スタックをテストするためのネストされたテストスイート
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;

import java.util.EmptyStackException;
import java.util.Stack;

import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;

@DisplayName("スタック")
class TestingAStackDemo {

    Stack<Object> stack;

    @Test
    @DisplayName("Stackのインスタンスを生成")
    void isInstantiatedWithNew() {
        new Stack<>();
    }

    @Nested
    @DisplayName("生成直後")
    class WhenNew {

        @BeforeEach
        void createNewStack() {
            stack = new Stack<>();
        }

        @Test
        @DisplayName("空")
        void isEmpty() {
            assertTrue(stack.isEmpty());
        }

        @Test
        @DisplayName("popを行うとEmptyStackExceptionをスローする")
        void throwsExceptionWhenPopped() {
            assertThrows(EmptyStackException.class, () -> stack.pop());
        }

        @Test
        @DisplayName("peekを行うとEmptyStackExceptionをスローする")
        void throwsExceptionWhenPeeked() {
            assertThrows(EmptyStackException.class, () -> stack.peek());
        }

        @Nested
        @DisplayName("1つの要素をpushした後")
        class AfterPushing {

            String anElement = "要素";

            @BeforeEach
            void pushAnElement() {
                stack.push(anElement);
            }

            @Test
            @DisplayName("もはや空ではない")
            void isEmpty() {
                assertFalse(stack.isEmpty());
            }

            @Test
            @DisplayName("popした要素を返すとスタックは空になる")
            void returnElementWhenPopped() {
                assertEquals(anElement, stack.pop());
                assertTrue(stack.isEmpty());
            }

            @Test
            @DisplayName("peekした要素を返してもスタックは空にならない")
            void returnElementWhenPeeked() {
                assertEquals(anElement, stack.peek());
                assertFalse(stack.isEmpty());
            }
        }
    }
}

Note: 内部クラス(非staticなネストされたクラス)のみ@Nestedクラスにすることができます。任意の深さでネストが可能で、それらの内部クラスはテストクラスファミリの@BeforeAllおよび@AfterAll以外のすべてのメンバを利用できます。(Javaにおいて内部クラスのstaticメンバは許されないため、@BeforeAllおよび@AfterAllは利用できません)

3.9. コンストラクタとメソッドのためのDI(Dependency Injection)

以前のバージョンのJUnitでは(少なくとも標準のRunnerの実装においては)、テストコンストラクタやテストメソッドはパラメータを持つことはできませんでした。JUnit Jupiterにおける大きな変更の一つとして、テストコンストラクタとテストメソッドの両方において、パラメータを持つことができるようになっています。これにより、より高い柔軟性を持ち、コンストラクタやメソッドに対してDependency Injectionが可能になります。

ParameterResolverでは、実行時に動的にパラメータを解決したいテスト拡張のためのAPIを定義している。
テストコンストラクタや@Test@TestFactory@BeforeEach@AfterEach@BeforeAll@AfterAllメソッドはパラメータを持つことができます。それらのパラメータは登録されたParameterResolverにより実行時に解決されなければなりません。

これらは現在の自動的に登録される2つの組み込みリゾルバです。

  • TestInfoParameterResolver: メソッドがTestInfo型のパラメータを持つ場合、TestInfoParameterResolverは現在のテストに関連付けられたTestInfoのインスタンスをパラメータの値として提供する。TestInfoはテストの表示名、テストクラス、テストメソッド、関連するタグなど、現在のテストに関する情報を確認する時に利用できる。表示名はテストクラスやテストメソッドの名前ようなコードに直接記述された名前であるか、もしくは@DisplayNameにより設定された名前です。

TestInfoはJUnit 4のTestNameルールの差し当りの代替として機能します。以下が利用例です。

TestInfoの利用例
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;

import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Tag;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.TestInfo;

class TestInfoDemo {

    @BeforeEach
    void init(TestInfo testInfo) {
        String displayName = testInfo.getDisplayName();
        assertTrue(displayName.equals("TEST 1") || displayName.equals("test2()"));
    }

    @Test
    @DisplayName("TEST 1")
    @Tag("my tag")
    void test1(TestInfo testInfo) {
        assertEquals("TEST 1", testInfo.getDisplayName());
        assertTrue(testInfo.getTags().contains("my tag"));
    }

    @Test
    void test2() {
    }

}
  • TestReporterParameterResolver: パラメータがTestReporter型の場合、TestReporterParameterResolverTestReporterのインスタンス提供します。このTestReporterは現行のテストに関するデータをレポートに追加することができます。このデータはTestExecuterListenerで使用されます。reportingEntryPublished()を呼び出すことによりIDEでの表示、レポートに含めることができます。

JUnit Jupiterでは、JUnit 4の標準出力や標準エラーに情報を出力する際にはTestReportを利用するべきです。@RunWith(JUnitPlatform.class)を使用すると、すべてのレポートのエントリが標準出力にい出力されます。

TestReportの利用例
import java.util.HashMap;

import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.TestReporter;

class TestReporterDemo {

    @Test
    void reportSingleValue(TestReporter testReporter) {
        testReporter.publishEntry("a key", "a value");
    }

    @Test
    void reportSeveralValues(TestReporter testReporter) {
        HashMap<String, String> values = new HashMap<>();
        values.put("user name", "dk38");
        values.put("award year", "1974");

        testReporter.publishEntry(values);
    }

}

Note: @ExtendWithにより適切な拡張を登録することで、他のパラメータリゾルバを明示的に有効にする必要があります。

ParameterResolverをカスタムするの例のためにMockitoExtensionをチェックアウトします。本番環境での運用を目的としたものではありませんが、拡張モデルとパラメータ解決プロセスの簡潔さと表現力を示しています。MyMockitoTest@BeforeEach@TestメソッドにMockitoのモックをインジェクトする方法を示しています。

ParameterResolverのカスタム例
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.mockito.Mockito.when;

import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.Mock;
import com.example.Person;
import com.example.mockito.MockitoExtension;

@ExtendWith(MockitoExtension.class)
class MyMockitoTest {

    @BeforeEach
    void init(@Mock Person person) {
        when(person.getName()).thenReturn("Dilbert");
    }

    @Test
    void simpleTestWithInjectedMock(@Mock Person person) {
        assertEquals("Dilbert", person.getName());
    }

}

3.10. インタフェースのデフォルトメソッド

JUnit Jupiterでは、インタフェースのデフォルドメソッドで@Test@TestFactory@BeforeEach@AfterEachを宣言することができます。
この機能の応用として、インタフェースの制約に関するテストを書くことができます。
例えば、次のようにObject.equalsComparable.compareToの実装がどのように振る舞うべきかのテストを書くことができる。

public interface Testable<T> {

    T createValue();

}
public interface EqualsContract<T> extends Testable<T> {

    T createNotEqualValue();

    @Test
    default void valueEqualsItself() {
        T value = createValue();
        assertEquals(value, value);
    }

    @Test
    default void valueDoesNotEqualNull() {
        T value = createValue();
        assertFalse(value.equals(null));
    }

    @Test
    default void valueDoesNotEqualDifferentValue() {
        T value = createValue();
        T differentValue = createNotEqualValue();
        assertNotEquals(value, differentValue);
        assertNotEquals(differentValue, value);
    }

}
public interface ComparableContract<T extends Comparable<T>> extends Testable<T> {

    T createSmallerValue();

    @Test
    default void returnsZeroWhenComparedToItself() {
        T value = createValue();
        assertEquals(0, value.compareTo(value));
    }

    @Test
    default void returnsPositiveNumberComparedToSmallerValue() {
        T value = createValue();
        T smallerValue = createSmallerValue();
        assertTrue(value.compareTo(smallerValue) > 0);
    }

    @Test
    default void returnsNegativeNumberComparedToSmallerValue() {
        T value = createValue();
        T smallerValue = createSmallerValue();
        assertTrue(smallerValue.compareTo(value) < 0);
    }

}

テストクラスを実装する時、継承によりインタフェース間のテスト同士を関連付けできます。もちろん、抽象メソッドは実装する必要あります。

class StringTests implements ComparableContract<String>, EqualsContract<String> {

    @Override
    public String createValue() {
        return "foo";
    }

    @Override
    public String createSmallerValue() {
        return "bar"; // 'b' < 'f' in "foo"
    }

    @Override
    public String createNotEqualValue() {
        return "baz";
    }

}

上で書いたテストは単なる例であって、実用的なものではありません。

3.11. 動的テスト(Dynamic Tests)

JUnit Jupiterにおける標準の@Testアノテーションの機能は、JUnit 4の@Testアノテーションと非常によく似ています。JUnit JupiterにおいてもJUnit 4においても、テストケースのメソッドの実装に記述します。これらのテストケースは、コンパイル時に完全に記述され、実行時に挙動が変更されない、という意味では静的です。Assumptionsは動的な振る舞いに関する基本的な形式を提供しますが、意図的に表現力が限定されています。

JUnit Jupiterには、これらの標準的なテストに加えて、まったく新しい形式のテストが導入されました。
@TestFactoryアノテーションが付与されたファクトリメソッドによって実行時に生成される動的なテストです。

@Testメソッドとは対照的に、@TestFactoryメソッド自身はテストケースではなく、テストケースのためのファクトリです。
したがって、動的なテストはファクトリによって生成されます。技術的な話をすると、@TestFactoryメソッドはDynamicTestインスタンスのストリーム、コレクション、イテラブル、イテレータを返す必要があります。
これらのDynamicTestインスタンスは遅延実行されるため、テストケースの動的生成や非決定的生成さえも可能になります。

@Testと同様に、@TestFacotryメソッドはprivatestaticであってはならず、ParameterResolverによって解決されるパラメータをオプションとして宣言することができます。

DynamicTestは実行時に生成されるテストケースです。これは表示名とExecutableで構成されています。Executable@FunctioanlInterfaceで、動的テストはメソッド参照やラムダ式を用いて実装することができる。

:warning: 動的テストのライフサイクル:
動的テストの実行ライフサイクルは、標準の@Testの場合とはかなり異なります。具体的には、ライフサイクルは動的テストのためのコールバックを行いません。これは、@BeforeEachメソッドと@AfterEachメソッドとそれに対応する拡張コールバックは動的テストでは実行されないということです。 つまり、ダイナミックテストのラムダ式内のテストインスタンスからフィールドにアクセスする場合、これらのフィールドは同じ@TestFactoryメソッドで生成された動的テストの実行間のコールバックメソッドまたは拡張によってリセットされません。

JUnit Jupiter 5.0.0-M3以降、動的テストは常にファクトリメソッドにより作成されるべきです。ただし、これは後のリリースで提供されるであろうファクトリにより達成されます。

3.11.1. 動的テストの例

以下のDynamicTestDemoクラスでは、テストファクトリと動的テストの例をいくつか示しています。

最初のメソッドは不正な戻り値の型を返します。コンパイル時には不正な戻り値の型が検出されず、実行時にJUnitExceptionがスローされます。

次の5つのメソッドは、DynamicTestインスタンスのCollectionIterableIteratorStreamの生成を示す非常に簡単な例です。
これらの例のほとんどは実際に動的な振る舞いを示すものではなく、原則としてサポートされている戻り値型を単に示しています。
ただし、dynamicTestsFromStream()およびdynamicTestsFromIntStream()は、指定された文字列または入力範囲の範囲に対して簡単に動的テストを生成できることを示しています。

最後の例は自然な真に動的なものです。
generateRandomNumberOfTests()は、乱数を生成するIterator、表示名ジェネレータ、およびテスト実行プログラムを実装し、3つすべてをDynamicTest.stream()に式渡しています。 generateRandomNumberOfTests()の非決定論的な振る舞いは、テストの再現性と矛盾しますので、注意して使用する必要がありますが、動的テストの表現力とパワーを実証することができています。

動的テストの例
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.junit.jupiter.api.DynamicTest.dynamicTest;

import java.util.Arrays;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import java.util.Random;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.stream.IntStream;
import java.util.stream.Stream;

import org.junit.jupiter.api.DynamicTest;
import org.junit.jupiter.api.Tag;
import org.junit.jupiter.api.TestFactory;

class DynamicTestsDemo {

    // JUnitExceptionが発生する
    @TestFactory
    List<String> dynamicTestsWithInvalidReturnType() {
        return Arrays.asList("Hello");
    }

    @TestFactory
    Collection<DynamicTest> dynamicTestsFromCollection() {
        return Arrays.asList(
            dynamicTest("1つ目の動的テスト", () -> assertTrue(true)),
            dynamicTest("2つ目の動的テスト", () -> assertEquals(4, 2 * 2))
        );
    }

    @TestFactory
    Iterable<DynamicTest> dynamicTestsFromIterable() {
        return Arrays.asList(
            dynamicTest("3つ目の動的テスト", () -> assertTrue(true)),
            dynamicTest("4つ目の動的テスト", () -> assertEquals(4, 2 * 2))
        );
    }

    @TestFactory
    Iterator<DynamicTest> dynamicTestsFromIterator() {
        return Arrays.asList(
            dynamicTest("5つ目の動的テスト", () -> assertTrue(true)),
            dynamicTest("6つ目の動的テスト", () -> assertEquals(4, 2 * 2))
        ).iterator();
    }

    @TestFactory
    Stream<DynamicTest> dynamicTestsFromStream() {
        return Stream.of("A", "B", "C").map(
            str -> dynamicTest("test" + str, () -> { /* ... */ }));
    }

    @TestFactory
    Stream<DynamicTest> dynamicTestsFromIntStream() {
        // 最初の10個の偶数に関するテストを生成する
        return IntStream.iterate(0, n -> n + 2).limit(10).mapToObj(
            n -> dynamicTest("test" + n, () -> assertTrue(n % 2 == 0)));
    }

    @TestFactory
    Stream<DynamicTest> generateRandomNumberOfTests() {

        // 7の倍数が出現するまで、0から100までのランダムな正整数を生成(7の倍数は含まれない)
        Iterator<Integer> inputGenerator = new Iterator<Integer>() {

            Random random = new Random();
            int current;

            @Override
            public boolean hasNext() {
                current = random.nextInt(100);
                return current % 7 != 0;
            }

            @Override
            public Integer next() {
                return current;
            }
        };

        // 以下のような表示名生成する: input:5、input:37、input:85 etc.
        Function<Integer, String> displayNameGenerator = (input) -> "input:" + input;

        // 現在の入力値に対するテストを実行する
        Consumer<Integer> testExecutor = (input) -> assertTrue(input % 7 != 0);

        // 動的テストのストリームを返す
        return DynamicTest.stream(inputGenerator, displayNameGenerator, testExecutor);
    }

}
16
20
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
16
20