JUnit - @Rule・@ClassRule使用方法の備忘録

# はじめに
みなさんはJUnit使ってますか？

Java用のテスティングフレームワークはTestNGなどもありますが、JUnitを使っている人が多いと思います。

そんなJUnitの`@Rule`・`@ClassRule`について、備忘録として自分なりにまとめてみました。
間違っている場合もありますのでご容赦ください。

なお、実装例の`package`文・`import`文は省略しています。

# 概要
`@Rule`は`@Before`・`@After`、
`@ClassRule`は`@BeforeClass`・`@AfterClass`の代替だそうです。
実際にはもっといろいろなことが出来るようです。

今後`@Before`や`@After`などが非推奨となることはないとは思いますが、読めるようにはしておきたいですね。

# `TestRule`クラスについて
`TestRule`クラスを継承したクラスのインスタンスを、テストクラスの`public`フィールドに代入しておくことによって、テストに関する便利な機能が使えます。
(`@ClassRule`の場合は`static`にもしなければなりません。)

下記は、テストケース内で動的にテストケース名を取得する例です。

```java:テストケース名を取得する
public class TestClass {
    // テストケース名を取得するルールを定義
    @Rule
    public TestName testName = new TestName();

    @Test
    public void testMethod() {
        System.out.println("Method Name is " + testName.getMethodName());
    }
}
```

```txt:実行結果
Method Name is testMethod
```

# 前処理・後処理の記述方法について
前処理・後処理を書くには、`ExternalResource`クラスが便利です。
クラス名に`Resource`と付いてますが、特にリソースに関する処理でなくても大丈夫です。

このクラスをインスタンス化する際に、`before`・`after`メソッドをオーバーライドすることで、それぞれ前処理・後処理を定義できます。

よくある使い方としては、ログに関する処理です。

下記では、例としてテストクラス・ケース実行の前後にテストケース名を出力しています。

テストケース名は`ExternalResource`クラスと前述の`TestName`クラスで出力し、テストクラス名は`TestRule`クラスを自分で拡張して取得・出力しています。

```java:テスト実行の前後にログを出力する
public class TestClass {
    // テストクラス名を出力するルールを作成
    @ClassRule
    public static TestRule classRule = new TestRule() {
        @Override
        public Statement apply(Statement arg0, Description arg1) {
            // Statementクラスの匿名クラスを作成し、
            // テストクラス名を出力するようにevaluateメソッドをオーバーライドする。
            return new Statement() {
                private final Description description = arg1;
                private final Statement base = arg0;

                @Override
                public void evaluate() throws Throwable {
                    // テスト実行前に行う処理
                    System.out.println("Class : " + this.description.getClassName() + " start.");

                    // テストを実行する
                    // このメソッドを実行すると、実際にテストケースが実行される。
                    base.evaluate();

                    // テスト実行後に行う処理
                    System.out.println("Class : " + this.description.getClassName() + "end.");
                }
            };
        }
    };


    // テストケース名を出力するルール
    @Rule
    public TestRule rule = new ExternalResource() {
        @Override
        public void before() {
            System.out.println("Method : " + testName.getMethodName() + "start.");
        }
        @Override
        public void after() {
            System.out.println("Method : " + testName.getMethodName() + "end.");
        }
    };
    @Rule
    public TestName testName = new TestName();


    @Test
    public void testMethod1() {
        System.out.println("testMethod1 runnning...");
    }

    @Test
    public void testMethod2() {
        System.out.println("testMethod2 runnning...");
    }
```

```txt:実行結果
Class : junit.TestClass start.
Method : testMethod1start.
testMethod1 runnning...
Method : testMethod1end.
Method : testMethod2start.
testMethod2 runnning...
Method : testMethod2end.
Class : junit.TestClassend.
```

テストクラス名を出力するルールについてですが、

* `TestRule`クラスの匿名クラスを作成し、
* `apply`メソッドをオーバーライドし、
* その戻り値として`Statement`クラスの匿名クラスを作成し、
* `evaluate`メソッドをオーバーライドし、
* その中でテストクラス名の出力を行なっています。

ややこしいですが、やっていることはそれほど難しくないのですぐわかると思います。
`@Before`・`@After`の代わりとして使うだけなら、このコードをコピペして、`base.evaluate();`の前後にやりたい処理を記述するだけです。

### 2016/09/09 追記
Java8のラムダ式を使うと、以下のように（比較的）簡潔に書けます。

```java:テストケース実行の前後にテストクラス・ケース名を出力する
@Rule
public TestRule logRule = (Statement statement, Description description) -> new Statement() {
    @Override
    public void evaluate() throws Throwable {
        System.out.println("Start : " + description.getClassName() + "#" + description.getMethodName());
        try {
            statement.evaluate();
        } finally {
            // 例外発生時にも終了ログを出力する
            System.out.println("End : " + description.getClassName() + "#" + description.getMethodName());
        }
    }
};
```

## 補足
このコードの`@Rule`や`@ClassRule`は、親クラスに書いていてもちゃんと実行中のテストクラス・ケース名を取得してくれます。

# おわりに
ログ処理、DB処理等で使うことがメインになりそうですが、もっとおもしろい使い方とかあるのか調べてみたいです。
（AOPのようなことができるみたいです。）
詳しくは[JUnitの公式ドキュメント](https://github.com/junit-team/junit4/wiki/Rules)や[Javadoc](http://junit.org/junit4/javadoc/latest/index.html)等を参照してください。