JUnit 5 M1 をやってみよう


本資料について


自己紹介

  • 福原和朗 @kazurof
    • Qiita, twitter, Facebook とかやってます。
  • 所属
    • GMOリサーチ
      • アンケートシステムの開発保守

本日のお題

  • JUnit 5.0.0-M1 が、2016/7/7にリリースされました
    • 七夕さま
  • いじってみた感触など並べてみます。
  • 注意:Milestoneなので今後変更入るかもしれません。

そもそもの大前提

  • 元となるパッケージ名が変わった。
    • org.junit.jupiter
    • org.junit.platform など
    • JUnit4 とのAPI互換性はありません。
  • Java8以降をサポート。
    • Java7以前はさようなら。

JUnit5 のサブモジュール

  • JUnit Platform
    • テストの実行の全体枠組み
  • JUnit Jupiter
    • テスト開発者・拡張の開発者向けAPI
  • JUnit Vintage
    • JUnit3, 4 の実行API

Jupiter という名前の由来


JUnit5で何が変わったか?

  • 基本的なやり方は変わっていない。

@Test テストメソッド
@BeforeEach 事前処理
@AfterEach 事後処理


サンプル

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

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

無くなった(?)もの

  • assertThat

アサーション系の機能は好きなもの使ってね、というスタンスらしい。


無くなった(?)もの

  • TestSuite
    • 複数のテストを木構造のようにグループ化
    • テストメソッドに"タグ"をつけるやり方に移行
      • @Tag("nantoka")
    • 従前、Categoriesと呼ばれていた機能。
      • 以前はexperimentalだった。

JUnit4 のTestSuite

//こんなボイラープレートコードは大変かも!
package tryjunit4.suite;
import org.junit.runner.RunWith;
import org.junit.runners.Suite;
import org.junit.runners.Suite.SuiteClasses; 
@RunWith(Suite.class)
@SuiteClasses( { SampleTest.class, AnotherTest.class,
        tryjunit4.subpack.AllTests.class })
public class AllTests {
}

JUnit5 の@Tagの例

import org.junit.jupiter.api.Tag;
import org.junit.jupiter.api.Test;
//"fast"タグを指定してテスト実行などできる
@Tag("fast")
class TaggingDemo {
    @Test
    @Tag("taxes")
    void testingTaxCalculation() {
    }
}

無くなった(?)もの

  • @Ruleによる拡張
    • なくなる見込み。多分。
    • そもそも、フィールドを使う形なので大仰になりがちだった。
    • 新たに設計された拡張が導入される。
    • @ExtendWith

import org.junit.rules.TestName;
public class TestNameTest {
    //Ruleはフィールドを使うので
    //別のテストで使ってないか注意必要
    @Rule 
    public TestName name = new TestName();
    @Test
    public void testA() {
        assertEquals("testA", name.getMethodName());
    }
    @Test
    public void testB() {
        assertEquals("testB", name.getMethodName());
    }
}

TestNameはどうなった?

  • テスト名をとれる@RuleのAPI
  • 消えます。
  • 代わりに、テストメソッドのパラメータから取れます。
    • テスト名含む各種情報も取れます。
    • TestInfo

テストにパラメータを渡せる

  • どんなパラメータを渡すかもカスタマイズ可能
    • ParameterResolver によるしくみ
  • 組み込みのResolver
    • TestInfoParameterResolver
    • TestReporterParameterResolver
      • テストでログを吐きたい時の委譲先

import org.junit.jupiter.api.TestInfo;
// importは適当に省略してます。
class TestInfoDemo {
  @Test
  @Tag("my tag")
  void test1(TestInfo testInfo) {
    assertTrue(testInfo.getTags().contains("my tag"));
  }
  @Test
  void test2() {   }
}

できるようになったこと

  • テスト名を個別につけられるようになった。
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
class DisplayNameDemo {
  @Test
  @DisplayName("なんとかてすと")
  void testWithDisplayNameContainingSpaces() {
  }
}

できるようになったこと 

  • テストクラスを内部クラスとする機能が標準になりました。
    • experimental ではなくなりました。
    • @Nested

できるようになったこと 

  • DynamicTest
    • テストを作る処理を書くことができる。
      • 動的にテストが作られる。

parameterized の代替

テスト対象メソッドに、テストデータを多数渡してテストしたい。

  • テストしたい処理
  • テストデータを作る処理

以上をラムダで書いておくと、JUnit5が組み合わせて実行してくれます。動的です。


// JUnit4 だとやっぱりフィールドを使うので、つかいにくい
@RunWith(Parameterized.class)
public class NoConstructorTest {
  @Parameters
  public static Iterable<Object[]> data() {
      return Arrays.asList(new Object[][] { { 1, 1 }, { -2, 2 } });
  }
  @Parameter
  public int fInput;
  @Parameter(1)
  public int fExpected;
  @Test
  public void testNantoka() {
    assertEquals(fExpected, Math.abs(fInput));
  }
}

JUnit5 M1 の個人的な感想

  • 普通の機能はそのまま。
  • experimentalで便利な機能を標準機能に昇格させる。
  • テストメソッドだけで機能が完結するような設計にする。
    • Rule の廃止。ExtendWithによる新設計

興味を持ったら。

  • https://github.com/junit-team/junit5-samples
    • サンプルプロジェクト
    • gradleのJUnit5プラグインとか試せます。
    • コードに絵文字が普通に入っているので環境によっては修正必要かも

IDE

  • IntelliJ IDEA 2016.2 がおすすめ。
    • テストクラス右クリックからテスト実行できます。
    • テストメソッドだと実行はできるが謎のエラーが出る。

JUnit4なプロジェクトの場合

  • @RunWith(JUnitPlatform.class)で、ツール側にはJUnit4のテストだとみせかけつつ、中身はJUnit5のテストを実行するとができる。

import static org.junit.jupiter.api.Assertions.fail;
import org.junit.jupiter.api.Test;
import org.junit.platform.runner.JUnitPlatform;
import org.junit.runner.RunWith;

@RunWith(JUnitPlatform.class)
public class JUnit4ClassDemo {
    @Test
    void succeedingTest() {
    }
    @Test
    void failingTest() {
        fail("Failing for failing's sake.");
    }
}

今回触れてないこと

  • 例外を想定するテスト
    • 簡潔に書けるようになりました。
    • ExpectedExceptionというRuleは消えます。
  • Interface の defaultメソッドのテスト
  • 他にも色々あるとおもいます。多分。

参考文献


ご清聴ありがとうございました!