mike-neckのブログ

Java or Groovy or Swift or Golang

JUnit5入門(2) - アサーション

前回 の続き

今回は値の比較、アサーションについて。


JUnit5が提供するアサーション

JUnit5はJUnit4とほぼ同等のAssertion機能を提供している。また、より高度なAssertionを求める場合は、サードパーティライブラリーの利用を勧めている。

JUnit5のAssertionの基本的なもの

以下の通り。なお、assertThat はなくなった。また、メッセージはこれまでは引数の最初に渡していたが、引数の最後に渡すようになった。

メソッド 比較内容
Assertions#assertEquals 左(期待値)と右(実際の値)が等しいことを確認する
Assertions#assertNotEquals 左(期待値)と右(実際の値)が等しくないことを確認する
Assertions#assertTrue 値が true であることを確認する
Assertions#assertFalse 値が false であることを確認する
Assertions#assertSame 左(期待するオブジェクト)と右(実際のオブジェクト)が同じインスタンスであることを確認する
Assertions#assertNotSame 左(期待しないオブジェクト)と右(実際のオブジェクト)が同じインスタンスでないことを確認する
Assertions#assertNull 値が null であることを確認する
Assertions#assertNotNull 値が null でないことを確認する
Assertions#assertArrayEquals 左の配列(期待値)と右の配列(実際の値)が順番、値ともに等しい配列であることを確認する
サンプルコード
@Test
void assertVariation() {
  assertEquals("2nd test", getText(), "メッセージは3つめの引数");
  assertEquals("2nd test", getText(), () -> "メッセージは Supplier でも渡せる");
  assertTrue(true, "これは [true]");
  assertFalse(false); // メッセージなし
  assertNotEquals("いろいろなassertion", getText());
  assertNotSame(new Something(), new Something(), () -> "notSame は reference の比較");
  assertSame(something, something);
  assertNotSame(something, new Something(), () -> "型が違うけど比較できる...");
  assertNull(returnsNull());
  assertNotNull(getText());
  assertArrayEquals(new int[]{1,2,3,4,5,6,7,8}, intArray(), () -> "配列のテスト");
}

private static final Object something = new Object();
private static class Something {}
@NotNull
private String getText() {
    return "2nd test";
}
@Nullable
private String returnsNull() {
  return null;
}
@NotNull
private static int[] intArray() {
  return new int[]{1, 2, 3, 4, 5, 6, 7, 8};
}

追加になったAssertion

Iterable の比較をおこなうassertionメソッド(Assertions#assertIterableEquals)が追加された。

サンプルコード
@Test
void assertionForCollections() {
  assertIterableEquals(Arrays.asList(
      new Identity<>(0)
      , new Identity<>(1)
      , new Identity<>(2)
      , new Identity<>(3)
  ), identityList());
}

@Data @RequiredArgsConstructor
static class Identity<T> {
  private final T id;
}
@NotNull
private List<Identity> identityList() {
  return IntStream.range(0, 4)
      .mapToObj(Identity::new)
      .collect(toList());
}

便利なAssertion

Assertions#assertAll という複数のAssertをサポートするメソッドが追加された。

可変長引数で渡された Executable(Assertionの式)または Stream<Executable> を受取りすべてのAssertionをすべて実行し、どれがfailしたかエラー表示する。

サンプルコード
@Test
void assertAllThatFails() {
  Stream<Executable> tests = IntStream.rangeClosed(1, 7)
      .mapToObj(i -> () -> {
        log.info("This is assertion[{}]", i);
        assertEquals(i, i + (i % 3 == 0? 1 : 0), () -> "assertion" + i);
      });
  assertAll(tests);
}
実行結果
19:06:46.940 [INFO  com.example.ex2.FourthTest] - This is assertion[1]
19:06:46.945 [INFO  com.example.ex2.FourthTest] - This is assertion[2]
19:06:46.946 [INFO  com.example.ex2.FourthTest] - This is assertion[3]
19:06:46.947 [INFO  com.example.ex2.FourthTest] - This is assertion[4]
19:06:46.947 [INFO  com.example.ex2.FourthTest] - This is assertion[5]
19:06:46.948 [INFO  com.example.ex2.FourthTest] - This is assertion[6]
19:06:46.948 [INFO  com.example.ex2.FourthTest] - This is assertion[7]

Failures (1):
  JUnit Jupiter:FourthTest:assertAllThatFails()
    MethodSource [className = 'com.example.ex2.FourthTest', methodName = 'assertAllThatFails', methodParameterTypes = '']
    => org.opentest4j.MultipleFailuresError: Multiple Failures (2 failures)
        assertion3 ==> expected: <3> but was: <4>
        assertion6 ==> expected: <6> but was: <7>

なお、複数のAssertionを実行してもテストは1としてしかカウントされない。

JUnit5のAssumption

Assumptionは条件に合致した場合のみテストを実行し、条件に合致しない場合にはテストをスキップするヘルパーメソッド。JUnit4にあった assumeThatassumeNotNullassumeNoException がなくなり、 assumeTrue が残った。また、 assumeFalseassumingThat が追加された。

サンプルコード
@Test
void thisTestDoNotRunOnSunday() {
  final DayOfWeek dayOfWeek = today().getDayOfWeek();
  assumeFalse(SUNDAY.equals(dayOfWeek));
  log.info("日曜以外");
  assertFalse("日".equals(dayOfWeek.getDisplayName(TextStyle.SHORT, Locale.JAPAN)));
}
@Test
void thisTestRunOnSunday() {
  final DayOfWeek dayOfWeek = today().getDayOfWeek();
  assumeTrue(SUNDAY.equals(dayOfWeek));
  log.info("日曜");
  assertEquals("日", dayOfWeek.getDisplayName(TextStyle.SHORT, Locale.JAPAN));
}

private static final ZoneId ZONE_ID = ZoneId.of("Asia/Tokyo");
@NotNull @Contract(pure = true)
private static LocalDate today() {
  return LocalDate.now(ZONE_ID);
}
実行結果
Test run finished after 10468 ms
[         2 containers found      ]
[         0 containers skipped    ]
[         2 containers started    ]
[         0 containers aborted    ]
[         2 containers successful ]
[         0 containers failed     ]
[         1 tests found           ]
[         0 tests skipped         ]
[         1 tests started         ]
[         1 tests aborted         ]
[         0 tests successful      ]
[         0 tests failed          ]

assumingThat は最初の引数の BooleanSuppliertrue を返した場合のみ後続の Executable(Assertion)が実行される。

assumingThat のサンプルコード
@Test
void thisTestDoNotRunOnSunday() {
  final DayOfWeek dayOfWeek = today().getDayOfWeek();
  assumingThat(
      () -> !SUNDAY.equals(dayOfWeek)
      , () -> assertFalse("日".equals(dayOfWeek.getDisplayName(TextStyle.SHORT, Locale.JAPAN)))
  );
}
@Test
void thisTestRunOnSunday() {
  final DayOfWeek dayOfWeek = today().getDayOfWeek();
  assumingThat(
      () -> SUNDAY.equals(dayOfWeek)
      , () -> assertEquals("日", dayOfWeek.getDisplayName(TextStyle.SHORT, Locale.JAPAN))
  );
}
実行結果
Test run finished after 10261 ms
[         2 containers found      ]
[         0 containers skipped    ]
[         2 containers started    ]
[         0 containers aborted    ]
[         2 containers successful ]
[         0 containers failed     ]
[         2 tests found           ]
[         0 tests skipped         ]
[         2 tests started         ]
[         0 tests aborted         ]
[         2 tests successful      ]
[         0 tests failed          ]

コードはこちらから

github.com