JUnit API探訪:@Rule, @ClassRule アノテーション

JUnit4.8で@Ruleアノテーションが、4.9でそれのクラス版となる@ClassRuleアノテーションが使えるようになってみたいですね。

調べて行くと、@Ruleアノテーション及び@ClassRuleアノテーションは、TestRuleというインターフェース(での用途)に関連して利用されるもののようです。

@Rule @Before, @Afterアノテーションの代替 メソッドレベルでTestRuleと併せて利用
@ClassRule @BeforeClass, @AfterClassアノテーションの代替 クラスレベルでTestRuleと併せて利用

また、4.7時点では @RuleはMethodRuleに対して利用するものでしたが、4.9にて非推奨となった模様です。(MethodRuleが非推奨→TestRuleを推奨)

@Rule アノテーション

ルールによりテストごとにJUnitに機能を追加することが可能となる。

@Rule アノテーションに関しての概要把握には以下のエントリ辺りが良さげにカバーしてそう。


コアとなるルールは以下。

ExternalResourceルールテスト(ファイル、ソケット、サーバー、データベース接続など)、
それを後に取り壊すことを保証する前に、外部リソースを設定する
ルールの基本クラス(例:TemporaryFolder)
└─TemporaryFolderルールテスト前にファイルを作成、テスト後に削除
Verifierクラスオブジェクトが意図しない状態で終了した場合、テストを失敗させる
└─ErrorCollectorルール1つのテストメソッド内で複数のエラーを収集
TestWatcherルールメソッドの実行中、イベントにロジックを追加する
└─TestNameルールメソッドの実行中、テストの名前を利用出来る
Timeoutルール設定時間を超えた場合、テストを失敗させる
ExpectedExceptionルールスローされる例外に対し柔軟なアサーションを行う

TemporaryFolderルール
  • ExternalResourceのサブクラス。テスト前にファイルを作成、テスト後に削除。

サンプルコード:

import java.io.File;
import java.io.IOException;

import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.TemporaryFolder;
import org.junit.rules.TestName;

public class TemporaryFolderExample {

    @Rule
    public TemporaryFolder temporaryFolderRule = new TemporaryFolder();
    @Rule
    public TestName testNameRule = new TestName();

    @Test
    public void folderTest() throws IOException {
        System.out.println(testNameRule.getMethodName() + "() start.");

        File file = temporaryFolderRule.newFile("junitbc.txt");
        System.out.println(file.getPath());
        //temporaryFolderRule.delete(); │
        //temporaryFolderRule.create(); ↓:IOException occurs!

        File folder = temporaryFolderRule.newFolder("junitbc2012");
        System.out.println(folder.getPath());

        System.out.println(testNameRule.getMethodName() + "() end.");
    }
}
folderTest() start.
C:\DOCUME~1\XXX~1.SHI\LOCALS~1\Temp\junit3577141403479062617\junitbc.txt
C:\DOCUME~1\XXX~1.SHI\LOCALS~1\Temp\junit3577141403479062617\junitbc2012
folderTest() end.
  • 実行経過(メソッド終了前。この状態から、メソッド完了に併せて一時ファイル・フォルダは削除される)

ErrorCollectorルール
『読んで字の通り、例外を集めて保持するクラスです。例外をaddして、最後のverifyでaddされたエラーの個数などでチェックをかけます。』

チェックを掛ける場合、addされたエラーの個数は確認出来るものなのかな?見た感じ個数を取得するメソッドとかは無さそうな感じなのだが…

Javadoc APIにあるように、

@Rule
public ErrorCollector collector= new ErrorCollector();
 
@Test
public void example() {
    collector.addError(new Throwable("first thing went wrong"));
    collector.addError(new Throwable("second thing went wrong"));
    collector.checkThat(getResult(), not(containsString("ERROR!")));
    // all lines will run, and then a combined failure logged at the end.
    //【全ての行は実行されます。そして最後に失敗(情報)が連結され(て表示され)ます】
}

また、こちらのブログエントリにあるように

org.junit.rules.ErrorCollector は、addErrorメソッドにてテストで発生したエラーを集めておけます。
checkThatメソッドでは、テスト値の検証を行います。これらのメソッドでは問題が検知されてもテストが中断されず、
テストメソッド内の処理を最後まで実行することができます。

『失敗となっても最後まで実行され、終わったらそこまでの失敗を全て吐きだす』以外の機能は無い、という認識で良いのかな?先述のJavadoc APIにも

The ErrorCollector rule allows execution of a test to continue after the first problem is found
(for example, to collect _all_ the incorrect rows in a table, and report them all at once):

とあるように、想定外の情報を一挙に出したい場合、という用途に使うべし、という事なのだろうか。一応そういう認識で先に進もう。


※なお、Verifierクラスについてはこの辺のエントリが詳しいかと。日本語のエントリがあるのは助かる。ひとまずこんな感じで使うんだな〜位の認識で留めておこうと思う。

TestNameルール

メソッドの実行中、テストの名前を利用出来る。

  • サンプルコード:
package jp.shinyaa31.junitbc;

import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.TestName;

public class TestNameExample {

    @Rule
    public TestName testNamerule = new TestName();

    @Test
    public void checkAssertThat() {
        System.out.println(testNamerule.getMethodName());
    }

    @Test
    public void 日本語メソッド名の確認() {
        System.out.println(testNamerule.getMethodName());
    }
}
  • 実行結果:
checkAssertThat
日本語メソッド名の確認
Timeoutルール

時間内に終わらなかった場合、失敗とするルール。

public class TimeoutRuleExample {

    @Rule
    public TestRule timeoutRule = new Timeout(1000);

    @Test
    public void timeoutTest() {
        while(true) {
            System.out.println(System.currentTimeMillis());
        }
    }
}

実行結果:

java.lang.Exception: test timed out after 1000 milliseconds
	at java.io.FileOutputStream.writeBytes(Native Method)
	at java.io.FileOutputStream.write(FileOutputStream.java:260)
        :
        :
ExpectedExceptionルール

例外を期待するルール。
例外の種類の他にも、メッセージに含まれる文字列で判定を行う事も出来るようです。

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

    @Test
    public void throwsNothing() {
        // no exception expected, none thrown: passes.
    }

    @Test
    public void throwsNullPointerException() {
        thrown.expect(NullPointerException.class);
        throw new NullPointerException();
    }

    @Test
    public void throwsNullPointerExceptionWithMessage() {
        thrown.expect(NullPointerException.class);
        thrown.expectMessage("happened?");
        thrown.expectMessage(startsWith("What"));
        throw new NullPointerException("What happened?");
    }
    @Test
    public void throwsNullPointerExceptionWithMessage2nd() {
        thrown.expect(NullPointerException.class);
        thrown.expectMessage("きのこ");
        thrown.expectMessage(startsWith("この先"));
        throw new NullPointerException("この先生きのこるための");
    }

@ClassRule アノテーション

backpaper0さんのエントリにあるように、"@BeforeClassと@AfterClassの代替となるアノテーション"となるのがこの@ClassRule。基本メソッド単位のRuleがClassRuleとしてクラス単位に範囲を広げただけ、という認識なので個別にソース写経は行わず、内容把握に留めておく事にしようと思います。(若干手抜き)