S2JUnit4をJUnit4.8でも使いたい

このブログでJavaについて書くのは初めてな気がしますが、仕事では基本的にJava使いです。

Seasar2を使い始めたのは、今の会社に入ってからなのですが、S2JUnit4が中々使いやすいので、今のプロジェクトでも導入してみてました。

しかし、S2JUnit4は、JUnit4.4でしか(完全には)動作せず、4.5や自分のプロジェクトで使っている4.8とかでは、一部メソッドだけの実行が上手くいかなかったり、Enclosedの内部クラスで上手く動作しなかったりと、なにかと不便です。
特に、メソッド単位で実行できないのは、Quick JUnitの恩恵があまり受けられないため、わりとストレスに感じていました。ですが、今のプロジェクトでは、テストに限定して導入しているため、全体はDIをするような作りになっていませんし、テストだけだからまぁいっか・・・という感じでした。

それでも、ひたすらコーディングしていると、それらのわずらわしさがやたらと気になりはじめたので、ちょっと調査してやってみました。

S2JUnit4でメソッド単位実行できるように

まず、上手くいかない原因は、Sorterに渡しているS2TestClassRunnerのインターフェースがRunnerで作成しているのですが、Sorterに渡すのはSortableインターフェースでなければならないため、ここに齟齬が発生していたようでした。S2TestClassRunner自体はSortableインターフェースをimplementsしていたので、これをそのまま渡せばいいんじゃまいか、ということで、以下のように派生クラスを作成してやってみました。

まずはSeasar2クラスの派生クラス。

package hoge;

import org.junit.runner.Description;
import org.junit.runner.manipulation.Filter;
import org.junit.runner.manipulation.NoTestsRemainException;
import org.junit.runner.manipulation.Sorter;
import org.junit.runner.notification.RunNotifier;
import org.seasar.framework.unit.S2TestClassRunner;
import org.seasar.framework.unit.Seasar2;

public class Seasar2Extension extends Seasar2 {

  private final S2TestClassRunner extendedDelegate;

  /**
   * @param clazz
   * @throws Exception
   */
  public Seasar2Extension(final Class<?> clazz) throws Exception {
    super(clazz);
    setProvider(new S2DefaultProviderExtension());
    this.extendedDelegate = (S2TestClassRunner) createTestClassRunner(clazz);
  }

  @Override
  public Description getDescription() {
    return extendedDelegate.getDescription();
  }

  @Override
  public void run(final RunNotifier notifier) {
    extendedDelegate.run(notifier);
  }

  @Override
  public void filter(final Filter filter) throws NoTestsRemainException {
    filter.apply(extendedDelegate);
  }

  @Override
  public void sort(final Sorter sorter) {
    sorter.apply(extendedDelegate);
  }
}

S2TestClassRunnerを生成するためのProviderについても、派生クラスを定義しておきます。

package hoge;

import org.junit.runner.Runner;
import org.seasar.framework.unit.S2Parameterized;
import org.seasar.framework.unit.S2TestClassMethodsRunner;
import org.seasar.framework.unit.Seasar2.DefaultProvider;

public class S2DefaultProviderExtension extends DefaultProvider {

  @Override
  public Runner createTestClassRunner(final Class<?> clazz) throws Exception {

    if (hasParameterAnnotation(clazz)) {
      return new S2Parameterized(clazz);
    }
    return new S2TestClassRunnerExtension(clazz,
                                          new S2TestClassMethodsRunner(clazz));
  }
}

S2TestClassRunnerExtensionは、Seaser2Extensionと同様に、filterとsortでsorterなどに渡しているdelegateを、Runnerインターフェースではなく、S2TestClassMethodRunnerにして渡しているだけです。

実際に調べながらやったので、2時間くらいはかかりましたが、これでQuick JUnitも使えるようになり、同時にEnclosedしたクラスの内部クラスで@RunWithを使っても、問題なく動作するようになりました。

後はjmockitと同時に動かせればいいのですが・・・、まぁその辺はできなくてもなんとかなるので大丈夫です。

もし同じように悩んでいる方の参考になれば。