概要
- Junit5の公式docの"Writing Test"の章で紹介されている機能を紹介します。(執筆中)
- なお後半の執筆予定はありません。
- https://junit.org/junit5/docs/current/user-guide/#writing-tests
カスタムアノテーションにメタタグを定義して利用できる。
- カスタムアノテーションの作成
Fast.java
import org.junit.jupiter.api.Tag;
@Target({ ElementType.TYPE, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Tag("fast")
public @interface Fast {
}
- テストメソッドに設定
SomeTest.java
class SomeTest {
@Test
@Fast
void succeedingTest() {
}
}
- 解説
- メソッドに
@Tag("fast")
と書かずに、@Fast
と書くことができる。 - 何がうれしいかはいまいちわからない。
- メソッドに
テストの実行結果に好きな文字列を表示できる。
SomeTest.java
@DisplayName("A special test case")
class SomeTest {
@Test
@DisplayName("Custom test name containing spaces")
void testWithDisplayNameContainingSpaces() {
}
@Test
@DisplayName("╯°□°)╯")
void testWithDisplayNameContainingSpecialCharacters() {
}
@Test
@DisplayName("😱")
void testWithDisplayNameContainingEmoji() {
}
}
- 実行結果
- Junit4まで
assertion関連の機能
Person.java
private class Person{
String firstName;
String lastName;
public Person(String firstName, String lastName) {
this.firstName = firstName;
this.lastName = lastName;
}
public String getFirstName() {
return firstName;
}
public String getLastName() {
return lastName;
}
}
class sample {
private Person person = new Person("first", "last");
@Test
void standardAssertions() {
assertEquals(2, 2);
assertEquals(3, 4, "アサーション失敗時のメッセージを任意に設定できる。(失敗するとここに設定した文字が表示される。).");
assertTrue('a' < 'b', () -> "不要に複雑なメッセージを避けるためアサーションのメッセージは遅延評価される");
}
@Test
void groupedAssertions() {
// この書き方だとアサーションは頭で失敗しても、すべて評価される。(Junit4の書き方だとfailしたステップで終了。)
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"))
);
},
() -> {
// このように書くことで、firstNameのアサーション結果とは無関係に
// こちらのアサーションを実行できる。
String lastName = person.getLastName();
assertNotNull(lastName);
// 前のステップが成功したら実行される。
assertAll("last name",
() -> assertTrue(lastName.startsWith("D")),
() -> assertTrue(lastName.endsWith("e"))
);
}
);
}
@Test
void exceptionTesting() {
Throwable exception = assertThrows(IllegalArgumentException.class, () -> {
throw new IllegalArgumentException("a message");
});
assertEquals("a message", exception.getMessage());
}
@Test
void timeoutNotExceeded() {
assertTimeout(ofMinutes(2), () -> {
// 2秒以内に処理が終わったらこのアサーションは成功する。
});
}
@Test
void timeoutExceeded() {
assertTimeout(ofMillis(10), () -> {
// この処理は100ミリかかるので失敗する。
Thread.sleep(100);
});
}
}
"前提とする"というアサーション
assumeTrue、assumingThat
@Test
void testOnlyOnCiServer() {
assumeTrue("CI".equals(System.getenv("ENV")));
// remainder of test
}
@Test
void testOnlyOnDeveloperWorkstation() {
assumeTrue("DEV".equals(System.getenv("ENV")),
() -> "Aborting test: not on developer workstation");
// remainder of test
}
@Test
void testInAllEnvironments() {
assumingThat("CI".equals(System.getenv("ENV")),
() -> {
// CIサーバーの時だけ実施される。
assertEquals(2, 2);
});
// 全部の条件で実施される。
assertEquals("a string", "a string");
}
- 解説
- 特定の値を持つ場合のみ、あるテストを実施するというテストが可能。(assumingthat)
- assumeTrueとassertTrueはあんまり違いがわからない。
特定の状況においてのみメソッドやクラスを実行する。
-
@EnabledOnOs
、@DisabledOnOs
@Test
@EnabledOnOs(MAC)
void onlyOnMacOs() {
// ...
}
@TestOnMac
void testOnMac() {
// ...
}
@Test
@EnabledOnOs({ LINUX, MAC })
void onLinuxOrMac() {
// ...
}
- 解説
- テストが実行されるOSによって、テストを実施するかどうかをクラス、メソッドレベルで指定することができる。
- ExecutionConditionを拡張することでこういった条件を独自に設定可能。
- そのほかにも
@EnabledOnJre
、@DisabledOnJre
、@EnabledIfSystemProperty
、@DisabledIfSystemProperty
といったJavaバージョンやシステムプロパティの値によって切り分けるものもある。他にもいろいろあるのでサンプルだけ乗せておきます。
@Test // Static JavaScript expression.
@EnabledIf("2 * 3 == 6")
void willBeExecuted() {
// ...
}
@RepeatedTest(10) // Dynamic JavaScript expression.
@DisabledIf("Math.random() < 0.314159")
void mightNotBeExecuted() {
// ...
}
@Test // Regular expression testing bound system property.
@DisabledIf("/32/.test(systemProperty.get('os.arch'))")
void disabledOn32BitArchitectures() {
assertFalse(System.getProperty("os.arch").contains("32"));
}
@Test
@EnabledIf("'CI' == systemEnvironment.get('ENV')")
void onlyOnCiServer() {
assertTrue("CI".equals(System.getenv("ENV")));
}
@Test // Multi-line script, custom engine name and custom reason.
@EnabledIf(value = {
"load('nashorn:mozilla_compat.js')",
"importPackage(java.time)",
"",
"var today = LocalDate.now()",
"var tomorrow = today.plusDays(1)",
"tomorrow.isAfter(today)"
},
engine = "nashorn",
reason = "Self-fulfilling: {result}")
void theDayAfterTomorrow() {
LocalDate today = LocalDate.now();
LocalDate tomorrow = today.plusDays(1);
assertTrue(tomorrow.isAfter(today));
}
タグ付けとフィルタリング
-
@Tag("model")
みたいなのが使えて、これでフィルターできるっぽい。
テストインスタンスのライフサイクル
- 今までのJunitはテストメソッドが実行されるたびにインスタンスが再生成されていた。
- それを、メソッド単位ではなくクラス単位で行えるようになった。
@TestInstance(Lifecycle.PER_CLASS)
- これを設定すると
@BeforeAll
,@AfterAll
が使えるようになる。 - JVM引数でもこの挙動は変えられる。
- -Djunit.jupiter.testinstance.lifecycle.default=per_class
ネストクラスでテストをグループ化できるようになった
class TestingAStackDemo {
Stack<Object> stack;
@Nested
class WhenNew {
@BeforeEach
void createNewStack() {
stack = new Stack<>();
}
@Test
void isEmpty() {
assertTrue(stack.isEmpty());
}
@Nested
class AfterPushing {
String anElement = "an element";
@BeforeEach
void pushAnElement() {
stack.push(anElement);
}
@Test
void isNotEmpty() {
assertFalse(stack.isEmpty());
}
}
}
}
コンストラクターとメソッドが引数を受け取れるようになった。
-
TestInfo
、RepetitionInfo
、TestReporter
- これらの型の引数をメソッドに設定すると
@DisplayName
に設定した値などがとれる。 - TestReporterは実行結果のレポートに情報を出力できる。(stdoutではなくこっちを使う。)
- これらの型の引数をメソッドに設定すると
-
@ExtendWith
で拡張できる。mockitoとかspringのもある。(ここ追記する。)