JUnit4でテストクラスの並列実行

最近、スローテスト問題というのが深刻になっています。
JUnitは基本的に逐次実行されるため、高性能なPCでも待たされる処理があるとどうしても時間がかかってしまいます。
JUnit3では川口さんが作成した「Parallel Junit」があるので並列実行することができるのですが、JUnit4対応はされていません。
なので、JUnit4で何かないかと調べてみましたが、使えそうなものはなさそうでした。
なければ作るというわけで、とりあえずSuiteクラスを継承して実装してみました。

package org.cactusman;

import java.util.ArrayList;
import java.util.List;

import org.junit.runner.Runner;
import org.junit.runner.notification.RunNotifier;
import org.junit.runners.Suite;
import org.junit.runners.model.InitializationError;
import org.junit.runners.model.RunnerBuilder;
import org.junit.runners.model.Statement;

public class ParallelSuite extends Suite{

	public ParallelSuite(Class<?> klass, RunnerBuilder builder) throws InitializationError {
		super(builder, klass, getAnnotatedClasses(klass));
	}

	private static Class<?>[] getAnnotatedClasses(Class<?> klass) throws InitializationError {
		SuiteClasses annotation= klass.getAnnotation(SuiteClasses.class);
		if (annotation == null)
			throw new InitializationError(String.format("class '%s' must have a SuiteClasses annotation", klass.getName()));
		return annotation.value();
	}

	protected Statement childrenInvoker(final RunNotifier notifier) {
		return new Statement() {
			@Override
			public void evaluate() {
				runChildren(notifier);
			}
		};
	}

	private void runChildren(RunNotifier notifier) {
		final RunNotifier _notifier = notifier;
		List<Thread> threadList = new ArrayList<Thread>();
		for (final Runner each : getChildren()) {
			Thread thread = new Thread(){
				@Override
				public void run() {
					runChild(each, _notifier);
				}
			};
			threadList.add(thread);
		}
		for (Thread thread : threadList) {
			thread.start();
		}
		for (Thread thread : threadList) {
			try {
				thread.join();
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
	}
}

あくまでもとりあえず実装なのでかなりいい加減です。
登録したテストクラスの数だけスレッドが立ち上がるため、行儀のいいやり方じゃないです。
この辺はConcurrency Utilを使ったほうがいいですね。
それはおいおいと。


使い方はこのクラスをテストソースに追加し、後は以下のように普通のSuiteクラスと同じになります。

package org.cactusman;

import org.junit.runner.RunWith;
import org.junit.runners.Suite.SuiteClasses;

@RunWith(ParallelSuite.class)
@SuiteClasses({Test1.class, Test2.class})
public class Test {
}

これでTest1とTest2がマルチスレッドで実行されます。
それぞれのテストクラス内のメソッドは逐次実行されるため、並列化したいところはクラスに分けないといけません。
この辺もなんとかしたいところですね。


このSuiteを継承して機能拡張するのは、並列実行だけの話ではなくJUnit4を拡張し自分好みのSuiteを作成できる手段です。
今回の話以上に何か面白いことができるかもしれませんね。