1. 概要

このチュートリアルでは、 JUnit4ライブラリによって提供されるルール機能を見ていきます。

ディストリビューションによって提供される最も重要な基本ルールを説明する前に、JUnitルールモデルを紹介することから始めます。 さらに、独自のカスタムJUnitルールを作成して使用する方法も説明します。

JUnitを使用したテストの詳細については、包括的なJUnitシリーズをご覧ください。

JUnit 5を使用している場合、ルールは Extensionmodelに置き換えられていることに注意してください。

2. JUnit4ルールの概要

JUnit 4ルールは、テストケースの実行の周りでコードを実行することにより、テストを強化するための柔軟なメカニズムを提供します。 ある意味では、テストクラスに@Beforeおよび@Afterアノテーションがあるのと似ています。

テストのセットアップ中にデータベースなどの外部リソースに接続し、テストの終了後に接続を閉じたいと想像してみてください。 そのデータベースを複数のテストで使用する場合は、すべてのテストでそのコードを複製することになります。

ルールを使用することで、すべてを1つの場所に分離し、複数のテストクラスからコードを簡単に再利用できます。

3. JUnit4ルールの使用

では、どのようにルールを使用できますか? 次の簡単な手順に従って、JUnit4ルールを使用できます。

  • public フィールドをテストクラスに追加し、このフィールドのタイプがorg.junit.rules.TestRuleインターフェイスのサブタイプであることを確認します
  • @Ruleアノテーションでフィールドにアノテーションを付けます

次のセクションでは、開始するために必要なプロジェクトの依存関係を確認します。

4. Mavenの依存関係

まず、例に必要なプロジェクトの依存関係を追加しましょう。 必要なのはメインのJUnit4ライブラリだけです。

<dependency>
    <groupId>junit</groupId>
    <artifactId>junit</artifactId>
    <version>4.12</version>
</dependency>

いつものように、 MavenCentralから最新バージョンを入手できます。

5. 配布で提供されるルール

もちろん、 JUnitは、ライブラリの一部として、いくつかの便利な事前定義されたルールを提供します。 これらのルールはすべて、org.junit.rulesパッケージに含まれています。

このセクションでは、それらの使用方法の例をいくつか示します。

5.1. TemporaryFolderルール

テストするとき、一時ファイルまたはフォルダーにアクセスする必要があることがよくあります。 ただし、これらのファイルの作成と削除の管理は面倒な場合があります。 TemporaryFolderルールを使用して、テストメソッドの終了時に削除する必要があるファイルとフォルダーの作成を管理できます

@Rule
public TemporaryFolder tmpFolder = new TemporaryFolder();

@Test
public void givenTempFolderRule_whenNewFile_thenFileIsCreated() throws IOException {
    File testFile = tmpFolder.newFile("test-file.txt");

    assertTrue("The file should have been created: ", testFile.isFile());
    assertEquals("Temp folder and test file should match: ", 
      tmpFolder.getRoot(), testFile.getParentFile());
}

ご覧のとおり、最初にTemporaryFolderルールtmpFolderを定義します。 次に、テストメソッドは一時フォルダにtest-file.txtというファイルを作成します。 次に、ファイルが作成され、必要な場所に存在することを確認します。 本当に素晴らしくてシンプルです!

テストが終了したら、一時フォルダーとファイルを削除する必要があります。 ただし、このルールは削除が成功したかどうかをチェックしません。

このクラスで言及する価値のある他のいくつかの興味深いメソッドもあります。

  • newFile()

    ファイル名を指定しない場合、このメソッドはランダムに名前が付けられた新しいファイルを作成します。

  • newFolder(String... folderNames)

    再帰的に深い一時フォルダを作成するには、この方法を使用できます。

  • newFolder()

    同様に、 newFolder()メソッドは、ランダムに名前が付けられた新しいフォルダーを作成します。

言及する価値のある素晴らしい追加は、バージョン4.13以降、TemporaryFolderルールで削除されたリソースの検証が可能になることです。

@Rule 
public TemporaryFolder folder = TemporaryFolder.builder().assureDeletion().build();

リソースを削除できない場合、テストはAssertionErrorで失敗します。

最後に、 JUnit 5 では、 TemporaryDirectoryextensionを使用して同じ機能を実現できます。

5.2. ExpectedExceptionルール

名前が示すように、 ExpectedExceptionルールを使用して、一部のコードが予期される例外をスローすることを確認できます。

@Rule
public final ExpectedException thrown = ExpectedException.none();

@Test
public void givenIllegalArgument_whenExceptionThrown_MessageAndCauseMatches() {
    thrown.expect(IllegalArgumentException.class);
    thrown.expectCause(isA(NullPointerException.class));
    thrown.expectMessage("This is illegal");

    throw new IllegalArgumentException("This is illegal", new NullPointerException());
}

上記の例でわかるように、最初にExpectedExceptionルールを宣言しています。 次に、テストでは、IllegalArgumentExceptionがスローされることを表明しています。

このルールを使用して、メッセージや原因など、例外の他のいくつかのプロパティを確認することもできます。

JUnitを使用して例外をテストするための詳細なガイドについては、例外をアサートする方法に関する優れたガイドを確認してください。

5.3. TestNameルール

簡単に言うと、 TestName ルールは、特定のテストメソッド内の現在のテスト名を提供します。

@Rule public TestName name = new TestName();

@Test
public void givenAddition_whenPrintingTestName_thenTestNameIsDisplayed() {
    LOG.info("Executing: {}", name.getMethodName());
    assertEquals("givenAddition_whenPrintingTestName_thenTestNameIsDisplayed", name.getMethodName());
}

この簡単な例では、単体テストを実行すると、出力にテスト名が表示されます。

INFO  c.baeldung.rules.JUnitRulesUnitTest - 
    Executing: givenAddition_whenPrintingTestName_thenTestNameIsDisplayed

5.4. タイムアウトルール

この次の例では、Timeoutルールを見ていきます。 このルールは、個々のテストアノテーションでタイムアウトパラメーターを使用する代わりの便利な方法を提供します。

次に、このルールを使用して、テストクラスのすべてのテストメソッドにグローバルタイムアウトを設定する方法を見てみましょう。

@Rule
public Timeout globalTimeout = Timeout.seconds(10);

@Test
public void givenLongRunningTest_whenTimout_thenTestFails() throws InterruptedException {
    TimeUnit.SECONDS.sleep(20);
}

上記の簡単な例では、最初に10秒のすべてのテストメソッドのグローバルタイムアウトを定義します。 次に、10秒以上かかるテストを意図的に定義します。

このテストを実行すると、テストの失敗が表示されます。

org.junit.runners.model.TestTimedOutException: test timed out after 10 seconds
...

5.5. ErrorCollectorルール

次に、ErrorCollectorルールを見ていきます。 このルールにより、最初の問題が見つかった後もテストの実行を続行できます

このルールを使用してすべてのエラーを収集し、テストの終了時に一度にすべてを報告する方法を見てみましょう。

@Rule 
public final ErrorCollector errorCollector = new ErrorCollector();

@Test
public void givenMultipleErrors_whenTestRuns_thenCollectorReportsErrors() {
    errorCollector.addError(new Throwable("First thing went wrong!"));
    errorCollector.addError(new Throwable("Another thing went wrong!"));
        
    errorCollector.checkThat("Hello World", not(containsString("ERROR!")));
}

上記の例では、コレクターに2つのエラーを追加します。 テストを実行すると、実行は続行されますが、テストは最後に失敗します。

出力には、両方のエラーが報告されています。

java.lang.Throwable: First thing went wrong!
...
java.lang.Throwable: Another thing went wrong!

5.6. ベリファイアルール

Verifierルールは、テストからいくつかの追加の動作を検証する場合に使用できる抽象基本クラスです。 実際、前のセクションで見た ErrorCollector ルールは、このクラスを拡張します。

ここで、独自のベリファイアを定義する簡単な例を見てみましょう。

private List messageLog = new ArrayList();

@Rule
public Verifier verifier = new Verifier() {
    @Override
    public void verify() {
        assertFalse("Message Log is not Empty!", messageLog.isEmpty());
    }
};

ここでは、新しいベリファイアを定義し、 verify()メソッドをオーバーライドして、検証ロジックを追加します。 この簡単な例では、この例のメッセージログが空ではないことを確認するだけです。

ここで、単体テストを実行してメッセージを追加すると、ベリファイアが適用されていることがわかります。

@Test
public void givenNewMessage_whenVerified_thenMessageLogNotEmpty() {
    // ...
    messageLog.add("There is a new message!");
}

5.7. DisableOnDebugルール

デバッグ中にルールを無効にしたい場合があります。 たとえば、デバッグ時に Timeout ルールを無効にして、テストがタイムアウトして適切にデバッグする前に失敗しないようにすることが望ましい場合がよくあります。

DisableOnDebug ルールはこれを正確に実行し、デバッグ時に無効にする特定のルールにラベルを付けることができます。

@Rule
public DisableOnDebug disableTimeout = new DisableOnDebug(Timeout.seconds(30));

上記の例では、このルールを使用するために、無効にするルールをコンストラクターに渡すだけであることがわかります。

このルールの主な利点は、デバッグ中にテストクラスを変更せずにルールを無効にできることです。

5.8. ExternalResourceルール

通常、統合テストを作成するときは、テストの前に外部リソースをセットアップし、後でそれを破棄したい場合があります。 ありがたいことに、JUnitはこのためのもう1つの便利な基本クラスを提供します。

抽象クラスExternalResourceを拡張して、ファイルやデータベース接続などのテストの前に外部リソースを設定できます。実際、前に見たTemporaryFolderルールは[X212X ]ExternalResource。

このクラスを拡張する方法を簡単に見てみましょう。

@Rule
public final ExternalResource externalResource = new ExternalResource() {
    @Override
    protected void before() throws Throwable {
        // code to set up a specific external resource.
    };
    
    @Override
    protected void after() {
        // code to tear down the external resource
    };
};

この例では、外部リソースを定義するときに、 before()メソッドと after()メソッドをオーバーライドして、外部リソースをセットアップおよび破棄する必要があります。

6. クラスルールの適用

これまで、これまで見てきたすべての例は、単一のテストケースメソッドに適用されてきました。 ただし、テストクラスレベルでルールを適用したい場合があります。 これは、@ClassRuleアノテーションを使用して実現できます。

このアノテーションは@Ruleと非常によく似ていますが、テスト全体にルールをラップします— 主な違いは、クラスルールに使用するフィールドが静的でなければならないことです:

@ClassRule
public static TemporaryFolder globalFolder = new TemporaryFolder();

7. カスタムJUnitルールの定義

これまで見てきたように、JUnit4は箱から出してすぐに使える便利なルールをいくつか提供します。 もちろん、独自のカスタムルールを定義することもできます。 カスタムルールを作成するには、TestRuleインターフェイスを実装する必要があります。

カスタムテストメソッド名ロガールールを定義する例を見てみましょう。

public class TestMethodNameLogger implements TestRule {

    private static final Logger LOG = LoggerFactory.getLogger(TestMethodNameLogger.class);

    @Override
    public Statement apply(Statement base, Description description) {
        logInfo("Before test", description);
        try {
            return new Statement() {
                @Override
                public void evaluate() throws Throwable {
                    base.evaluate();
                }
            };
        } finally {
            logInfo("After test", description);
        }
    }

    private void logInfo(String msg, Description description) {
        LOG.info(msg + description.getMethodName());
    }
}

ご覧のとおり、 TestRule インターフェイスには、 apply(Statement、Description)という1つのメソッドが含まれており、Statementのインスタンスを返すためにオーバーライドする必要があります。 このステートメントは、JUnitランタイム内のテストを表しています。 Evaluation()メソッドを呼び出すと、テストが実行されます。

この例では、前後のメッセージをログに記録し、Descriptionオブジェクトから個々のテストのメソッド名を含めます。

8. ルールチェーンの使用

この最後のセクションでは、RuleChainルールを使用していくつかのテストルールを注文する方法を見ていきます。

@Rule
public RuleChain chain = RuleChain.outerRule(new MessageLogger("First rule"))
    .around(new MessageLogger("Second rule"))
    .around(new MessageLogger("Third rule"));

上記の例では、各MessageLoggerコンストラクターに渡されたメッセージを単純に出力する3つのルールのチェーンを作成します。

テストを実行すると、チェーンがどのように順番に適用されるかがわかります。

Starting: First rule
Starting: Second rule
Starting: Third rule
Finished: Third rule
Finished: Second rule
Finished: First rule

9. 結論

要約すると、このチュートリアルでは、JUnit4のルールについて詳しく説明しました。

まず、ルールとは何か、どのように使用できるかを説明することから始めました。 次に、JUnitディストリビューションの一部として提供されるルールについて詳しく見ていきました。

最後に、独自のカスタムルールを定義する方法と、ルールを連鎖させる方法について説明しました。

いつものように、記事の完全なソースコードは、GitHubから入手できます。