読者です 読者をやめる 読者になる 読者になる

JUnit4.7 の新機能 Rules とは

Java JUnit

Rules とは

JUnit4.7から@Ruleアノテーションが追加されました。@Ruleアノテーションは、org.junit.rules.MethodRuleインターフェースのサブクラスによって定義された振る舞いをテストメソッドに追加します。

MethodRuleの組み込み実装クラス

MethodRuleの具象クラスとして、以下のクラスが提供されています。

MethodRule
 ├ Verifier           : オブジェクトの状態が不正な場合にテストを失敗させる
 │  └ ErrorCollector : 1つのテストメソッドの複数のエラーを集集する
 ├ ExpectedException  : スローされた例外について柔軟なアサーションを行う
 ├ ExternalResource   : サーバの起動停止などの外部リソースの操作を行う
 │  └ TemporaryFolder: テストメソッド前に一時ファイルを作成し、終了後に削除する
 ├ TestWatchman       : メソッド実行時のイベントに対してロジックを追加する
 │  └ TestName       : メソッド実行中に使用する名前を記憶する
 └ Timeout            : 設定時間でテストを失敗させる

テストケースでは、JUnitにより提供されているこれらのRuleを使用することもできますし、それを拡張して新たなルールを作成することもできます。また、自身で新しいRuleを作成することもできます。

MethodRuleの中身は

applyメソッドが定義されています。具象クラスではこのapplyメソッドを実装します。雰囲気としてはAOPですね。

public interface MethodRule {
    Statement apply(Statement base, FrameworkMethod method, Object target);
}

methodが実行しているテストメソッドで、targetがテスト対象のオブジェクトです。そして、Statementにより振る舞いの変更を行います。

例えばVerifierでは

Verifier は、オブジェクトの状態が不正な場合にテストを失敗させるルールです。こんな感じで実装されています。

public class Verifier implements MethodRule {
    public Statement apply(final Statement base, FrameworkMethod method, Object target) {
        return new Statement() {
            @Override public void evaluate() throws Throwable {
                base.evaluate();
                verify();
            }
        };
    }
    protected void verify() throws Throwable { }
}

Verifierを使用する場合は、verify() メソッドをオーバーライドして検証処理を追加することで、テストメソッドに振る舞いを追加することになります。

Verifierルールの実装

以下の例では、Verifierにてロガーがnullかどうかの検証処理をテストメソッドに追加します。

import java.util.logging.Logger;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.MethodRule;
import org.junit.rules.Verifier;
import static org.junit.Assert.*;
import static org.hamcrest.CoreMatchers.*;

public class Example {

    public static class ErrorLogVerifier {
        private static Logger logger = Logger.getLogger("hoge");

        @Rule public MethodRule verifier = new Verifier(){
            @Override public void verify() {
                assertThat(logger, nullValue());
            }
        };
                 
        @Test public void testfoo() {
            assertThat("foo", is("foo"));
        }
        @Test public void testbar() {
            assertThat("bar", is("bar"));
        }
    }
}

2つのテストメソッドは、追加した検証ルールにより(hogeというロガーは無いため、verify()による検証が失敗するため)失敗となります。

TemporaryFolder Rule

TemporaryFolder Rule は、ファイルやフォルダを作成できるようにするRuleです。テストの成功や失敗に関わらず、各テスト終了後には作成したファイルやフォルダが削除されることが保障されます。

import java.io.File;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.TemporaryFolder;
import static org.hamcrest.CoreMatchers.*;
import static org.junit.Assert.*;

public class HasTempFolder {
    @Rule
    public TemporaryFolder folder= new TemporaryFolder();

    @Test public void testUsingTempFolder() throws Exception {
        File createdFolder = folder.newFolder("folder");
        File createdFile = folder.newFile("folder\\myfile.txt");

        System.out.print(createdFile.getAbsolutePath());
        
        assertThat(createdFile.canWrite(), is(true));
        assertThat(createdFile.canRead(), is(true));
    }
}

以下の様な一時ファイルが作成され、テストメソッド毎に削除されます。

C:\DOCUME~1・・\Temp\junit2746898332630277904\folder\myfile.txt

TemporaryFolder Ruleの中身は、

TemporaryFolder の親である ExternalResource はこんな実装になってます。

public abstract class ExternalResource implements MethodRule {
    public final Statement apply(final Statement base,
            FrameworkMethod method, Object target) {
        return new Statement() {
            @Override
            public void evaluate() throws Throwable {
                before();
                try {
                    base.evaluate();
                } finally {
                    after();
                }
            }
        };
    }
    ・・・

ステートメントの実行前に before() を、実行後に finally で after() を実行します。つまり、テストメソッドの前後で before と after が実行されます。
そして、TemporaryFolder で、一時ファイルの作成と削除の処理が実装されています。詳細は省略しますが、以下のような実装です。

public class TemporaryFolder extends ExternalResource {
    private File folder;

    @Override
    protected void before() throws Throwable {
        create();  // 一時ファイル作成
    }

    @Override
    protected void after() {
        delete();  // 一時ファイル削除
    }
    ・・・

テストメソッドにAOPのように振る舞いを追加しているのが良くわかると思います。

まとめ

@Ruleアノテーションにてテストメソッドに任意の振る舞いを追加できます。その他のルールについてはJUnit4.7 の新機能 Rules とは〜その2 - A Memorandumにて。





実践 JUnit ―達人プログラマーのユニットテスト技法

実践 JUnit ―達人プログラマーのユニットテスト技法