Junit5の使い方

Junit5の使い方

Junit5の書き方

org.junit.jupiterパッケージのアノテーションとクラスを使用する。

テストメソッド

@Testを付与したメソッドは、テスト実施の対象となる。

import org.junit.jupiter.api.Test;

class SampleTest {

    @Test
    public void sample_検証内容() {
        // ...
    }
}

テストの前処理・後処理

テストメソッド実行の前後に行いたい処理を記述することが出来る。

@BeforeAll

全てのテストの実行前に1度だけ行う処理を記述する。
メソッドは引数・戻り値無しで、staticで定義する。

@BeforeEach

テストメソッド実行前に毎回行う処理を記述することが出来る。
メソッドは引数・戻り値無しで、インスタンスメソッドで定義する。

@AfterEach

テストメソッド実行後に毎回行う処理を記述することが出来る。
メソッドは引数・戻り値無しで、インスタンスメソッドで定義する。

@AfterAll

全てのテストの実行後に1度だけ行う処理を記述する。
メソッドは引数・戻り値無しで、staticで定義する。

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

class SampleTest {

    @BeforeAll
    static void beforeAll() {
        // テストクラス内で一番最初に一度だけ実行される
    }

    @BeforeEach
    void beforeEach() {
        // 全てのテストの実行前に実行される
    }

    @Test
    public void sample_検証内容() {
        // ...
    }

    @AfterEach
    void afterEach() {
        // 各テストメソッド実行後に実行される
    }

    @AfterAll
    static void afterAll() {
        // 全てのテスト実行後に一度だけ実行される
    }
}

ネストしたテスト

内部クラスに@Nestedを付与することで、非static な内部クラスを入れ子にすることが出来る。
テストメソッドの数が多い場合などにおいて、テストの分類をするのに使用される。
ネストしたクラス単位でのテスト実行も可能。

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

class SampleTest {

    @BeforeEach
    void beforeEach() {
        //...
    }

    @Test
    public void sample_検証内容() {
        // ...
    }

    @AfterEach
    void afterEach() {
        //...
    }

    @Nested
    class NestedTest {

        @BeforeEach
        void beforeEach() {
            //...
        }

        @Test
        void nested_検証内容() {
            //...
        }

        @AfterEach
        void afterEach() {
            //...
        }
    }

    @Nested
    class NestedTest2 {

        @Test
        void test() {
            //...
        }
    }
}

上記のようにテストクラスと内部クラスにそれぞれ@BeforeEach@AfterEachを付与したメソッドが定義されている場合、以下の順番でメソッドが実行される。

  1. SampleTest.beforeEach()
  2. NestedTest.beforeEach()
  3. NestedTest.nested_検証内容()
  4. NestedTest.afterEach()
  5. SampleTest.afterEach()

ネストしたクラスで@BeforeAll@AfterAllを使用するには

内部クラスは非staticである必要があるため、staticメソッドとして定義する必要のある@BeforeAll@AfterAllを利用することはできない。
⇒ テストインスンタンスのライフサイクルの指定でPER_CLASSを設定する必要がある。
設定を追加することで、内部クラスで定義する@BeforeAll@AfterAllのメソッドに限りインスタンスメソッドとして定義できるようになる。

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.Nested;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.TestInstance;

class SampleTest {

    @BeforeAll
    static void beforeAll() {
        //...
    }

    @BeforeEach
    void beforeEach() {
        //...
    }

    @AfterEach
    void afterEach() {
        //...
    }

    @AfterAll
    static void afterAll() {
        //...
    }

    @Nested
    @TestInstance(TestInstance.Lifecycle.PER_CLASS)
    class NestedTest {

        @BeforeAll
        void beforeAll() {
            //...
        }

        @BeforeEach
        void beforeEach() {
            //...
        }

        @Test
        void nested_検証内容1() {
            //...
        }

        @Test
        void nested_検証内容2() {
            //...
        }

        @AfterEach
        void afterEach() {
            //...
        }

        @AfterAll
        void afterAll() {
            //...
        }
    }
}

上記のように前処理・後処理とテストメソッドを定義した場合は、以下の順番で処理が実行される。

  1. SampleTest.beforeAll()
  2. NestedTest.beforeAll()
  3. SampleTest.beforeEach()
  4. NestedTest.beforeEach()
  5. NestedTest.nested_検証内容1()
  6. NestedTest.afterEach()
  7. SampleTest.afterEach()
  8. SampleTest.beforeEach()
  9. NestedTest.beforeEach()
  10. NestedTest.nested_検証内容2()
  11. NestedTest.afterEach()
  12. SampleTest.afterEach()
  13. NestedTest.afterAll()
  14. SampleTest.afterAll()

前提条件の指定

テストメソッド内で特定の条件に合致した場合のみテストを続行したり、処理を実行したりするのに使用する。

Assumptions.assumeTrue(boolean)

引数に true を渡された場合のみ続きのテストが実行される。
引数が false だった場合は、そのテストメソッド内の残りの処理は中断される。
中断されたテストは successful でも failed でもなく、 aborted という状態となる。

Assumptions.assumingThat(boolean, Executable)

第1引数の値が true の場合のみ第2引数で渡した処理が実行される。

import org.junit.jupiter.api.Assumptions;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;

class SampleTest {

    @Test
    void test_検証内容1() {
        Assumptions.assumeTrue(true);
        // 以降の処理は実行される
    }

    @Test
    void test_検証内容2() {
        Assumptions.assumeTrue(false);
        // 以降の処理は実行されない
    }

    @Test
    void test_検証内容3() {
        Assumptions.assumingThat(true, () -> {
            // 第2引数で渡した処理が実行される 
        });
        // 以降の処理は実行される
    }

    @Test
    void test_検証内容4() {
        Assumptions.assumingThat(false, () -> {
            // 第2引数で渡した処理が実行されない
        });
        // 以降の処理は実行される
    }
}

テストの無効化

@Disabledを付与したテストメソッドは実行されなくなる。
クラスに付与した場合は、テストクラス内の全テストメソッドが実行されなくなる。

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

class SampleTest {

    @Test
    void sample_検証内容1() {
        // 実行されるテストメソッド
    }

    @Test
    @Disabled
    void sample_検証内容2() {
        // 実行されないテストメソッド
    }
}
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;

@Disabled
class SampleTest {

    @Test
    void sample_検証内容1() {
        // クラスに@Disabledが付与されているため、実行されないテストメソッド
    }

    @Test
    void sample_検証内容2() {
        // クラスに@Disabledが付与されているため、実行されないテストメソッド
    }
}

条件付きテスト(OS)

@EnabledOnOs

特定の OS でのテスト実行を有効する。

@DisabledOnOs

特定の OS でのテスト実行を無効にする。

引数には OS (org.junit.jupiter.api.condition.OS) で定義された定数を指定する。

import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.condition.DisabledOnOs;
import org.junit.jupiter.api.condition.EnabledOnOs;
import org.junit.jupiter.api.condition.OS;

class SampleTest {

    @Test
    @EnabledOnOs(OS.WINDOWS)
    void sample_検証内容1() {
        // Windows環境でのみ実行されるテストメソッド
    }

    @Test
    @DisabledOnOs(OS.WINDOWS)
    void sample_検証内容2() {
        // Windows環境でのみ実行されないテストメソッド
    }
}

条件付きテスト(Javaバージョン)

@EnabledOnJre

特定の Java バージョンのみでテスト実行を有効にする。

@DisabledOnJre

特定の Java バージョンでのテスト実行を無効にする。

引数には JRE (org.junit.jupiter.api.condition.JRE) で定義された定数を指定する。

import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.condition.DisabledOnJre;
import org.junit.jupiter.api.condition.EnabledOnJre;
import org.junit.jupiter.api.condition.JRE;

class SampleTest {

    @Test
    @EnabledOnJre(JRE.JAVA_11)
    void sample_検証内容1() {
        // Java11環境でのみ実行されるテストメソッド
    }

    @Test
    @DisabledOnJre(JRE.JAVA_11)
    void sample_検証内容2() {
        // Java11環境でのみ実行されないテストメソッド
    }
}

条件付きテスト(システムプロパティ)

@EnabledIfSystemProperty

システムプロパティの値を条件にしてテスト実行を有効にする。

@DisabledIfSystemProperty

システムプロパティの値を条件にしてテスト実行を無効にする。

属性 named :条件にしたいシステムプロパティの名前を指定する
属性 matches :条件となる値を正規表現で指定する(完全一致)

import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.condition.DisabledIfSystemProperty;
import org.junit.jupiter.api.condition.EnabledIfSystemProperty;

class SampleTest {

    @Test
    @EnabledIfSystemProperty(named = "java.vendor", matches = "AdoptOpenJDK")
    void sample_検証内容1() {
        // AdoptOpenJDKのJava環境でのみ実行されるテストメソッド
    }

    @Test
    @DisabledIfSystemProperty(named = "java.vendor", matches = "AdoptOpenJDK")
    void sample_検証内容2() {
        // AdoptOpenJDKのJava環境でのみ実行されないテストメソッド
    }
}

条件付きテスト(環境変数)

@EnabledIfEnvironmentVariable

環境変数の値を条件にしてテスト実行を有効にする。

@DisabledIfEnvironmentVariable

環境変数の値を条件にしてテスト実行を無効にする。

属性named :条件にしたい環境変数の名前を指定する
属性 matches :条件となる値を正規表現で指定する(完全一致)

import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.condition.DisabledIfEnvironmentVariable;
import org.junit.jupiter.api.condition.EnabledIfEnvironmentVariable;

class SampleTest {

    @Test
    @EnabledIfEnvironmentVariable(named = "JAVA_HOME", matches = ".*\\\\zulu-11-azure-jdk_11.*")
    void sample_検証内容1() {
        // 環境変数JAVA_HOMEに、Zulu11が設定されている環境でのみ実行されるテストメソッド
    }

    @Test
    @DisabledIfEnvironmentVariable(named = "JAVA_HOME", matches = ".*\\\\zulu-11-azure-jdk_11.*")
    void sample_検証内容2() {
        // 環境変数JAVA_HOMEに、Zulu11が設定されている環境でのみ実行されないテストメソッド
    }
}

よく使うアサーション

fail

テストを失敗させる時に使用する。

import org.junit.jupiter.api.Test;
import static org.assertj.core.api.Assertions.fail;

class SampleTest {

    @Test
    void sample_検証内容() {
        try {
            // ...
        } catch (Exception e) {
            fail("例外が発生したら失敗とする", e);
        }
    }
}

assertTrue, assertFalse

真偽値の確認に使用する。

import org.junit.jupiter.api.Test;
import static org.assertj.core.api.Assertions.assertTrue;
import static org.assertj.core.api.Assertions.assertFalse;

class SampleTest {

    @Test
    void sample_検証内容() {
        String sample = "";
        assertTrue(sample.isEmpty());

        sample = "sample";
        assertFalse(sample.isEmpty());
    }
}

assertNull, assertNotNull

null比較に使用する。

import org.junit.jupiter.api.Test;
import static org.assertj.core.api.Assertions.assertNull;
import static org.assertj.core.api.Assertions.assertNotNull;

class SampleTest {

    @Test
    void sample_検証内容() {
        String sample = null;
        assertNull(sample );

        sample = "sample";
        assertNotNull(sample);
    }
}

assertEquals, assertNotEquals

等値比較に使用する。

import org.junit.jupiter.api.Test;
import static org.assertj.core.api.Assertions.assertEquals;
import static org.assertj.core.api.Assertions.assertNotEquals;

class SampleTest {

    @Test
    void sample_検証内容_文字列の場合() {
        String sample = "sample";
        assertEquals("sample", sample.toLowerCase());
        assertNotEquals("sample" , sample.toUpperCase());
    }

    @Test
    void sample_検証内容_浮動小数点の場合() {
        // 浮動小数点は完全一致するとは限らないので、第3引数で一致する範囲(delta)を指定する
        assertEquals(0.333f, 1 / 3f, 0.001f); // float
        assertEquals(0.333d, 1 / 3d, 0.001d); // double
    }
}

assertArrayEquals

配列の要素比較に使用する。

import org.junit.jupiter.api.Test;
import static org.assertj.core.api.Assertions.assertArrayEquals;

class SampleTest {

    @Test
    void sample_検証内容() {
        int[] expected = { 1, 2, 3 };
        int[] actual   = { 1, 2, 3 };
        assertArrayEquals(expected, actual);
    }
}

assertIterableEquals

リスト(Iterable)の要素比較に使用する。
assertEqualsでも比較できるが、不一致だった場合にassertIterableEqualsを使用していた場合は「不一致だった要素」が表示される。

import org.junit.jupiter.api.Test;
import static org.assertj.core.api.Assertions.assertIterableEquals;
import java.util.Arrays;

class SampleTest {

    @Test
    void sample_検証内容() {
        List<String> expected = Arrays.asList("a", "b", "c");
        List<String> actual   = Arrays.asList("a", "b", "c");
        assertIterableEquals(expected, actual);
    }
}

assertLinesMatch

複数行にわたるメッセージ(スタックトレースとかログメッセージ)の比較に使用する。

import org.junit.jupiter.api.Test;
import static org.assertj.core.api.Assertions.assertLinesMatch;
import java.util.Arrays;

class SampleTest {

    @Test
    void sample_検証内容() {
        // assertLinesMatchは、基本的にはList<String>の比較に使用する。
        // また、期待値の方には正規表現等を指定可能(「\\d+ms」や「.{3}」など)
        // 「>> >>」は、次のデータがマッチするまでスキップする
        // 「>> skip >>」のように間に文字列を書いた場合はコメント扱いで無視されるが、
        // 「>> 3 >>」のように数値を書いた場合はその行数分スキップする。
        List<String> expected = Arrays.asList("\\d+ms", "abc", ">> skip >>", ".{3}");
        List<String> actual   = Arrays.asList("123ms", "abc", "1", "2", "3", "zzz");
        assertLinesMatch(expected, actual);
    }
}

assertSame, assertNotSame

インスタンスの比較に使用する。

import org.junit.jupiter.api.Test;
import static org.assertj.core.api.Assertions.assertSame;
import static org.assertj.core.api.Assertions.assertNotSame;

class SampleTest {

    @Test
    void sample_検証内容() {
        String sample = new String("sample");
        assertNotSame("sample", sample);
        assertSame("sample", sample.intern());
    }
}

assertAll

全チェック(1つのアサーションに失敗しても残りのアサーションを全て実行)する場合に使用する。
複数のアサーションが失敗しても、エラーがスローされるのは1回だけ(MultipleFailuresError)となる。

import org.junit.jupiter.api.Test;
import static org.assertj.core.api.Assertions.assertAll;
import static org.assertj.core.api.Assertions.assertEquals;

class SampleTest {

    @Test
    void sample_検証内容() {
        String sample = new String("test");
        // assertAllの引数は可変長引数になっており、複数のラムダ式を記述する(ラムダ式で個々のテストを記述する)
        assertAll("sample_all_NG", 
                  () -> assertEquals("sample", sample, "string not equals"),
                  () -> assertEquals(6, sample.length(), "length not equals")
        );
        // 実行結果は以下のようになる
        //    org.opentest4j.MultipleFailuresError: sample_all_NG (2 failures)
    //        org.opentest4j.AssertionFailedError: string not equals ==> expected: <sample> but was: <test>
    //        org.opentest4j.AssertionFailedError: length not equals ==> expected: <6> but was: <4>
    }
}

assertTimeout, assertTimeoutPreemptively

タイムアウトしないこと(一定時間内に終了すること)を確認する際に使用する。

◇ asserTimeoutとassertTimeoutPreemptivelyの差異について
assertTimeoutはテスト対象の処理が終わるまで待ち、終わってからAssertionFailedErrorを発生させる。
assertTimeoutPreemptivelyは、タイムアウト時間になったらすぐに(テスト対象の処理が終わるのを待たずに)AssertionFailedErrorを発生させる。

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTimeout;
import static org.junit.jupiter.api.Assertions.assertTimeoutPreemptively;
import java.time.Duration;
import java.util.concurrent.Callable;
import java.util.concurrent.Executors;
import org.junit.jupiter.api.Test;

class SampleTest {

    Callable<String> task = () -> {
        Thread.sleep(1000); // 1秒待機
        return "success";
    };

    @Test
    void sample_検証内容1() {
        // 第1引数でタイムアウト時間、第2引数(ラムダ式)でテスト対象の処理を指定
        String result = assertTimeout(Duration.ofSeconds(2), () -> {
            return Executors.newCachedThreadPool().submit(task).get();
        });
        assertEquals("success", result);
    }

    @Test
    void sample_検証内容2() {
        // 第1引数でタイムアウト時間、第2引数(ラムダ式)でテスト対象の処理を指定
        String result = assertTimeoutPreemptively(Duration.ofSeconds(2), () -> {
            return Executors.newCachedThreadPool().submit(task).get();
        });
        assertEquals("success", result);
    }
}

例外テスト方法

assertThrows

例外が発生することを確認する際に使用する。

  • 第1引数で「発生するであろう例外」のクラスを指定
  • 第2引数(ラムダ式)でテスト対象の処理を指定
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertEquals;
import org.junit.jupiter.api.Test;

class SampleTest {

    /**
     * テスト対象
     */
    class Example1 {
        public void execute(String arg) {
            if (arg == null) {
                throw new IllegalArgumentException("不正な引数:nullです");
            }
        }
    }

    @Test
    void execute_IllegalArgumentException() {
        Example1 target = new Example1();
        Throwable exception = assertThrows(IllegalArgumentException.class, () -> target.execute(null));
        // 発生した例外を受け取り、例外が保持している情報(メッセージなど)を検証することも可能
        assertEquals(exception.getMessage(), "不正な引数:nullです");
    }
}

assertDoesNotThrow

例外が発生しないことを確認する際に使用する。
第1引数(ラムダ式)でテスト対象の処理を指定

import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
import org.junit.jupiter.api.Test;

class SampleTest {

    /**
     * テスト対象
     */
    class Example1 {
        public void execute(String arg) {
            if (arg == null) {
                throw new IllegalArgumentException("nullです");
            }
        }
    }

    @Test
    void execute_OK() {
        Example1 target = new Example1();
        assertDoesNotThrow(() -> target.execute("sample")); // 例外が発生しないこと
    }
}

パラメータテスト

1つのテストメソッドに複数のパターンのパラメータを渡してテストを実行する場合に使用する。

@ParameterizedTest

パラメータテストを実行することを明示する。
以下のアノテーションと組み合わせて使用する(下記以外にも使用可能なアノテーションは存在する)

  • @ValueSource: パラメータを1つ渡す場合に使用する
  • @CsvSource: 複数のパラメータを渡す場合に使用する
  • @NullAndEmptySource: nullと空白を順番に渡す

@ValueSource@NullAndEmptySourceを組み合わせて使用することもできる。

import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.CsvSource;
import org.junit.jupiter.params.provider.NullAndEmptySource;
import org.junit.jupiter.params.provider.ValueSource;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue;

class SampleTest {

    /**
     * テスト対象
     */
    class Example1 {
        public boolean isValid(String arg) {
            if (arg == null || arg.isEmpty()) {
                return false;
            }
            return true;
        }
    }

    Example1 target = new Example1();

    @ParameterizedTest
    @ValueSource(strings = { "foo", "bar" })
    void isValid_バリデーション動作確認1(String arg) {
        // "foo"、"bar"が順番に渡される
        assertTrue(target.isValid(arg));
    }

    @ParameterizedTest
    @NullAndEmptySource
    void isValid_バリデーション動作確認2(String arg) {
        // nullと空白が順番に渡される
        assertFalse(target.isValid(arg));
    }

    @ParameterizedTest
    @CsvSource({
            // "値, バリデーション結果(null以外はtrue)"
            " 'sample' , true  ", // 文字列
            " ''       , false ", // 空白
            "          , false " // null
    })
    public void isValid_バリデーション動作確認3(String arg, boolean result) {
        // 「"sample" と true」、「空白 と false」、「null と false」が順番に渡される
        assertEquals(target.isValid(arg), result);
    }

    @ParameterizedTest
    @ValueSource(strings = { "foo", "bar" })
    @NullAndEmptySource
    void isValid_サンプル(String arg) {
        // パラメータが「"foo"、"bar"、null、空白」の順番で、計4回のテストが実行される
    }
}

テストカバレッジについての記事はこちら: olafnosuke.hatenablog.com

モックについての記事も書いています:

olafnosuke.hatenablog.com

olafnosuke.hatenablog.com

OSSを使用したログの出力内容確認テストについてはこちら:

olafnosuke.hatenablog.com