Junit5の使い方
Junit5の書き方
org.junit.jupiter
パッケージのアノテーションとクラスを使用する。
テストメソッド
@Test
を付与したメソッドは、テスト実施の対象となる。
import org.junit.jupiter.api.Test; class SampleTest { @Test public void sample_検証内容() { // ... } }
テストの前処理・後処理
テストメソッド実行の前後に行いたい処理を記述することが出来る。
@BeforeAll
全てのテストの実行前に1度だけ行う処理を記述する。
メソッドは引数・戻り値無しで、static
で定義する。
@BeforeEach
テストメソッド実行前に毎回行う処理を記述することが出来る。
メソッドは引数・戻り値無しで、インスタンスメソッドで定義する。
@AfterEach
テストメソッド実行後に毎回行う処理を記述することが出来る。
メソッドは引数・戻り値無しで、インスタンスメソッドで定義する。
@AfterAll
全てのテストの実行後に1度だけ行う処理を記述する。
メソッドは引数・戻り値無しで、static
で定義する。
import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; class SampleTest { @BeforeAll static void beforeAll() { // テストクラス内で一番最初に一度だけ実行される } @BeforeEach void beforeEach() { // 全てのテストの実行前に実行される } @Test public void sample_検証内容() { // ... } @AfterEach void afterEach() { // 各テストメソッド実行後に実行される } @AfterAll static void afterAll() { // 全てのテスト実行後に一度だけ実行される } }
ネストしたテスト
内部クラスに@Nested
を付与することで、非static な内部クラスを入れ子にすることが出来る。
テストメソッドの数が多い場合などにおいて、テストの分類をするのに使用される。
ネストしたクラス単位でのテスト実行も可能。
import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; class SampleTest { @BeforeEach void beforeEach() { //... } @Test public void sample_検証内容() { // ... } @AfterEach void afterEach() { //... } @Nested class NestedTest { @BeforeEach void beforeEach() { //... } @Test void nested_検証内容() { //... } @AfterEach void afterEach() { //... } } @Nested class NestedTest2 { @Test void test() { //... } } }
上記のようにテストクラスと内部クラスにそれぞれ@BeforeEach
、@AfterEach
を付与したメソッドが定義されている場合、以下の順番でメソッドが実行される。
- SampleTest.beforeEach()
- NestedTest.beforeEach()
- NestedTest.nested_検証内容()
- NestedTest.afterEach()
- SampleTest.afterEach()
ネストしたクラスで@BeforeAll
、@AfterAll
を使用するには
内部クラスは非staticである必要があるため、staticメソッドとして定義する必要のある@BeforeAll
、@AfterAll
を利用することはできない。
⇒ テストインスンタンスのライフサイクルの指定でPER_CLASS
を設定する必要がある。
設定を追加することで、内部クラスで定義する@BeforeAll
、@AfterAll
のメソッドに限りインスタンスメソッドとして定義できるようになる。
import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestInstance; class SampleTest { @BeforeAll static void beforeAll() { //... } @BeforeEach void beforeEach() { //... } @AfterEach void afterEach() { //... } @AfterAll static void afterAll() { //... } @Nested @TestInstance(TestInstance.Lifecycle.PER_CLASS) class NestedTest { @BeforeAll void beforeAll() { //... } @BeforeEach void beforeEach() { //... } @Test void nested_検証内容1() { //... } @Test void nested_検証内容2() { //... } @AfterEach void afterEach() { //... } @AfterAll void afterAll() { //... } } }
上記のように前処理・後処理とテストメソッドを定義した場合は、以下の順番で処理が実行される。
- SampleTest.beforeAll()
- NestedTest.beforeAll()
- SampleTest.beforeEach()
- NestedTest.beforeEach()
- NestedTest.nested_検証内容1()
- NestedTest.afterEach()
- SampleTest.afterEach()
- SampleTest.beforeEach()
- NestedTest.beforeEach()
- NestedTest.nested_検証内容2()
- NestedTest.afterEach()
- SampleTest.afterEach()
- NestedTest.afterAll()
- SampleTest.afterAll()
前提条件の指定
テストメソッド内で特定の条件に合致した場合のみテストを続行したり、処理を実行したりするのに使用する。
Assumptions.assumeTrue(boolean)
引数に true を渡された場合のみ続きのテストが実行される。
引数が false だった場合は、そのテストメソッド内の残りの処理は中断される。
中断されたテストは successful でも failed でもなく、 aborted という状態となる。
Assumptions.assumingThat(boolean, Executable)
第1引数の値が true の場合のみ第2引数で渡した処理が実行される。
import org.junit.jupiter.api.Assumptions; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; class SampleTest { @Test void test_検証内容1() { Assumptions.assumeTrue(true); // 以降の処理は実行される } @Test void test_検証内容2() { Assumptions.assumeTrue(false); // 以降の処理は実行されない } @Test void test_検証内容3() { Assumptions.assumingThat(true, () -> { // 第2引数で渡した処理が実行される }); // 以降の処理は実行される } @Test void test_検証内容4() { Assumptions.assumingThat(false, () -> { // 第2引数で渡した処理が実行されない }); // 以降の処理は実行される } }
テストの無効化
@Disabled
を付与したテストメソッドは実行されなくなる。
クラスに付与した場合は、テストクラス内の全テストメソッドが実行されなくなる。
import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; class SampleTest { @Test void sample_検証内容1() { // 実行されるテストメソッド } @Test @Disabled void sample_検証内容2() { // 実行されないテストメソッド } }
import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; @Disabled class SampleTest { @Test void sample_検証内容1() { // クラスに@Disabledが付与されているため、実行されないテストメソッド } @Test void sample_検証内容2() { // クラスに@Disabledが付与されているため、実行されないテストメソッド } }
条件付きテスト(OS)
@EnabledOnOs
特定の OS でのテスト実行を有効する。
@DisabledOnOs
特定の OS でのテスト実行を無効にする。
引数には OS (org.junit.jupiter.api.condition.OS
) で定義された定数を指定する。
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.condition.DisabledOnOs; import org.junit.jupiter.api.condition.EnabledOnOs; import org.junit.jupiter.api.condition.OS; class SampleTest { @Test @EnabledOnOs(OS.WINDOWS) void sample_検証内容1() { // Windows環境でのみ実行されるテストメソッド } @Test @DisabledOnOs(OS.WINDOWS) void sample_検証内容2() { // Windows環境でのみ実行されないテストメソッド } }
条件付きテスト(Javaバージョン)
@EnabledOnJre
特定の Java バージョンのみでテスト実行を有効にする。
@DisabledOnJre
特定の Java バージョンでのテスト実行を無効にする。
引数には JRE (org.junit.jupiter.api.condition.JRE
) で定義された定数を指定する。
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.condition.DisabledOnJre; import org.junit.jupiter.api.condition.EnabledOnJre; import org.junit.jupiter.api.condition.JRE; class SampleTest { @Test @EnabledOnJre(JRE.JAVA_11) void sample_検証内容1() { // Java11環境でのみ実行されるテストメソッド } @Test @DisabledOnJre(JRE.JAVA_11) void sample_検証内容2() { // Java11環境でのみ実行されないテストメソッド } }
条件付きテスト(システムプロパティ)
@EnabledIfSystemProperty
システムプロパティの値を条件にしてテスト実行を有効にする。
@DisabledIfSystemProperty
システムプロパティの値を条件にしてテスト実行を無効にする。
属性 named
:条件にしたいシステムプロパティの名前を指定する
属性 matches
:条件となる値を正規表現で指定する(完全一致)
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.condition.DisabledIfSystemProperty; import org.junit.jupiter.api.condition.EnabledIfSystemProperty; class SampleTest { @Test @EnabledIfSystemProperty(named = "java.vendor", matches = "AdoptOpenJDK") void sample_検証内容1() { // AdoptOpenJDKのJava環境でのみ実行されるテストメソッド } @Test @DisabledIfSystemProperty(named = "java.vendor", matches = "AdoptOpenJDK") void sample_検証内容2() { // AdoptOpenJDKのJava環境でのみ実行されないテストメソッド } }
条件付きテスト(環境変数)
@EnabledIfEnvironmentVariable
環境変数の値を条件にしてテスト実行を有効にする。
@DisabledIfEnvironmentVariable
環境変数の値を条件にしてテスト実行を無効にする。
属性named
:条件にしたい環境変数の名前を指定する
属性 matches
:条件となる値を正規表現で指定する(完全一致)
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.condition.DisabledIfEnvironmentVariable; import org.junit.jupiter.api.condition.EnabledIfEnvironmentVariable; class SampleTest { @Test @EnabledIfEnvironmentVariable(named = "JAVA_HOME", matches = ".*\\\\zulu-11-azure-jdk_11.*") void sample_検証内容1() { // 環境変数JAVA_HOMEに、Zulu11が設定されている環境でのみ実行されるテストメソッド } @Test @DisabledIfEnvironmentVariable(named = "JAVA_HOME", matches = ".*\\\\zulu-11-azure-jdk_11.*") void sample_検証内容2() { // 環境変数JAVA_HOMEに、Zulu11が設定されている環境でのみ実行されないテストメソッド } }
よく使うアサーション
fail
テストを失敗させる時に使用する。
import org.junit.jupiter.api.Test; import static org.assertj.core.api.Assertions.fail; class SampleTest { @Test void sample_検証内容() { try { // ... } catch (Exception e) { fail("例外が発生したら失敗とする", e); } } }
assertTrue, assertFalse
真偽値の確認に使用する。
import org.junit.jupiter.api.Test; import static org.assertj.core.api.Assertions.assertTrue; import static org.assertj.core.api.Assertions.assertFalse; class SampleTest { @Test void sample_検証内容() { String sample = ""; assertTrue(sample.isEmpty()); sample = "sample"; assertFalse(sample.isEmpty()); } }
assertNull, assertNotNull
null比較に使用する。
import org.junit.jupiter.api.Test; import static org.assertj.core.api.Assertions.assertNull; import static org.assertj.core.api.Assertions.assertNotNull; class SampleTest { @Test void sample_検証内容() { String sample = null; assertNull(sample ); sample = "sample"; assertNotNull(sample); } }
assertEquals, assertNotEquals
等値比較に使用する。
import org.junit.jupiter.api.Test; import static org.assertj.core.api.Assertions.assertEquals; import static org.assertj.core.api.Assertions.assertNotEquals; class SampleTest { @Test void sample_検証内容_文字列の場合() { String sample = "sample"; assertEquals("sample", sample.toLowerCase()); assertNotEquals("sample" , sample.toUpperCase()); } @Test void sample_検証内容_浮動小数点の場合() { // 浮動小数点は完全一致するとは限らないので、第3引数で一致する範囲(delta)を指定する assertEquals(0.333f, 1 / 3f, 0.001f); // float assertEquals(0.333d, 1 / 3d, 0.001d); // double } }
assertArrayEquals
配列の要素比較に使用する。
import org.junit.jupiter.api.Test; import static org.assertj.core.api.Assertions.assertArrayEquals; class SampleTest { @Test void sample_検証内容() { int[] expected = { 1, 2, 3 }; int[] actual = { 1, 2, 3 }; assertArrayEquals(expected, actual); } }
assertIterableEquals
リスト(Iterable)の要素比較に使用する。
assertEqualsでも比較できるが、不一致だった場合にassertIterableEqualsを使用していた場合は「不一致だった要素」が表示される。
import org.junit.jupiter.api.Test; import static org.assertj.core.api.Assertions.assertIterableEquals; import java.util.Arrays; class SampleTest { @Test void sample_検証内容() { List<String> expected = Arrays.asList("a", "b", "c"); List<String> actual = Arrays.asList("a", "b", "c"); assertIterableEquals(expected, actual); } }
assertLinesMatch
複数行にわたるメッセージ(スタックトレースとかログメッセージ)の比較に使用する。
import org.junit.jupiter.api.Test; import static org.assertj.core.api.Assertions.assertLinesMatch; import java.util.Arrays; class SampleTest { @Test void sample_検証内容() { // assertLinesMatchは、基本的にはList<String>の比較に使用する。 // また、期待値の方には正規表現等を指定可能(「\\d+ms」や「.{3}」など) // 「>> >>」は、次のデータがマッチするまでスキップする // 「>> skip >>」のように間に文字列を書いた場合はコメント扱いで無視されるが、 // 「>> 3 >>」のように数値を書いた場合はその行数分スキップする。 List<String> expected = Arrays.asList("\\d+ms", "abc", ">> skip >>", ".{3}"); List<String> actual = Arrays.asList("123ms", "abc", "1", "2", "3", "zzz"); assertLinesMatch(expected, actual); } }
assertSame, assertNotSame
インスタンスの比較に使用する。
import org.junit.jupiter.api.Test; import static org.assertj.core.api.Assertions.assertSame; import static org.assertj.core.api.Assertions.assertNotSame; class SampleTest { @Test void sample_検証内容() { String sample = new String("sample"); assertNotSame("sample", sample); assertSame("sample", sample.intern()); } }
assertAll
全チェック(1つのアサーションに失敗しても残りのアサーションを全て実行)する場合に使用する。
複数のアサーションが失敗しても、エラーがスローされるのは1回だけ(MultipleFailuresError)となる。
import org.junit.jupiter.api.Test; import static org.assertj.core.api.Assertions.assertAll; import static org.assertj.core.api.Assertions.assertEquals; class SampleTest { @Test void sample_検証内容() { String sample = new String("test"); // assertAllの引数は可変長引数になっており、複数のラムダ式を記述する(ラムダ式で個々のテストを記述する) assertAll("sample_all_NG", () -> assertEquals("sample", sample, "string not equals"), () -> assertEquals(6, sample.length(), "length not equals") ); // 実行結果は以下のようになる // org.opentest4j.MultipleFailuresError: sample_all_NG (2 failures) // org.opentest4j.AssertionFailedError: string not equals ==> expected: <sample> but was: <test> // org.opentest4j.AssertionFailedError: length not equals ==> expected: <6> but was: <4> } }
assertTimeout, assertTimeoutPreemptively
タイムアウトしないこと(一定時間内に終了すること)を確認する際に使用する。
◇ asserTimeoutとassertTimeoutPreemptivelyの差異について
assertTimeoutはテスト対象の処理が終わるまで待ち、終わってからAssertionFailedErrorを発生させる。
assertTimeoutPreemptivelyは、タイムアウト時間になったらすぐに(テスト対象の処理が終わるのを待たずに)AssertionFailedErrorを発生させる。
import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertTimeout; import static org.junit.jupiter.api.Assertions.assertTimeoutPreemptively; import java.time.Duration; import java.util.concurrent.Callable; import java.util.concurrent.Executors; import org.junit.jupiter.api.Test; class SampleTest { Callable<String> task = () -> { Thread.sleep(1000); // 1秒待機 return "success"; }; @Test void sample_検証内容1() { // 第1引数でタイムアウト時間、第2引数(ラムダ式)でテスト対象の処理を指定 String result = assertTimeout(Duration.ofSeconds(2), () -> { return Executors.newCachedThreadPool().submit(task).get(); }); assertEquals("success", result); } @Test void sample_検証内容2() { // 第1引数でタイムアウト時間、第2引数(ラムダ式)でテスト対象の処理を指定 String result = assertTimeoutPreemptively(Duration.ofSeconds(2), () -> { return Executors.newCachedThreadPool().submit(task).get(); }); assertEquals("success", result); } }
例外テスト方法
assertThrows
例外が発生することを確認する際に使用する。
- 第1引数で「発生するであろう例外」のクラスを指定
- 第2引数(ラムダ式)でテスト対象の処理を指定
import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertEquals; import org.junit.jupiter.api.Test; class SampleTest { /** * テスト対象 */ class Example1 { public void execute(String arg) { if (arg == null) { throw new IllegalArgumentException("不正な引数:nullです"); } } } @Test void execute_IllegalArgumentException() { Example1 target = new Example1(); Throwable exception = assertThrows(IllegalArgumentException.class, () -> target.execute(null)); // 発生した例外を受け取り、例外が保持している情報(メッセージなど)を検証することも可能 assertEquals(exception.getMessage(), "不正な引数:nullです"); } }
assertDoesNotThrow
例外が発生しないことを確認する際に使用する。
第1引数(ラムダ式)でテスト対象の処理を指定
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; import org.junit.jupiter.api.Test; class SampleTest { /** * テスト対象 */ class Example1 { public void execute(String arg) { if (arg == null) { throw new IllegalArgumentException("nullです"); } } } @Test void execute_OK() { Example1 target = new Example1(); assertDoesNotThrow(() -> target.execute("sample")); // 例外が発生しないこと } }
パラメータテスト
1つのテストメソッドに複数のパターンのパラメータを渡してテストを実行する場合に使用する。
@ParameterizedTest
パラメータテストを実行することを明示する。
以下のアノテーションと組み合わせて使用する(下記以外にも使用可能なアノテーションは存在する)
@ValueSource
: パラメータを1つ渡す場合に使用する@CsvSource
: 複数のパラメータを渡す場合に使用する@NullAndEmptySource
: nullと空白を順番に渡す
@ValueSource
と@NullAndEmptySource
を組み合わせて使用することもできる。
import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.CsvSource; import org.junit.jupiter.params.provider.NullAndEmptySource; import org.junit.jupiter.params.provider.ValueSource; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertTrue; class SampleTest { /** * テスト対象 */ class Example1 { public boolean isValid(String arg) { if (arg == null || arg.isEmpty()) { return false; } return true; } } Example1 target = new Example1(); @ParameterizedTest @ValueSource(strings = { "foo", "bar" }) void isValid_バリデーション動作確認1(String arg) { // "foo"、"bar"が順番に渡される assertTrue(target.isValid(arg)); } @ParameterizedTest @NullAndEmptySource void isValid_バリデーション動作確認2(String arg) { // nullと空白が順番に渡される assertFalse(target.isValid(arg)); } @ParameterizedTest @CsvSource({ // "値, バリデーション結果(null以外はtrue)" " 'sample' , true ", // 文字列 " '' , false ", // 空白 " , false " // null }) public void isValid_バリデーション動作確認3(String arg, boolean result) { // 「"sample" と true」、「空白 と false」、「null と false」が順番に渡される assertEquals(target.isValid(arg), result); } @ParameterizedTest @ValueSource(strings = { "foo", "bar" }) @NullAndEmptySource void isValid_サンプル(String arg) { // パラメータが「"foo"、"bar"、null、空白」の順番で、計4回のテストが実行される } }
テストカバレッジについての記事はこちら: olafnosuke.hatenablog.com
モックについての記事も書いています:
OSSを使用したログの出力内容確認テストについてはこちら: