JUnit5(Alpha版)使い方メモ

JUnit5 の Alpha 版が公開されてたので、<span class="hidden">関西DDDに補欠になってしまった悲しみを紛らわすために</span>使い方を調べた。

※Alpha 版なので、今後変更されるかもしれません。

# JUnit5 とは
言わずと知れた JUnit の次期バージョン。

Java 8 以上のみをサポートするようになり、 JUnit4 からは大きく変わっている。

でも、テストメソッドとか基本的な考えは変わっていない。

2016/02/06 現在、 Alpha 版が公開されている。

# Hello World
Gradle で使う方法（Maven でもいけるらしい）。

## ビルドファイル
```groovy:build.gradle
buildscript {
    repositories {
        maven { url 'https://oss.sonatype.org/content/repositories/snapshots' }
    }
    dependencies {
        classpath 'org.junit:junit-gradle:5.0.0-SNAPSHOT'
    }
}

apply plugin: 'org.junit.gen5.gradle'
apply plugin: 'java'

repositories {
    mavenCentral()
}

dependencies {
    testCompile 'org.junit:junit5-api:5.0.0-ALPHA'
}

junit5 {
    version '5.0.0-ALPHA'
}
```

## 実装
```java:Junit5Test.java
package sample.junit5;

import static org.junit.gen5.api.Assertions.*;

import org.junit.gen5.api.Test;

public class Junit5Test {

    @Test
    public void test1() {
        assertEquals("hoge", "hoge");
    }

    @Test
    public void test2() {
        assertEquals("hoge", "fuga");
    }
}
```

## 動作確認
```text
> gradle -q junit5Test
2 06, 2016 2:55:35 午後 org.junit.gen5.launcher.main.ServiceLoaderTestEngineRegistry loadTestEngines
情報: Discovered TestEngines with IDs [junit5]

Test failures (1):
  JUnit 5:sample.junit5.Junit5Test:test2
    sample.junit5.Junit5Test#test2()
    => Exception: expected: <hoge> but was: <fuga>

Test run finished after 76 ms
[         2 tests found     ]
[         0 tests skipped   ]
[         2 tests started   ]
[         0 tests aborted   ]
[         1 tests successful]
[         1 tests failed    ]


FAILURE: Build failed with an exception.

* What went wrong:
Execution failed for task ':junit5Test'.
> Process 'command 'C:\Program Files\Java\jdk8\bin\java.exe'' finished with non-zero exit value 1

* Try:
Run with --stacktrace option to get the stack trace. Run with --info or --debug option to get more log output.
```

- Gradle 用のプラグインが用意されているので、それを利用する。
- とりあえず `junit5.version` を設定したら動いた。
- `junit5Test` タスクでテストを実行できる。

## テスト結果のファイル
デフォルトでは、 `build/test-results/junit5` の下に xml ファイルが出力される。

このファイルは、 JUnit4 までのテスト結果ファイルと同じフォーマットなので、 eclipse などで開いて見ることができる。

![junit5.JPG](https://qiita-image-store.s3.amazonaws.com/0/28302/95ae4adf-ce9a-5187-6032-d76638fdbdf3.jpeg)


# テストインスタンスのライフサイクル
```java:Junit5Test.java
package sample.junit5;

import org.junit.gen5.api.Test;

public class Junit5Test {
    
    @Test
    public void test1() {
        System.out.println("test1 : " + this.hashCode());
    }
    
    @Test
    public void test2() {
        System.out.println("test2 : " + this.hashCode());
    }
}
```

```text:実行結果
test1 : 66233253
test2 : 152005629
```

- テストメソッドが実行されるごとに新しいインスタンスが生成される。

# アサーション
標準で、簡単なアサーションがいくつか用意されている。

```java:Junit5Test.java
package sample.junit5;

import static org.junit.gen5.api.Assertions.*;

import org.junit.gen5.api.Test;

public class Junit5Test {
    
    @Test
    public void trueであることの検証() {
        assertTrue(false);
    }

    @Test
    public void trueであることの検証_ラムダ式も渡せる() {
        assertTrue(() -> false);
    }

    @Test
    public void trueであることの検証_末尾に任意のメッセージをセットできる() {
        assertTrue(false, "true を期待したのに false だった");
    }

    @Test
    public void trueであることの検証_末尾に任意のメッセージをラムダ式でセットできる() {
        assertTrue(false, () -> "true を期待したのに false だった");
    }

    @Test
    public void falseであることの検証() {
        assertFalse(true);
    }

    @Test
    public void equalsで比較して同じことを検証() {
        assertEquals("hoge", "fuga");
    }

    @Test
    public void equalsで比較して異なることを検証() {
        assertNotEquals("hoge", "hoge");
    }

    @Test
    public void nullでないことを検証() {
        assertNotNull(null);
    }

    @Test
    public void nullであることを検証() {
        assertNull("not null");
    }

    @Test
    public void 等号で比較して同じインスタンスであることを検証() {
        assertSame(new String("hoge"), new String("hoge"));
    }

    @Test
    public void 指定した例外がスローされることを検証() {
        assertThrows(IllegalArgumentException.class, () -> {throw new NullPointerException();});
    }

    @Test
    public void スローされた例外を検証したうえで_その例外を取得する() {
        IllegalArgumentException e = expectThrows(IllegalArgumentException.class, () -> {
            throw new IllegalArgumentException("テストです");
        });
        
        assertEquals("test です", e.getMessage());
    }

    @Test
    public void 複数のアサーションをまとめて実行() {
        assertAll(
            () -> assertTrue(false, "true と信じてたら false だった"),
            () -> assertEquals("hoge", "fuga", "hoge と信じてる")
        );
    }
}
```

```text:実行結果
Test failures (13):
  JUnit 5:sample.junit5.Junit5Test:trueであることの検証_ラムダ式も渡せる
    sample.junit5.Junit5Test#trueであることの検証_ラムダ式も渡せる()
    => Exception: null

  JUnit 5:sample.junit5.Junit5Test:指定した例外がスローされることを検証
    sample.junit5.Junit5Test#指定した例外がスローされることを検証()
    => Exception: Unexpected exception type thrown ==> expected: <java.lang.IllegalArgumentException> but was: <java.lang.NullPointerException>

  JUnit 5:sample.junit5.Junit5Test:trueであることの検証_末尾に任意のメッセージをセットできる
    sample.junit5.Junit5Test#trueであることの検証_末尾に任意のメッセージをセットできる()
    => Exception: true を期待したのに false だった

  JUnit 5:sample.junit5.Junit5Test:equalsで比較して異なることを検証
    sample.junit5.Junit5Test#equalsで比較して異なることを検証()
    => Exception: expected: not equal but was: <hoge>

  JUnit 5:sample.junit5.Junit5Test:複数のアサーションをまとめて実行
    sample.junit5.Junit5Test#複数のアサーションをまとめて実行()
    => Exception: Multiple Failures (2 failures)
	true と信じてたら false だった
	hoge と信じてる ==> expected: <hoge> but was: <fuga>

  JUnit 5:sample.junit5.Junit5Test:trueであることの検証
    sample.junit5.Junit5Test#trueであることの検証()
    => Exception: null

  JUnit 5:sample.junit5.Junit5Test:falseであることの検証
    sample.junit5.Junit5Test#falseであることの検証()
    => Exception: null

  JUnit 5:sample.junit5.Junit5Test:nullでないことを検証
    sample.junit5.Junit5Test#nullでないことを検証()
    => Exception: expected: not <null>

  JUnit 5:sample.junit5.Junit5Test:nullであることを検証
    sample.junit5.Junit5Test#nullであることを検証()
    => Exception: expected: <null> but was: <not null>

  JUnit 5:sample.junit5.Junit5Test:trueであることの検証_末尾に任意のメッセージをラムダ式でセットできる
    sample.junit5.Junit5Test#trueであることの検証_末尾に任意のメッセージをラムダ式でセットできる()
    => Exception: true を期待したのに false だった

  JUnit 5:sample.junit5.Junit5Test:equalsで比較して同じことを検証
    sample.junit5.Junit5Test#equalsで比較して同じことを検証()
    => Exception: expected: <hoge> but was: <fuga>

  JUnit 5:sample.junit5.Junit5Test:等号で比較して同じインスタンスであることを検証
    sample.junit5.Junit5Test#等号で比較して同じインスタンスであることを検証()
    => Exception: expected: java.lang.String@69d9c55<hoge> but was: java.lang.String@13a57a3b<hoge>

  JUnit 5:sample.junit5.Junit5Test:スローされた例外を検証したうえで_その例外を取得する
    sample.junit5.Junit5Test#スローされた例外を検証したうえで_その例外を取得する()
    => Exception: expected: <test です> but was: <テストです>
```

## 任意のアサーションライブラリを使う
```groovy:build.gradle
dependencies {
    testCompile 'org.junit:junit5-api:5.0.0-ALPHA'
    testCompile 'org.assertj:assertj-core:3.3.0'
    testCompile 'org.hamcrest:hamcrest-all:1.3'
}
```

```java:Junit5Test.java
package sample.junit5;

import static org.hamcrest.CoreMatchers.*;

import org.assertj.core.api.Assertions;
import org.hamcrest.MatcherAssert;
import org.junit.gen5.api.Test;

public class Junit5Test {
    
    @Test
    public void assertj() {
        Assertions.assertThat("hoge").isEqualTo("fuga");
    }
    
    @Test
    public void hamcrest() {
        MatcherAssert.assertThat("hoge", is("fuga"));
    }
}
```

```text:実行結果
Test failures (2):
  JUnit 5:sample.junit5.Junit5Test:assertj
    sample.junit5.Junit5Test#assertj()
    => Exception:
Expecting:
 <"hoge">
to be equal to:
 <"fuga">
but was not.

  JUnit 5:sample.junit5.Junit5Test:hamcrest
    sample.junit5.Junit5Test#hamcrest()
    => Exception:
Expected: is "fuga"
     but: was "hoge"

Test run finished after 109 ms
[         2 tests found     ]
[         0 tests skipped   ]
[         2 tests started   ]
[         0 tests aborted   ]
[         0 tests successful]
[         2 tests failed    ]


FAILURE: Build failed with an exception.
```

- JUnit5 は特定のアサーションライブラリに依存しておらず、任意のライブラリを使用できる。
- 試しに Hamcrest と AssertJ を試したが、問題なく動いた。
    - どちらも JUnit4 に依存してるんじゃないの？ って思ってたけど、 `gradle dependencies` で確認したら依存してなかった。
    - よくできとるわ。


# アノテーション
## `@Test`
```java:Junit5Test.java
package sample.junit5;

import static org.junit.gen5.api.Assertions.*;

import org.junit.gen5.api.Test;

public class Junit5Test {

    public void test0() {
        assertEquals("hoge", "test0");
    }

    @Test
    public void test1() {
        assertEquals("hoge", "test1");
    }

    @Test
    protected void test2() {
        assertEquals("hoge", "test2");
    }

    @Test
    void test3() {
        assertEquals("hoge", "test3");
    }

    @Test
    private void test4() {
        assertEquals("hoge", "test4");
    }
}
```

```text:実行結果
Test failures (3):
  JUnit 5:sample.junit5.Junit5Test:test1
    sample.junit5.Junit5Test#test1()
    => Exception: expected: <hoge> but was: <test1>
  JUnit 5:sample.junit5.Junit5Test:test2
    sample.junit5.Junit5Test#test2()
    => Exception: expected: <hoge> but was: <test2>
  JUnit 5:sample.junit5.Junit5Test:test3
    sample.junit5.Junit5Test#test3()
    => Exception: expected: <hoge> but was: <test3>

Test run finished after 81 ms
[         3 tests found     ]
[         0 tests skipped   ]
[         3 tests started   ]
[         0 tests aborted   ]
[         0 tests successful]
[         3 tests failed    ]
```

- `@Test` でアノテートしたメソッドがテストメソッドになる。
    - JUnit4 の `@Test` とはパッケージが異なる。
- テストメソッドは `public`, `protected`, デフォルトのいずれか。
    - `private` を `@Test` でアノテートしてもエラーにはならずに無視される。

## `@DisplayName`
```java:Junit5Test.java
package sample.junit5;

import static org.junit.gen5.api.Assertions.*;

import org.junit.gen5.api.DisplayName;
import org.junit.gen5.api.Test;

@DisplayName("JUnit5 のテスト")
public class Junit5Test {

    @Test
    @DisplayName("１つ目のテストやで！")
    public void test1() {
        assertEquals("hoge", "test1");
    }
    
    @Test
    public void test2() {
        assertEquals("hoge", "test2");
    }
}
```

```text:実行結果
Test failures (2):
  JUnit 5:JUnit5 のテスト:１つ目のテストやで！
    sample.junit5.Junit5Test#test1()
    => Exception: expected: <hoge> but was: <test1>
  JUnit 5:JUnit5 のテスト:test2
    sample.junit5.Junit5Test#test2()
    => Exception: expected: <hoge> but was: <test2>
```

- テスト名を指定できる。
- 先頭が数字でもいいし、スペースが入っていてもいい。
- クラスもアノテートできる。

## `@BeforeEach` - `@AfterEach`
```java:Junit5Test.java
package sample.junit5;

import org.junit.gen5.api.AfterEach;
import org.junit.gen5.api.BeforeEach;
import org.junit.gen5.api.Test;

public class Junit5Test {

    @BeforeEach
    public void before1() {
        System.out.println("before1");
    }

    @BeforeEach
    protected void before2() {
        System.out.println("before2");
    }

    @BeforeEach
    void before3() {
        System.out.println("before3");
    }

    @BeforeEach
    private void before4() {
        System.out.println("before4");
    }
    
    @Test
    public void test1() {
        System.out.println("  test1");
    }
    
    @Test
    public void test2() {
        System.out.println("  test2");
    }

    @AfterEach
    public void after1() {
        System.out.println("after1");
    }

    @AfterEach
    protected void after2() {
        System.out.println("after2");
    }

    @AfterEach
    void after3() {
        System.out.println("after3");
    }

    @AfterEach
    private void after4() {
        System.out.println("after4");
    }
}
```

```text:実行結果
before1
before2
before3
before4
  test1
after4
after3
after2
after1
before1
before2
before3
before4
  test2
after4
after3
after2
after1
```

- テストメソッド単位で前処理と後処理を挟める。
- `@BeforeEach` が前処理で、 `@AfterEach` が後処理。
- メソッドの可視性はなんでもいい。

## `@BeforeAll` - `@AfterAll`
```java:Junit5Test.java
package sample.junit5;

import org.junit.gen5.api.AfterAll;
import org.junit.gen5.api.AfterEach;
import org.junit.gen5.api.BeforeAll;
import org.junit.gen5.api.BeforeEach;
import org.junit.gen5.api.Test;

public class Junit5Test {

    @BeforeAll
    public static void beforeAll1() {
        System.out.println("beforeAll1");
    }

    @BeforeAll
    protected static void beforeAll2() {
        System.out.println("beforeAll2");
    }

    @BeforeAll
    static void beforeAll3() {
        System.out.println("beforeAll3");
    }

    @BeforeAll
    private static void beforeAll4() {
        System.out.println("beforeAll4");
    }

    @BeforeEach
    public void before() {
        System.out.println("  before");
    }
    
    @Test
    public void test1() {
        System.out.println("    test1");
    }
    
    @Test
    public void test2() {
        System.out.println("    test2");
    }

    @AfterEach
    public void after() {
        System.out.println("  after");
    }

    @AfterAll
    public static void afterAll1() {
        System.out.println("afterAll1");
    }

    @AfterAll
    protected static void afterAll2() {
        System.out.println("afterAll2");
    }

    @AfterAll
    static void afterAll3() {
        System.out.println("afterAll3");
    }

    @AfterAll
    private static void afterAll4() {
        System.out.println("afterAll4");
    }
}
```

```text:実行結果
beforeAll1
beforeAll2
beforeAll3
beforeAll4
  before
    test1
  after
  before
    test2
  after
afterAll4
afterAll3
afterAll2
afterAll1
```

- テストクラス単位で前処理と後処理を挟める。
- `@BeforeAll` が前処理で、 `@AfterAll` が後処理。
- メソッドの可視性はなんでもいい。
- メソッドは `static` にしておく必要がある。

## `@Nested`
### 基本
```java:Junit5Test.java
package sample.junit5;

import org.junit.gen5.api.Nested;
import org.junit.gen5.api.Test;

public class Junit5Test {
    
    @Test
    public void test1() {
        System.out.println("test1");
    }
    
    @Nested
    public class NestedTest {
        
        @Test
        public void test2() {
            System.out.println("test2");
        }
    }
}
```

```text:実行結果
test2
test1
```

- `@Nested` でインナークラスをアノテートすることで、テストクラスの入れ子ができるようになる。
- インナークラスは非 `static` にする。

### `@DisplayName` との組み合わせ
```java:Junit5Test.java
package sample.junit5;

import static org.junit.gen5.api.Assertions.*;

import org.junit.gen5.api.DisplayName;
import org.junit.gen5.api.Nested;
import org.junit.gen5.api.Test;

@DisplayName("JUnit5 のテスト")
public class Junit5Test {
    
    @Nested
    @DisplayName("◯◯の場合")
    public class NestedTest {
        
        @Test
        @DisplayName("テスト１")
        public void test1() {
            assertEquals("foo", "bar");
        }
        
        @Test
        @DisplayName("テスト２")
        public void test2() {
            assertEquals("foo", "bar");
        }
    }
}
```

```text:実行結果
Test failures (2):
  JUnit 5:JUnit5 のテスト:◯◯の場合:テスト１
    sample.junit5.Junit5Test$NestedTest#test1()
    => Exception: expected: <foo> but was: <bar>
  JUnit 5:JUnit5 のテスト:◯◯の場合:テスト２
    sample.junit5.Junit5Test$NestedTest#test2()
    => Exception: expected: <foo> but was: <bar>
```

- `@DisplayName` でインナークラスをアノテートできる。
- 結果の表示にインナークラスの分も反映される。

### `@BeforeEach` とかとの組み合わせ
```java:Junit5Test.java
package sample.junit5;

import org.junit.gen5.api.AfterAll;
import org.junit.gen5.api.AfterEach;
import org.junit.gen5.api.BeforeAll;
import org.junit.gen5.api.BeforeEach;
import org.junit.gen5.api.Nested;
import org.junit.gen5.api.Test;

public class Junit5Test {
    
    @BeforeAll
    public static void beforeAll() {
        System.out.println("beforeAll");
    }
    
    @BeforeEach
    public void before1() {
        System.out.println("  before1");
    }
    
    @Test
    public void test1() {
        System.out.println("    test1");
    }
    
    @AfterEach
    public void after1() {
        System.out.println("  after1");
    }
    
    @AfterAll
    public static void afterAll() {
        System.out.println("afterAll");
    }
    
    @Nested
    public class NestedClass {
        
        @BeforeEach
        public void before2() {
            System.out.println("    before2");
        }
        
        @Test
        public void test2() {
            System.out.println("      test2");
        }
        
        @AfterEach
        public void after2() {
            System.out.println("    after2");
        }
    }
}
```

```text:実行結果
beforeAll
beforeAll
  before1
    before2
      test2
    after2
  after1
afterAll
  before1
    test1
  after1
afterAll
```

- `@BeforeAll` はネストされたクラスも含めて、それぞれのクラスの最初と最後で実行される。
    - ↑の実行結果では、最初と最後の `beforeAll`, `afterAll` が `JUnit5Test` の分で、
    - ２つ目の `beforeAll` と `afterAll` が `NestedClass` の分になっている。
- ネストされたクラスの外にある `@BeforeEach`, `@AfterEach` は、ネストされたクラス内のテストメソッドに対しても有効になる。

### クラスを継承している場合
```java:Junit5Test.java
package sample.junit5;

import org.junit.gen5.api.AfterAll;
import org.junit.gen5.api.AfterEach;
import org.junit.gen5.api.BeforeAll;
import org.junit.gen5.api.BeforeEach;
import org.junit.gen5.api.Nested;
import org.junit.gen5.api.Test;

public class Junit5Test {
    
    @BeforeAll
    public static void beforeAll1() {
        System.out.println("beforeAll1");
    }

    @AfterAll
    public static void afterAll1() {
        System.out.println("afterAll1");
    }
    
    @BeforeEach
    public void before1() {
        System.out.println("    before1");
    }

    @AfterEach
    public void after1() {
        System.out.println("    after1");
    }
    
    public static class Base {
        
        @BeforeAll
        public static void beforeAll2() {
            System.out.println("  beforeAll2");
        }

        @AfterAll
        public static void afterAll2() {
            System.out.println("  afterAll2");
        }
        
        @BeforeEach
        public void before2() {
            System.out.println("      before2");
        }

        @AfterEach
        public void after2() {
            System.out.println("      after2");
        }
    }
    
    @Nested
    public class NestedTest extends Base {

        @BeforeEach
        public void before3() {
            System.out.println("        before3");
        }
        
        @Test
        public void test() {
            System.out.println("          test");
        }

        @AfterEach
        public void after3() {
            System.out.println("        after3");
        }
    }
}
```

```text:実行結果
beforeAll1
beforeAll1
  beforeAll2
    before1
      before2
        before3
          test
        after3
      after2
    after1
  afterAll2
afterAll1
afterAll1
```

- ネストしたクラスの親が `@BeforeEach` や `@BeforeAll` を持つ場合、それらも有効になる。

## `@Tag`
### 基本
```java:Junit5Test.java
package sample.junit5;

import org.junit.gen5.api.Tag;
import org.junit.gen5.api.Test;

public class Junit5Test {
    
    @Test
    @Tag("hoge")
    public void test1() {
        System.out.println("[hoge] test1");
    }

    @Test
    @Tag("fuga")
    public void test2() {
        System.out.println("[fuga] test2");
    }

    @Test
    @Tag("hoge") @Tag("fuga")
    public void test3() {
        System.out.println("[hoge, fuga] test3");
    }

    @Test
    @Tag("hoge") @Tag("piyo")
    public void test4() {
        System.out.println("[hoge, piyo] test4");
    }
}
```

```groovy:build.gradle
junit5 {
    version '5.0.0-ALPHA'
    requireTag 'hoge'
    excludeTag 'piyo'
}
```

```text:実行結果
[hoge] test1
[hoge, fuga] test3
```

- テストメソッドを任意の名前でタグ付けできる。
    - １つのメソッドに複数のタグを設定することもできる。
    - クラスにタグを付けることも可能。
- オプションの `requireTag` または `excludeTag` で、実行するタグを絞り込める。
    - `requireTag` で実行するタグを絞り込む。
    - `excludeTag` で実行しないタグを絞り込む。
    - 両方の条件に一致する場合は、 `excludeTag` が優先される（`test4()`）。

`requireTag`, `excludeTag` で複数のタグを指定したい場合は、以下のように記述する。

```groovy:build.gradle
junit5 {
    version '5.0.0-ALPHA'
    requireTag 'hoge'
    requireTag 'fuga'
}
```

### メタアノテーション
```java:Hoge.java
package sample.junit5;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

import org.junit.gen5.api.Tag;

@Tag("hoge")
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Hoge {
}
```

```java:Junit5Test.java
package sample.junit5;

import org.junit.gen5.api.Test;

public class Junit5Test {
    
    @Test
    @Hoge
    public void test1() {
        System.out.println("test1");
    }

    @Test
    public void test2() {
        System.out.println("test2");
    }
}
```

```groovy:build.gradle
junit5 {
    version '5.0.0-ALPHA'
    requireTag 'hoge'
}
```

```text:実行結果
test1
```

- `@Tag` などでアノテートした独自のアノテーションを作ることができる。
- 複数のアノテーションをまとめることもできる。

## `@Disabled`
```java:Junit5Test.java
package sample.junit5;

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

public class Junit5Test {
    
    @Test
    public void test1() {
        System.out.println("test1");
    }

    @Test
    @Disabled
    public void test2() {
        System.out.println("test2");
    }
}
```

```text:実行結果
test1
```

- `@Disabled` でアノテートされたメソッド（クラス）はテスト対象外になる。

# Assumptions（特定の条件が満たされているときだけテストを続行する）
```java:Junit5Test.java
package sample.junit5;

import static org.junit.gen5.api.Assumptions.*;

import org.junit.gen5.api.Test;

public class Junit5Test {
    
    @Test
    public void test1() {
        assumeTrue(true);
        System.out.println("test1");
    }
    
    @Test
    public void test2() {
        assumeTrue(false);
        System.out.println("test2");
    }
}
```

```text:実行結果
test1

Test run finished after 76 ms
[         2 tests found     ]
[         0 tests skipped   ]
[         2 tests started   ]
[         1 tests aborted   ]
[         1 tests successful]
[         0 tests failed    ]
```

- `Assumptions` に定義されている `static` メソッドを使う。
- 条件が満たされたときだけ、テストが続行される。
    - 条件が満たされなかったテストは `aborted` にカウントされている。
- ユーザーガイドにあった利用例では、環境変数の値を見て環境によって実行するテストを動的に切り替えたりするのに使うらしい。

# メソッド引数
## テスト名を受け取る
```java:Junit5Test.java
package sample.junit5;

import org.junit.gen5.api.BeforeEach;
import org.junit.gen5.api.DisplayName;
import org.junit.gen5.api.Test;
import org.junit.gen5.api.TestInfo;

public class Junit5Test {
    
    @BeforeEach
    public void before(TestInfo info) {
        System.out.println(
            "[before]\n" +
            "displayName=" + info.getDisplayName() + "\n" +
            "name=" + info.getName()
        );
    }
    
    @Test
    @DisplayName("テスト")
    public void test(TestInfo info) {
        System.out.println(
            "[test]\n" +
            "displayName=" + info.getDisplayName() + "\n" +
            "name=" + info.getName()
        );
    }
}
```

```text:実行結果
[before]
displayName=テスト
name=sample.junit5.Junit5Test#test(org.junit.gen5.api.TestInfo)
[test]
displayName=テスト
name=sample.junit5.Junit5Test#test(org.junit.gen5.api.TestInfo)
```

- `TestInfo` を引数として受け取るようにすると、自動的にインスタンスが渡される。
- `@BeforeEach` などでも受け取れる。

# JUnit4 を使って JUnit5 を動かす
JUnit4 を使って JUnit5 を動かすことができる。

この仕組を利用すれば、既存の JUnit4 をサポートしている IDE やビルドツール上から JUnit5 のテストを動かすことができる。

```groovy:build.gradle
apply plugin: 'java'

sourceCompatibility = '1.8'
targetCompatibility = '1.8'
[compileJava, compileTestJava]*.options*.encoding = 'UTF-8'

repositories {
    mavenCentral()
}

dependencies {
    testCompile 'junit:junit:4.12'
    testCompile 'org.junit:junit5-api:5.0.0-ALPHA'
    testCompile 'org.junit:junit4-runner:5.0.0-ALPHA'
    testCompile 'org.junit:junit5-engine:5.0.0-ALPHA'
}
```

- JUnit4 に加えて、以下を設定する。
    - `junit5-api`
    - `junit4-runner`
    - `junit5-engine`

```java:Junit5Test.java
package sample.junit5;

import static org.junit.gen5.api.Assertions.*;

import org.junit.gen5.api.DisplayName;
import org.junit.gen5.api.Test;
import org.junit.gen5.junit4.runner.JUnit5;
import org.junit.runner.RunWith;

@RunWith(JUnit5.class)
public class Junit5Test {
    
    @Test
    @DisplayName("テストです")
    public void test() {
        assertEquals("hoge", "fuga");
    }
}
```

- `@RunWith` で `JUnit5.class` を指定することで、 JUnit4 上で JUnit5 を実行できるようになる。

```text:実行結果
> gradle -q test
1 test completed, 1 failed

FAILURE: Build failed with an exception.
```

- Gradle から実行できた。
- Eclipse プラグインを使って Eclipse プロジェクト化してインポートすれば、普通に Eclipse 上でも実行できる。

# Extension Model
JUnit5 では、テストを拡張するための仕組みとして Extension Model というのが用意されている。

## 基本
```java:MyExtend.java
package sample.junit5;

import org.junit.gen5.api.extension.ConditionEvaluationResult;
import org.junit.gen5.api.extension.TestExecutionCondition;
import org.junit.gen5.api.extension.TestExtensionContext;

public class MyExtend implements TestExecutionCondition {

    @Override
    public ConditionEvaluationResult evaluate(TestExtensionContext context) {
        String name = context.getDisplayName();
        
        if (name.startsWith("hoge")) {
            return ConditionEvaluationResult.enabled("hoge で始まってるので");
        } else {
            return ConditionEvaluationResult.disabled("hoge で始まってないので");
        }
    }
}
```

```java:Junit5Test.java
package sample.junit5;

import org.junit.gen5.api.Test;
import org.junit.gen5.api.extension.ExtendWith;

@ExtendWith(MyExtend.class)
public class Junit5Test {
    
    @Test
    public void hoge() {
        System.out.println("hoge");
    }
    
    @Test
    public void fuga() {
        System.out.println("fuga");
    }
}
```

```text:実行結果
hoge
```

- 拡張用のクラスは、 `ExtensionPoint` というインターフェースを実装することで作成する。
    - ただし、 `ExtensionPoint` 自体はただのマーカーインターフェースで、実際はそれを継承したインターフェースを実装する。
    - ここでは `TestExecutionCondition` というインターフェースを実装している。
- 作成したクラスは、 `@ExtendWith` アノテーションの引数に、その `Class` オブジェクトを渡すことで利用できる。

## ContainerExecutionCondition
```java:MyExtend.java
package sample.junit5;

import org.junit.gen5.api.extension.ConditionEvaluationResult;
import org.junit.gen5.api.extension.ContainerExecutionCondition;
import org.junit.gen5.api.extension.ContainerExtensionContext;

public class MyExtend implements ContainerExecutionCondition {

    @Override
    public ConditionEvaluationResult evaluate(ContainerExtensionContext context) {
        String displayName = context.getDisplayName();
        
        if (displayName.contains("Hoge")) {
            return ConditionEvaluationResult.enabled("Hoge なので");
        } else {
            return ConditionEvaluationResult.disabled("Hoge でないので");
        }
    }

}
```

```java:Junit5Test.java
package sample.junit5;

import org.junit.gen5.api.Nested;
import org.junit.gen5.api.Test;
import org.junit.gen5.api.extension.ExtendWith;

public class Junit5Test {
    
    @Nested
    @ExtendWith(MyExtend.class)
    public class Hoge {
        
        @Test
        public void test() {
            System.out.println("Hoge.test");
        }
    }
    
    @Nested
    @ExtendWith(MyExtend.class)
    public class Fuga {
        
        @Test
        public void test() {
            System.out.println("Fuga.test");
        }
    }
}
```

```text:実行結果
Hoge.test
```

- テストクラス単位でテストを実行するかどうかを制御できる。
- `ConditionEvaluationResult.enabled()` で生成したインスタンスを返せばテストが実行され、 `ConditionEvaluationResult.disabled()` で生成したインスタンスを返せばテストをスキップする。

## TestExecutionCondition
```java:MyExtend.java
package sample.junit5;

import org.junit.gen5.api.extension.ConditionEvaluationResult;
import org.junit.gen5.api.extension.TestExecutionCondition;
import org.junit.gen5.api.extension.TestExtensionContext;

public class MyExtend implements TestExecutionCondition {

    @Override
    public ConditionEvaluationResult evaluate(TestExtensionContext context) {
        String displayName = context.getDisplayName();
        
        if ("hoge".equals(displayName)) {
            return ConditionEvaluationResult.enabled("hoge なので");
        } else {
            return ConditionEvaluationResult.disabled("hoge じゃないので");
        }
    }
}
```

```java:Junit5Test.java
package sample.junit5;

import org.junit.gen5.api.Test;
import org.junit.gen5.api.extension.ExtendWith;

@ExtendWith(MyExtend.class)
public class Junit5Test {

    @Test
    public void hoge() {
        System.out.println("hoge");
    }

    @Test
    public void fuga() {
        System.out.println("fuga");
    }
}
```

```text:実行結果
hoge
```

- メソッド単位でテストを実行するかどうかを制御できる。

## InstancePostProcessor
```java:MyExtend.java
package sample.junit5;

import org.junit.gen5.api.extension.InstancePostProcessor;
import org.junit.gen5.api.extension.TestExtensionContext;

public class MyExtend implements InstancePostProcessor {

    @Override
    public void postProcessTestInstance(TestExtensionContext context) throws Exception {
        Object testInstance = context.getTestInstance();
        System.out.println(testInstance.getClass());
    }
}
```

```java:Junit5Test.java
package sample.junit5;

import org.junit.gen5.api.BeforeAll;
import org.junit.gen5.api.BeforeEach;
import org.junit.gen5.api.Test;
import org.junit.gen5.api.extension.ExtendWith;

@ExtendWith(MyExtend.class)
public class Junit5Test {
    
    @BeforeAll
    public static void beforeAll() {
        System.out.println("beforeAll");
    }
    
    @BeforeEach
    public void before() {
        System.out.println("before");
    }
    
    @Test
    public void test1() {
        System.out.println("test1");
    }
    
    @Test
    public void test2() {
        System.out.println("test2");
    }
}
```

```text:実行結果
beforeAll
class sample.junit5.Junit5Test
before
test1
class sample.junit5.Junit5Test
before
test2
```

- テストクラスのインスタンスが生成されるたびに実行する処理を定義できる。
- テストインスタンスに依存する他のインスタンスをインジェクションしたいときなどに利用する。

## MethodParameterResolver
### 基本
```java:MyExtend.java
package sample.junit5;

import java.lang.reflect.Parameter;

import org.junit.gen5.api.extension.ExtensionContext;
import org.junit.gen5.api.extension.MethodInvocationContext;
import org.junit.gen5.api.extension.MethodParameterResolver;
import org.junit.gen5.api.extension.ParameterResolutionException;

public class MyExtend implements MethodParameterResolver {

    @Override
    public boolean supports(Parameter parameter, MethodInvocationContext methodInvocationContext, ExtensionContext extensionContext) throws ParameterResolutionException {
        System.out.println("supports()");
        return String.class.equals(parameter.getType());
    }

    @Override
    public Object resolve(Parameter parameter, MethodInvocationContext methodInvocationContext, ExtensionContext extensionContext) throws ParameterResolutionException {
        System.out.println("resolve()");
        return "hoge";
    }
}
```

```java:Junit5Test.java
package sample.junit5;

import org.junit.gen5.api.Test;
import org.junit.gen5.api.extension.ExtendWith;

@ExtendWith(MyExtend.class)
public class Junit5Test {
    
    @Test
    public void test1() {
        System.out.println("test1");
    }
    
    @Test
    public void test2(String string) {
        System.out.println("test2 string=" + string);
    }
}
```

```text:実行結果
test1
supports()
resolve()
test2 string=hoge
```

- メソッドの引数を解決することができる。
- `supports()` と `resolve()` を実装する。
    - どちらも、次の３つの引数を受け取る。
        1. `Parameter`：対象の引数のメタ情報
        1. `MethodInvocationContext`：対象のテストメソッドのメタ情報
        1. `ExtensionContext`：対象テストのメタ情報
    - `supports()` は、対象の引数が処理対象となるかどうかを判定して `boolean` で返す。
    - `resolve()` は、引数に渡す値を返す。
- テストメソッドが引数を持つ場合に、 `supports()` メソッドが呼ばれる。

### サポートしない引数を持つ場合
```java:Junit5Test.java
package sample.junit5;

import org.junit.gen5.api.Test;
import org.junit.gen5.api.extension.ExtendWith;

@ExtendWith(MyExtend.class)
public class Junit5Test {
    
    @Test
    public void test(int number) {
        System.out.println("test");
    }
}
```

```text:実行結果
supports()

Test failures (1):
  JUnit 5:sample.junit5.Junit5Test:test
    sample.junit5.Junit5Test#test(int)
    => Exception: No MethodParameterResolver registered for parameter [int arg0] in method [public void sample.junit5.Junit5Test.test(int)].
```

- エラーになる。

### 引数が複数存在する場合
```java:
package sample.junit5;

import org.junit.gen5.api.Test;
import org.junit.gen5.api.extension.ExtendWith;

@ExtendWith(MyExtend.class)
public class Junit5Test {
    
    @Test
    public void test(String str1, String str2) {
        System.out.println("test str1=" + str1 + ", str2=" + str2);
    }
}
```

```text:実行結果
supports()
resolve()
supports()
resolve()
test str1=hoge, str2=hoge
```

- それぞれの引数に対して `supports()` - `resolve()` が呼ばれる。

## ライフサイクル・コールバック
```java:MyExtend.java
package sample.junit5;

import org.junit.gen5.api.extension.BeforeEachExtensionPoint;
import org.junit.gen5.api.extension.TestExtensionContext;

public class MyExtend implements BeforeEachExtensionPoint {

    @Override
    public void beforeEach(TestExtensionContext context) throws Exception {
        System.out.println("MyExtend.beforeEach()");
    }
}
```

```java:Junit5Test.java
package sample.junit5;

import org.junit.gen5.api.Test;
import org.junit.gen5.api.extension.ExtendWith;

@ExtendWith(MyExtend.class)
public class Junit5Test {
    
    @Test
    public void test1() {
        System.out.println("test1");
    }
    
    @Test
    public void test2() {
        System.out.println("test2");
    }
}
```

```text:実行結果
MyExtend.beforeEach()
test1
MyExtend.beforeEach()
test2
```

- `BeforeEachExtensionPoint` を実装することで、テストメソッドの前処理を実装できる。
- これ以外には、以下のインターフェースが用意されている。
    - `AfterEachExtensionPoint`
    - `BeforeAllExtensionPoint`
    - `AfterAllExtensionPoint`
- それぞれ、名前から予想されるとおりに動くと思う。

## ExceptionHandlerExtensionPoint
```java:MyExtend.java
package sample.junit5;

import org.junit.gen5.api.extension.ExceptionHandlerExtensionPoint;
import org.junit.gen5.api.extension.TestExtensionContext;

public class MyExtend implements ExceptionHandlerExtensionPoint {

    @Override
    public void handleException(TestExtensionContext context, Throwable throwable) throws Throwable {
        System.out.println("handleException() class=" + throwable.getClass() + ", message=" + throwable.getMessage());
    }
}
```

```java:Junit5Test.java
package sample.junit5;

import static org.junit.gen5.api.Assertions.*;

import org.junit.gen5.api.Test;
import org.junit.gen5.api.extension.ExtendWith;

@ExtendWith(MyExtend.class)
public class Junit5Test {
    
    @Test
    public void test1() {
        assertEquals("hoge", "fuga");
    }
    
    @Test
    public void test2() {
        throw new NullPointerException("test");
    }
}
```

```text:実行結果
handleException() class=class org.opentest4j.AssertionFailedError, message=expected: <hoge> but was: <fuga>
handleException() class=class java.lang.NullPointerException, message=test

Test run finished after 70 ms
[         2 tests found     ]
[         0 tests skipped   ]
[         2 tests started   ]
[         0 tests aborted   ]
[         2 tests successful]
[         0 tests failed    ]
```

- スローされた例外をハンドリングできる。
- アサーションで失敗になったときの例外もハンドリングされてしまう。
- ハンドリングした例外をスローし直さないと、<ins>テストが成功扱いになる！</ins>（これはこわい）。

# 所感
- 良いなと思ったところ
    - テスト名が任意の文字列で定義できるようになったこと。
        - だからと言って、テストメソッドを適当な名前にすると、今まで Eclipse の「クイック・アウトライン」とかでテストメソッドの俯瞰をしていた人（俺）は辛くなりそう。
        - しかし、 `@DisplayName` と重複する名前つけるのはなんだかなぁ。
    - ネストされたテストが標準で、自然な形でサポートされるようになった。
    - 拡張方法が `@Rule` に比べれば単純な気がする。
    - 拡張ポイントも色々あって夢が広がる。
- といっても、既に JUnit4 で動いているテストをあえて JUnit5 にする必要はないかなぁと思う。
    - 4.12 ならネストしたテストもできるので。
- IDE やビルドツールが標準で JUnit5 をサポートし始めたら、新規に作るテストは JUnit5 にしていく感じでいいと思う。

# 参考
- [JUnit 5 User Guide](http://junit-team.github.io/junit5/)