Ruleを使ったJUnit4.7以降の拡張方法

 前回はテストメソッド実行用Statementクラスを直接拡張しましたが、今回は@Ruleアノテーションを使った拡張方法のエントリーを書いてみます。
JUnit4.7以降のバージョンには@RuleというアノテーションとMethodRuleというインターフェースが用意されています。
(11/2 追記:4.9以降はMethodRuleが非推奨になっています。MethodRuleの替わりにTestRuleというのが出来ているので4.9以降を使用する場合はTestRuleを使うようにしましょう。インターフェースのシグネチャが多少違いますが、やってることは同じです。)

 JUnit4.7以降はMethodRuleの代表的な実装としてExpectedExceptionがあります。こやつはテストケース内での例外をテストしてくれるやつですが4.6までだと

    @Test(expected=IllegalArgumentException.class)
    public void exceptionTest(){
        new Hoge().invoke();
    }

上記のように例外の型だけしか検査できず、例外のメッセージをアサートする場合は自力でいろいろやらないといけませんでした。しかしExpectedExceptionを使用すると下記のようになります。(@Ruleを付加するフィールドはpublicでなければいけません。)

    @Rule
    public ExpectedException exception = ExpectedException.none;

    @Test
    public void exceptionTest(){
        exception.expect(IllegalArgumentException.class); //このコードでExceptionの型をテスト
        exception.expectMessage("えらー"); //このコードでエラーメッセージをテスト可能
        new Hoge().invoke();
    }

んで、上記をふまえた上で前回のような、あるテストクラス内で特定のメソッド(テストケース)のみでいろいろやりたいといった場合に@RuleとMethodRuleを実装したクラスを使ったやり方は下記のようになります。

まずはRuleクラスの作成

public class HogeRule implements MethodRule{
    
    @Override
    public Statement apply(final Statement base,final FrameworkMethod method,final Object target) {
	return new Statement() {
	    @Override
	    public void evaluate() throws Throwable {
		if(method.getAnnotation(MyAnnotation.class) != null)
		    System.out.println("Annotation 発見。");
		System.out.println("かいし〜");
		base.evaluate();
		System.out.println("しゅーりょ〜");
	    }
	};
    }
}

(11/2 追記 4.9以降でMethodRuleではなくTestRuleを使う場合は下記のようになります。)

public class HogeRule implements TestRule{
    @Override
    public Statement apply(final Statement base,final Description description) {
        return new Statement() {
            @Override
            public void evaluate() throws Throwable {
                if (description.getAnnotation(MyAnnotation.class) != null) {
                    System.out.println("Annotation 発見。");
                }
                System.out.println("かいし〜");
                base.evaluate();
                System.out.println("しゅーりょ〜");
            }
        };
    }
}


んで、このHogeRuleを使うテストクラスは下記のようになります。

public class RuleTest {

    @Rule
    public HogeRule rule = new HogeRule();
    
    @BeforeClass
    public static void beforeClass(){
	System.out.println("before class!");
    }
    
    @AfterClass
    public static void afterClass(){
	System.out.println("after class!");
    }
    
    @Before
    public void before(){
	System.out.println("before!");
    }
    
    @After
    public void after(){
	System.out.println("after!");
    }
    
    @MyAnnotation
    @Test
    public void test1() {
	System.out.println("test1");
    }
}

これを実行した場合、コンソールには下記の順番で出力されます。

before class!
Annotation 発見。
かいし〜
before!
test1
after!
しゅーりょ〜
after class!

注意しないといけないのはHogeRule内の無名Statementクラス内の出力処理(Annotation 発見。)は@Beforeよりも先に呼ばれる点でしょうか。

こんな感じで@RuleアノテーションとMethodRuleを使えばいろいろ拡張することが可能になります。

しかし、個人的にpublicフィールドというのがどうしても嫌なんですよねぇ〜。privateフィールドもOKにするにはかなり拡張しないといけなくなります。そのくらい別にいいじゃんという意見はよくわかるのですが・・・・。ただし、4.7以降を使う場合はやはり前回のやり方よりも今回の@Ruleを使う方がJUnitの作法としては良いはず・・・。う〜ん悩ましいです。

なお、MethodRuleの拡張方法はorg.junit.rules.ExpectedExceptionクラスかorg.junit.rules.TestWatchmanクラスをみると非常にわかりやすいと思います。