concurrent test runs in JUnit 4 are still in experimental phase, a very good alternative to running parallel tests is migrating to
TestNG, although if you don't like it or have no possibility to simply migrate to it, you can still use a custom runner by extending the
BlockJUnit4ClassRunner class.
first we parametrize the concurrency using an Annotation:
Concurrent.java
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD})
public @interface Concurrent {
int threadPoolSize() default 3;
int invocationCount() default 10;
long timeOut() default 10000;
}
|
and here comes the actual runner which controls our parallel runs:
ConcurrentRunner.java
public class ConcurrentRunner extends BlockJUnit4ClassRunner {
public ConcurrentRunner(Class<?> klass) throws InitializationError {
super(klass);
}
@Override
public void run(final RunNotifier notifier) {
// run BeforeClass tests
final List<FrameworkMethod> beforeClassMethods = getTestClass().getAnnotatedMethods(BeforeClass.class);
for (final FrameworkMethod frameworkMethod : beforeClassMethods) {
notifier.fireTestStarted(getDescription());
try {
methodBlock(frameworkMethod).evaluate();
}
catch (Throwable e) {
Exceptions.throwException(e);
}
}
// run non-concurrent tests
final List<FrameworkMethod> testMethods = getTestClass().getAnnotatedMethods(Test.class);
for (final FrameworkMethod frameworkMethod : testMethods) {
if (frameworkMethod.getAnnotation(Concurrent.class) == null) {
notifier.fireTestRunStarted(getDescription());
runChild(frameworkMethod, notifier);
}
}
// run concurrent tests, Before and After tests will run automatically before and after each
// test
final List<FrameworkMethod> concurrentMethods = getTestClass().getAnnotatedMethods(Concurrent.class);
for (final FrameworkMethod frameworkMethod : concurrentMethods) {
final Concurrent concurrent = frameworkMethod.getAnnotation(Concurrent.class);
if (concurrent != null) {
final int invocationCount = concurrent.invocationCount();
final int threadPoolSize = concurrent.threadPoolSize();
final long timeout = concurrent.timeOut();
final ExecutorService executorService = Executors.newFixedThreadPool(threadPoolSize);
final List<Callable<Object>> calls = new ArrayList<Callable<Object>>();
for (int i = 0; i < invocationCount; i++)
calls.add(new JUnitRunner(frameworkMethod, notifier));
try {
executorService.invokeAll(calls, timeout, TimeUnit.MILLISECONDS);
}
catch (InterruptedException e) {
Exceptions.throwException(e);
}
executorService.shutdown();
}
}
// run AfterClass tests
final List<FrameworkMethod> afterClassMethods = getTestClass().getAnnotatedMethods(AfterClass.class);
for (final FrameworkMethod frameworkMethod : afterClassMethods) {
try {
methodBlock(frameworkMethod).evaluate();
}
catch (Throwable e) {
Exceptions.throwException(e);
}
}
}
private class JUnitRunner implements Callable<Object> {
private final FrameworkMethod method;
private final RunNotifier notifier;
private JUnitRunner(final FrameworkMethod method, final RunNotifier notifier) {
this.method = method;
this.notifier = notifier;
}
@Override
public Object call() {
notifier.fireTestRunStarted(getDescription());
runChild(method, notifier);
return null;
}
}
}
|
all we need is to test the stuff:
ConcurrentRunnerTest.java
@RunWith(ConcurrentRunner.class)
public class ConcurrentRunnerTest {
private static Object aGlobalObject = null;
private static Integer aGlobalInteger = null;
@BeforeClass
public static void setUp() throws Exception {
if (aGlobalObject == null) {
aGlobalObject = new String("aGlobalObject");
System.out.println("=========>> Setup test");
}
}
@AfterClass
public static void coolDown() throws Exception {
assertNotNull(aGlobalObject);
assertNotNull(aGlobalInteger);
assertEquals(aGlobalInteger.intValue(), 591);
aGlobalObject = null;
aGlobalInteger = null;
System.out.println("=========>> coolDown");
}
@BeforeClass
public static void setUpAgain() throws Exception {
if (aGlobalInteger == null) {
aGlobalInteger = new Integer(4177);
System.out.println("=========>> Setup test 2");
}
}
@Test
public void testOnce() {
assertNotNull(aGlobalObject);
assertNotNull(aGlobalInteger);
aGlobalInteger = 591;
System.out.println("===> testOnce");
}
@Test
@Concurrent(threadPoolSize = 4, invocationCount = 10, timeOut = 60000)
public void testConcurrent() {
assertNotNull(aGlobalObject);
assertNotNull(aGlobalInteger);
System.out.println("===> testConcurrent: " + Thread.currentThread());
}
@Test
public void testOnceAgain() {
assertNotNull(aGlobalObject);
assertNotNull(aGlobalInteger);
System.out.println("===> testOnceAgain");
}
}
|
and here is the Output:
=========>> Setup test 2
=========>> Setup test
===> testOnce
===> testOnceAgain
===> testConcurrent: Thread[pool-1-thread-2,5,main]
===> testConcurrent: Thread[pool-1-thread-2,5,main]
===> testConcurrent: Thread[pool-1-thread-1,5,main]
===> testConcurrent: Thread[pool-1-thread-2,5,main]
===> testConcurrent: Thread[pool-1-thread-3,5,main]
===> testConcurrent: Thread[pool-1-thread-3,5,main]
===> testConcurrent: Thread[pool-1-thread-3,5,main]
===> testConcurrent: Thread[pool-1-thread-3,5,main]
===> testConcurrent: Thread[pool-1-thread-2,5,main]
===> testConcurrent: Thread[pool-1-thread-4,5,main]
=========>> coolDown