S-JIS[2017-09-14/2022-08-19] 変更履歴

JUnit5 テストクラス

JUnit5のテストクラスの記述方法について。


概要

JUnit5(Jupiter)では、テストクラスはpublicにする必要は無い。
クラス名は何でもいいが、gradlew testコマンドでテストを実行する場合、デフォルトではクラス名の末尾がTest, Testsのものだけが実行対象となる。

戻り型がvoidで@Testアノテーションを付けたメソッドがテスト対象になる。
(テストメソッドもpublicにする必要は無い)

また、抽象クラスのメソッドやインターフェースのデフォルトメソッドに@Testアノテーションを付けてテストメソッドとすることが出来る。
これらの抽象クラスやインターフェースを継承したクラスを用意すると、抽象クラスやインターフェースのテストメソッドも実行される。

@Timeoutアノテーションを付けると、テストが長時間かかったときにタイムアウトさせることが出来る。(JUnit5.5以降)[2022-08-19]

テストクラスの内部クラスに@Nestedアノテーションを付けると、そのクラスにもテストメソッドを記述できる。
(テストをグルーピング(分類)して書ける。たぶん可読性の向上が目的)

テストクラスやテストメソッドにタグを付けることが出来る。
gradlew testコマンドの実行時に、特定のタグが付いたテストだけ実行したり、あるいは逆に除外したりすることが出来る。

デフォルトでは、テストメソッドを実行する毎にテストクラスのインスタンスが作られる。
テストクラス毎に1回だけインスタンスを生成するようにすることも出来る。
テストインスタンスのライフサイクル


テストクラス毎に、全テストの実行前に呼ばれる処理には@BeforeAllアノテーションを付ける。
全テストの実行後に呼ばれる処理には@AfterAllアノテーションを付ける。

各テスト毎に、テスト実行前に呼ばれる処理には@BeforeEachアノテーションを付ける。
テスト実行後に呼ばれる処理には@AfterEachアノテーションを付ける。

テストを実行しないクラスやメソッドには@Disabledアノテーションを付ける。
引数でコメントを入れることも出来る。

テストメソッドには@Testアノテーションを付ける。

テストクラスやテストメソッドには@DisplayNameアノテーションでテスト名を付けることが出来る。
テスト結果のログにはこの名称が表示される。
デフォルトはクラス名やメソッド名(引数の型名付き)。

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.Disabled;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.TestInfo;
class BeforeAfterTest {

	@BeforeAll // JUnit4の @BeforeClass
	static void beforeAllTests(TestInfo testInfo) {
		System.out.printf("before all [%s]\n", testInfo.getDisplayName());
	}

	@AfterAll // JUnit4の @AfterClass
	static void afterAllTests(TestInfo testInfo) {
		System.out.printf("after all [%s]\n", testInfo.getDisplayName());
	}
	@BeforeEach // JUnit4の @Before
	void beforeEachTest(TestInfo testInfo) {
		System.out.printf("before each [%s]\n", testInfo.getDisplayName());
	}

	@AfterEach // JUnit4の @After
	void afterEachTest(TestInfo testInfo) {
		System.out.printf("after each [%s]\n", testInfo.getDisplayName());
	}
	@Test
	@DisplayName("test1 !")
	void test1() {
		System.out.println("test1");
	}

	@Test
	void test2(TestInfo testInfo) {
		System.out.println("test2 " + testInfo);
	}

	@Test
	@Disabled // JUnit4の @Ignore
	void test3() {
		System.out.println("test3");
	}
}

TestInfo

@BeforeAll/@AfterAllや@BeforeEach/@AfterEachおよび@Testアノテーションを付けたメソッドには、引数にTestInfoを指定することが出来る。

import org.junit.jupiter.api.TestInfo;
	@BeforeEach
	void beforeEachTest(TestInfo testInfo) {
		System.out.printf("before each [%s]\n", testInfo.getDisplayName());
	}

TestInfoからは、テスト対象の情報を取得できる。

メソッド 内容
String getDisplayName() テストの表示名
@DisplayNameが付けられている場合はその名称、無ければテストクラス名あるいはテストメソッド名。
Set<String> getTags() テストに付けられているタグ
Optional<Class<?>> getTestClass() テストクラス
Optional<Method> getTestMethod( テストメソッド

タグ

テストクラスやテストメソッドに@Tagアノテーションでタグを付けることが出来る。

import org.junit.jupiter.api.Tag;
import org.junit.jupiter.api.Test;
class TagTest {

	@Tag("tag1")
	@Test
	void test1() {
		System.out.println("tag1-test1");
	}

	@Tag("tag2")
	@Test
	void test2() {
		System.out.println("tag2-test2");
	}

	@Tag("tag2")
	@Tag("tag3")
	@Test
	void test3() {
		System.out.println("tag12-test3");
	}
}

ちなみに、@Tagアノテーションは一箇所に複数指定することが出来る。


gradlew testコマンドでテストを実行する場合、build.gradleのjunitPlatformのfiltersのtagsでタグを指定することが出来る。

build.gradle:

junitPlatform {
    filters {
        tags {
            include 'tag1', 'tag3'
//          exclude 'tag2'
        }
    }
}

includeを指定すると、そのタグが付いたテストのみ実行する。
excludeを指定すると、そのタグが付いたテストは除外される。


内部クラス

内部クラスに@Nestedアノテーションを付けると、そのクラスもテスト対象になる。

import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;
class NestTest {

	@Test
	void test1() {
		System.out.println("nest0");
	}
	@Nested
	class Nest1 {

		@Test
		void test2() {
			System.out.println("nest1");
		}
	}
}

ただし、内部クラスがstaticクラスの場合はテスト対象にならない。
(エラーにもならず、ただ無視されるのみ)


抽象クラス・インターフェース

抽象クラスのメソッドやインターフェースのデフォルトメソッドもテストメソッドにすることが出来る。
(ドキュメントにはインターフェースにしか言及が無いが、抽象クラスも試したら出来た)

抽象クラス・インターフェースはインスタンス化できないので、これらを継承したテストクラスを用意する必要はあるが、
そのテストクラスを実行すると、抽象クラスやインターフェースのテストメソッドも実行される。

interface InterfaceExample {

	public int get();

	public int inc();

	@Test
	default void test() {
		int value1 = get();
		int value2 = inc();
		assertEquals(value1 + 1, value2);
	}
}
class InterfaceTest implements InterfaceExample {

	@Override
	public int get() {
		return 1;
	}

	@Override
	public int inc() {
		return 2;
	}
}

テストインスタンスのライフサイクル

デフォルトでは、テストメソッドを実行する毎にテストクラスのインスタンスを生成する。
これを、テストクラス毎に1回だけインスタンスを生成するようにすることが出来る。

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.Disabled;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.TestInfo;
import org.junit.jupiter.api.TestInstance;

// @TestInstance(TestInstance.Lifecycle.PER_METHOD)
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
class BeforeAfterTest {

	public BeforeAfterTest() {
		System.out.println("concstruct");
	}
	@BeforeAll
	static void beforeAllTests(TestInfo testInfo) {
		System.out.printf("before all [%s]\n", testInfo.getDisplayName());
	}

	@AfterAll
	static void afterAllTests(TestInfo testInfo) {
		System.out.printf("after all [%s]\n", testInfo.getDisplayName());
	}
	@BeforeEach
	void beforeEachTest(TestInfo testInfo) {
		System.out.printf("before each [%s]\n", testInfo.getDisplayName());
	}

	@AfterEach
	void afterEachTest(TestInfo testInfo) {
		System.out.printf("after each [%s]\n", testInfo.getDisplayName());
	}
	@Test
	void test1() {
		System.out.println("test1");
	}

	@Test
	void test2(TestInfo testInfo) {
		System.out.printf("test2 [%s]\n", testInfo.getDisplayName());
	}

	@Test
	@Disabled("実行しない")
	void test3() {
		System.out.println("test3");
	}
}

↓実行結果

Lifecycle.PER_METHOD Lifecycle.PER_CLASS
before all [BeforeAfterTest]
concstruct
before each [test1()]
test1
after each [test1()]
concstruct
before each [test2(TestInfo)]
test2 [test2(TestInfo)]
after each [test2(TestInfo)]
concstruct
after all [BeforeAfterTest]
concstruct
before all [BeforeAfterTest]
before each [test1()]
test1
after each [test1()]
before each [test2(TestInfo)]
test2 [test2(TestInfo)]
after each [test2(TestInfo)]
after all [BeforeAfterTest]
test3は実行されないが、インスタンスは生成される。 コンストラクターは1回しか呼ばれていない。

@BeforeAll・@AfterAllアノテーションを付けるメソッドは、インスタンスメソッドにする(staticを付けない)ことも出来る。
ただしその場合は、ライフサイクルをPRE_CLASSにする必要がある。

ライフサイクルがPRE_METHOD(デフォルト)のままだと、テスト実行時に以下のような例外が発生する。

org.junit.platform.commons.JUnitException: @BeforeAll method 'void com.example.BeforeAfterTest.beforeAllTests(org.junit.jupiter.api.TestInfo)' must be static unless the test class is annotated with @TestInstance(Lifecycle.PER_CLASS).
	at org.junit.jupiter.engine.descriptor.LifecycleMethodUtils.assertStatic(LifecycleMethodUtils.java:59)
	at org.junit.jupiter.engine.descriptor.LifecycleMethodUtils.lambda$findMethodsAndAssertStatic$0(LifecycleMethodUtils.java:83)
	at java.util.ArrayList.forEach(ArrayList.java:1249)
	at java.util.Collections$UnmodifiableCollection.forEach(Collections.java:1080)
	at org.junit.jupiter.engine.descriptor.LifecycleMethodUtils.findMethodsAndAssertStatic(LifecycleMethodUtils.java:83)
	at org.junit.jupiter.engine.descriptor.LifecycleMethodUtils.findBeforeAllMethods(LifecycleMethodUtils.java:42)
	at org.junit.jupiter.engine.descriptor.ClassTestDescriptor.prepare(ClassTestDescriptor.java:119)
	at org.junit.jupiter.engine.descriptor.ClassTestDescriptor.prepare(ClassTestDescriptor.java:61)
	at org.junit.platform.engine.support.hierarchical.HierarchicalTestExecutor.execute(HierarchicalTestExecutor.java:60)
	at org.junit.platform.engine.support.hierarchical.HierarchicalTestExecutor.lambda$null$2(HierarchicalTestExecutor.java:92)
	〜

デフォルトのライフサイクルの変更

テストクラスに@TestInstanceアノテーションを付けない場合のデフォルトは、設定により変更することが出来る。

src/test/resources/junit-platform.properties:

junit.jupiter.testinstance.lifecycle.default = PER_CLASS

ちなみに、設定値は大文字でも小文字でも大丈夫な模様。


gradlew testコマンドでテストを実行する場合は、build.gradleのjunitPlatformのconfigurationParameterで設定することも出来る。

build.gradle:

junitPlatform {
	configurationParameter 'junit.jupiter.testinstance.lifecycle.default', 'PER_CLASS'
}

テストの繰り返し

同一のテストを繰り返し実行したい場合は、@Testアノテーションの代わりに@RepeatedTestアノテーションを使う。

import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.RepeatedTest;
import org.junit.jupiter.api.TestInfo;
class RepeatTest {

	@RepeatedTest(5)
	@DisplayName("repeat1")
	void test1(TestInfo testInfo) {
		System.out.println(testInfo.getDisplayName());
	}
	@RepeatedTest(value = 5, name = "{displayName} {currentRepetition}/{totalRepetitions}")
	@DisplayName("repeat2")
	void test2(TestInfo testInfo) {
		System.out.println(testInfo.getDisplayName());
	}
}

この場合、@DisplayNameで付けたテスト名はTestInfo#getDisplayName()では取得できない。
@RepeatedTestの引数nameで付けた名前がTestInfo#getDisplayName()で取得できる。
@RepeatedTestの引数nameには、波括弧で特別な値を指定することが出来る。

変数 説明
{displayName} テスト名(@DisplayNameで付けた名前)
{currentRepetition} テストの番号(1から開始)
{totalRepetitions} テストの総回数

↓実行結果

repetition 1 of 5
repetition 2 of 5
repetition 3 of 5
repetition 4 of 5
repetition 5 of 5
repeat2 1/5
repeat2 2/5
repeat2 3/5
repeat2 4/5
repeat2 5/5

テストの回数毎にテスト値を変えたい場合は、@ParameterizedTestアノテーションを使う。


JUnit5へ戻る / JUnitへ戻る / Javaへ戻る / 技術メモへ戻る
メールの送信先:ひしだま