S-JIS[2017-09-15/2022-08-19] 変更履歴
JUnit5のテストの記述例。
|
|
JUnit5(Jupiter)ではassertEqualsメソッド等を使ってテスト(実行結果の確認)を記述する。
これらのメソッドはAssertionsクラスのstaticメソッドとして定義されているので、staticインポートしておくと便利。
import static org.junit.jupiter.api.Assertions.*;
JUnit3,4のassertEqualsメソッド等では、テスト失敗時のエラーメッセージは第1引数に書いていた。
JUnit5(Jupiter)では、テスト失敗時のエラーメッセージは最後の引数に書く。
また、エラーメッセージはラムダ式で書くことも出来る(Supplier<String>を渡せる)。これは、Map#toString()のようなコストがかかる文字列をメッセージに入れることを想定しているのだと思う。
assertTrue(condition, "メッセージ"); assertTrue(condition, () -> "メッセージ"); assertEquals(expected, actual, "メッセージ"); assertEquals(expected, actual, () -> "メッセージ");
テストを失敗させる例。
@Test
void failure1() {
fail("not yet implemented");
}
@Test
void failure2() {
try {
throw new Exception("test");
} catch (Exception e) {
fail("失敗", e);
}
}
booleanの値を確認する例。
@Test
@DisplayName("boolean")
void bool() {
assertTrue("".isEmpty());
assertTrue(() -> "".isEmpty());
assertFalse("a".isEmpty());
assertFalse(() -> "a".isEmpty());
}
nullかどうかを確認する例。
@Test
@DisplayName("null")
void ヌル() {
assertNull(null);
assertNotNull("");
}
値が等しいかどうかを確認する例。
class Example {
public String getValue() {
return "zzz";
}
}
@Test
void equals() {
assertEquals("zzz", "ZZZ".toLowerCase());
assertNotEquals("zzz", "zzz".toUpperCase());
}
浮動小数点は完全一致するとは限らないので、第3引数で一致する範囲(delta)を指定する。
@Test
@DisplayName("float, double")
void float_double() {
assertEquals(0.333f, 1 / 3f, 0.001f);
assertEquals(0.333d, 1 / 3d, 0.001d);
}
// JUnit4の場合 // import static org.hamcrest.Matchers.closeTo; assertThat(1 / 3d, closeTo(0.333d, 0.001d));
配列の比較にはassertArrayEqualsを使う。(配列の配列も、再帰的に比較される)
(assertEqualsで配列の比較は出来ない(インスタンスの比較になってしまう))
@Test
void array() {
int[] expected = { 1, 2, 3 };
int[] actual = { 1, 2, 3 };
assertArrayEquals(expected, actual);
}
リスト(Iterable)の比較にはassertIterableEqualsが便利。
(ListならassertEqualsでも比較できるが、不一致だった場合、assertIterableEqualsだと「不一致だった要素」を表示してくれる)
@Test
void iterable() {
List<String> expected = Arrays.asList("a", "b", "c");
List<String> actual = Arrays.asList("a", "b", "c");
// assertEquals(expected, actual); // 不一致時のメッセージの出方が違う
assertIterableEquals(expected, actual);
}
複数行にわたるメッセージ(スタックトレースとかログメッセージ)の比較にはassertLinesMatchが便利。
assertLinesMatchは、基本的にはList<String>の比較。
だが、期待値の方に正規表現等を指定できる。
@Test
void linesMatch() {
List<String> expected = Arrays.asList("\\d+ms", "abc", ">> skip >>", ".{3}");
List<String> actual = Arrays.asList("123ms", "abc", "1", "2", "3", "zzz");
assertLinesMatch(expected, actual);
}
「\\d+ms」や「.{3}」は正規表現。
「>> >>」は、次のデータがマッチするまでスキップする。「>> コメント >>」のように間に文字列を書いた場合はコメント扱いで無視されるが、
「>> 3 >>」のように数値を書いた場合はその行数分スキップする。
JUnit5.7で、Stream<String>を引数にとるassertLinesMatchも追加された。[2020-09-30]
インスタンスが同じかどうかを比較する例。
@Test
void same() {
String s = new String("zzz");
assertEquals("zzz", s);
assertNotSame("zzz", s);
assertSame("zzz", s.intern());
}
// JUnit4の場合
// import static org.hamcrest.CoreMatchers.*;
String s = new String("zzz");
assertThat(s, is("zzz"));
assertThat(s, not(sameInstance("zzz")));
assertThat(s.intern(), sameInstance("zzz"));
assertEquals等は、テストに失敗した時点でエラーが発生する(AssertionFailedErrorがスローされる)ので、それ以降のテストは実行されない。
assertAllを使うと、1つのテストに失敗しても残りのテストが全て実行される。
複数のテストが失敗しても、エラーがスローされるのは1回だけ(MultipleFailuresError)となる。
class Person {
public String getName() {
return "zzz";
}
public int getAge() {
return 20;
}
}
@Test
void all() {
Person target = new Person();
assertAll("person",
() -> assertEquals("aaa", target.getName(), "name"),
() -> assertEquals(99, target.getAge(), "age")
);
}
assertAllの引数は可変長引数になっており、複数のラムダ式を記述できる。
そのラムダ式で個々のテストを記述していく。
↓実行結果(nameとageの両方ともテスト失敗)
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) 〜
例外が発生することを確認する例。
class Example1 {
public void execute(String arg) {
if (arg == null) {
throw new IllegalArgumentException();
}
}
}
@Test
void exception1() {
Example1 target = new Example1();
assertThrows(IllegalArgumentException.class, () -> target.execute(null));
}
assertThrowsの第1引数で「発生するであろう例外」のクラスを指定し、第2引数(ラムダ式)でテスト対象の処理を実行する。
// JUnit4の場合
@Test(expected = IllegalArgumentException.class)
public void exception1() {
Example1 target = new Example1();
target.execute(null);
}
発生した例外を受け取り、例外が保持している情報をチェックすることも出来る。
class Example2 {
public String getValue() throws SQLException {
throw new SQLException("message", "state", 123);
}
}
@Test
void exception2() {
Example2 target = new Example2();
SQLException e = assertThrows(SQLException.class, () -> target.getValue());
assertEquals("message", e.getMessage());
assertEquals("state", e.getSQLState());
assertEquals(123, e.getErrorCode());
}
JUnit5.2で、例外が発生しないことを確認するassertDoesNotThrowが追加された。[2020-09-30]
assertDoesNotThrow(() -> target.execute());
タイムアウトしないこと(一定時間内に終了すること)を確認する例。
assertTimeoutというメソッド名だと、タイムアウトする事を確認するように見えてしまうのだが(苦笑)
(テストの実行が長時間かかった時にテストをタイムアウトさせたい場合は、@Timeoutを使用する。[2022-08-19])
import java.time.Duration;
@Test
void timeout1() {
assertTimeout(Duration.ofSeconds(2), () -> Thread.sleep(1000));
}
@Test
void timeout2() {
assertTimeoutPreemptively(Duration.ofSeconds(2), () -> Thread.sleep(1000));
}
assertTimeout(assertTimeoutPreemptively)の第1引数でタイムアウト時間を指定する。
第2引数(ラムダ式)でテスト対象の処理を実行する。
assertTimeoutとassertTimeoutPreemptivelyの違いは、テスト対象の処理がタイムアウト時間を過ぎた場合にある。
assertTimeoutはテスト対象の処理が終わるまで待ち、終わってからAssertionFailedErrorを発生させる。
assertTimeoutPreemptivelyは、タイムアウト時間になったらすぐに(テスト対象の処理が終わるのを待たずに)AssertionFailedErrorを発生させる。
// JUnit4の場合
@Test(timeout = 2000)
public void timeout2() throws InterruptedException {
Thread.sleep(1000);
}
値を返す処理をテストする場合は以下のように書く。
@Test
void timeout3() {
ExecutorService service = Executors.newCachedThreadPool();
Callable<String> task = () -> {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
return "zzz";
};
String result = assertTimeoutPreemptively(Duration.ofSeconds(2), () -> {
Future<String> future = service.submit(task);
return future.get();
});
assertEquals("zzz", result);
}