アサーションの種類
JUnit の assert 系のメソッドには、以下のものがあります。
- assertEquals:2 つの値が等しいかどうかをテスト
- assertTrue:引数の値が true かどうかをテスト
- assertFalse:引数の値が false かどうかをテスト
- assertNull:引数の値が null かどうかをテスト
- assertNotNull:引数の値が null でないかどうかをテスト
- assertSame:2 つの参照が同じインスタンスかどうかをテスト
- assertNotSame:2 つの参照が異なるインスタンスかどうかをテスト
- assertArrayEquals:2 つの配列が等しいかどうかをテスト
- assertThrows:指定した関数が例外をスローするかどうかをテスト
- assertNotEquals:2 つの値が等しくないかどうかをテスト
- assertThat:値が指定された条件を満たすかどうかを確認
- assertThrows:例外がスローされるかどうかを確認
- assertDoesNotThrow:例外がスローされないかどうかを確認
- assertTimeout:指定された時間内に実行が終了するかどうかを確認
- assertTimeoutPreemptively:指定された時間内に実行が終了するかどうかを確認
- assertLinesMatch:2つの文字列が一致するかどうかを確認
- assertAll:複数のアサーションをまとめて確認
※ assertTimeout と assertTimeoutPreemptively の違い
assertTimeoutPreemptivelyは、実行が長引いた場合にアサーションを実行して、実行を中断できるようになっています。
import static java.time.Duration.ofMillis; import static java.time.Duration.ofMinutes; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertTimeout; import static org.junit.jupiter.api.Assertions.assertTimeoutPreemptively; import static org.junit.jupiter.api.Assertions.assertTrue; import org.junit.jupiter.api.Test; class AssertionsDemo { @Test void standardAssertions() { assertEquals(2, 2); assertEquals(4, 4, "省略可能なアサーションメッセージは最後のパラメーター"); assertTrue('a' < 'b', () -> "アサーションメッセージは遅延評価できる -- " + "不必要に複雑なメッセージを構築するコストを割けるために"); } @Test void timeoutNotExceeded() { // 次のアサーションは成功する。 assertTimeout(ofMinutes(2), () -> { // 2分未満で終わるタスクを実行する。 }); } @Test void timeoutNotExceededWithResult() { // 次のアサーションは成功し、指定されたオブジェクトを返す。 String actualResult = assertTimeout(ofMinutes(2), () -> { return "a result"; }); assertEquals("a result", actualResult); } @Test void timeoutNotExceededWithMethod() { // 次のアサーションは、メソッド参照を実行してオブジェクトを返す。 String actualGreeting = assertTimeout(ofMinutes(2), AssertionsDemo::greeting); assertEquals("Hello, World!", actualGreeting); } @Test void timeoutExceeded() { // 次のアサーションは、以下のようなエラーメッセージを出して失敗する: // execution exceeded timeout of 10 ms by 91 ms assertTimeout(ofMillis(10), () -> { // 10ミリ秒より時間のかかるタスクをシミュレートする。 Thread.sleep(100); }); } @Test void timeoutExceededWithPreemptiveTermination() { // 次のアサーションは、以下のようなエラーメッセージを出して失敗する: // execution timed out after 10 ms assertTimeoutPreemptively(ofMillis(10), () -> { // 10ミリ秒より時間のかかるタスクをシミュレートする。 Thread.sleep(100); }); } private static String greeting() { return "Hello, World!"; } }
ラムダを使った assertAll()
と assertThrows()
引用元:初めてのJUnit 5
JUnit 5のorg.junit.gen5.Assertionsクラスには、テスト・メソッドの条件を扱うassertEquals、assertTrue、assertNull、assertSameなどのstaticアサーション・メソッドと、それぞれの否定版となるメソッドが含まれています。JUnit 5では、これらのアサーション・メソッドでラムダ式を活用できるように、各メソッドをオーバーロードしてjava.util.function.Supplierのインスタンスを受け取るようにしたメソッドが提供されています。これによってアサーション・メッセージを遅延評価できるようになるため、複雑になる可能性がある計算を実際にアサーションが失敗するまで遅らせることができます。
関数型Interface の Executable がラムダで実行されます。
- Interface Executable
Assertions.assertAll(Executable...)
Assertions.assertAll(String, Executable...)
Assertions.assertThrows(Class, Executable)
assertAll()
一つ一つの assert を途中で止めずに一気に評価したい場合に assertAll() を使用すると便利です。複数のアサーションの結果を一括で確認することができます。
assertEquals 等は、テストに失敗した時点でエラーが発生する(AssertionFailedErrorがスローされる)ので、それ以降のテストは実行されません。assert が多いとそれでは非常に面倒な場面があります。複数のテストが失敗しても、エラーがスローされるのは1回だけ(MultipleFailuresError)となります。
これでアサーションの結果を1つ直しては次のアサーションが失敗し、それを直すとまた次が、といったことを回避できます。また、アサーションのグループ化という側面もあります。
@Test void groupedAssertions() { // アサーションをグループ化すると、すべてのアサーションが一度に実行され、 // すべての失敗がまとめて報告される。 assertAll("person", () -> assertEquals("John", person.getFirstName()), () -> assertEquals("Doe", person.getLastName()) ); } @Test void dependentAssertions() { // コードブロック内でアサーションが失敗すると、同じブロック内の後続のコードはスキップされる。 assertAll("properties", () -> { String firstName = person.getFirstName(); assertNotNull(firstName); // 上のアサーションが成功した場合のみ実行される。 assertAll("first name", () -> assertTrue(firstName.startsWith("J")), () -> assertTrue(firstName.endsWith("n")) ); }, () -> { // グループ化されたアサーションは、first name のアサーションとは独立して実行される。 String lastName = person.getLastName(); assertNotNull(lastName); // 上のアサーションが成功した場合のみ実行される。 assertAll("last name", () -> assertTrue(lastName.startsWith("D")), () -> assertTrue(lastName.endsWith("e")) ); } ); }
org.opentest4j.MultipleFailuresError: person (2 failures) name ==> expected: <aaa> but was: <zzz> age ==> expected: <99> but was: <20> at org.junit.jupiter.api.AssertAll.assertAll(AssertAll.java:66) at org.junit.jupiter.api.AssertAll.assertAll(AssertAll.java:44) at org.junit.jupiter.api.AssertAll.assertAll(AssertAll.java:38) at org.junit.jupiter.api.Assertions.assertAll(Assertions.java:1039) at com.example.AssertTest.all(AssertTest.java:115) ~~~~~~~~~~~~~~
assertThrows()
assertThrows は例外の発生を確認する assert メソッドです。JUnit4ではテストメソッドから例外で抜け出たときに検証する方式だったので、例外発生後の状態を確認するようなテストが書きにくかった面がありました。別記事の「モックの使用例」で記述している try-catch などで冗長に行う必要がありました。
モック入門『考え方と使い分けについて』 - よしたろうブログ
public class ServiceTest { @InjectMocks private UserRepository mockUserRepository; @Mock private UserDao mockUserDao; @BeforeEach public void before() { UserInfo entity = new UserInfo(); } /** * ユーザ情報削除時の異常系テスト * DomaException を発生させる */ @Test public void DeleteExecutionErrorTest() { entity.setUserCd("userCd78901234567893"); when(mockUserDao.delete(any()) .thenThrow(new DomaException(Message.DOHA0001,"")); try { mockUserRepository.delete(entity); fail(); } catch (Exception expected){ assertTrue(expected instanceof DomaException); } }
assertThrows を使えばこの問題を解消することができます。発生した例外を受け取り、例外が保持している情報をチェックすることも出来る様になりました。assertThrowsの第1引数で「発生するであろう例外」のクラスを指定し、第2引数(ラムダ式)でテスト対象の処理を実行する構文です。
@Test void exceptionTesting() { // 例外発生を期待する。例外発生後も処理ができる Throwable exception = assertThrows(IllegalArgumentException.class, () -> { throw new IllegalArgumentException("a message"); }); assertEquals("a message", exception.getMessage()); }