codewing codewing - 2 months ago 15
Java Question

Parallelized + Parameterized threads with random execution order for each thread

I've got a class which runs my tests in parallel with parameters. But to check for concurrency problems I want to execute the tests in a random order.
Eg. test1 - test2 - test3 on the first thread and test2 - test1 - test3 on the first thread. (I think you got the point)

This is the code I'm currently working with and I already found some examples using BlockJUnit4ClassRunnerWithParameters but changing Parameterized to BlockJUnit4ClassRunnerWithParameters did obviously not work for me.

Thus my question is: how can I make each thread execute the test in a random order?

Hope you guys can give me some pointers...

public class Parallelized extends Parameterized
{

private static class ThreadPoolScheduler implements RunnerScheduler
{
private ExecutorService executor;

public ThreadPoolScheduler()
{
String threads = System.getProperty("junit.parallel.threads", "16");
int numThreads = Integer.parseInt(threads);
executor = Executors.newFixedThreadPool(numThreads);
}

@Override
public void finished()
{
executor.shutdown();
try
{
executor.awaitTermination(10, TimeUnit.MINUTES);
}
catch (InterruptedException exc)
{
throw new RuntimeException(exc);
}
}

@Override
public void schedule(Runnable childStatement)
{
executor.submit(childStatement);
}
}

public Parallelized(Class klass) throws Throwable
{
super(klass);
setScheduler(new ThreadPoolScheduler());
}
}

Answer

Okay, I found a solution which is fairly simple:

All you need is a ParametersRunnerFactory which creates Runners of type BlockJUnit4ClassRunnerWithParameters. To randomize the test order for each runner you only have to override computeTestMethods() and shuffle the list of methods.

To use your created RunnerFactory, you have to add @Parameterized.UseParametersRunnerFactory(Parallelized.RunnerFactory.class) right below the @RunWith(Parallelized.class) in each class you would like to use the randomized execution order.

Note: If don't add this annotation junit will use the default runner and not your custom runner -> no change.

public static class RunnerFactory implements ParametersRunnerFactory {
    @Override
    public org.junit.runner.Runner createRunnerForTestWithParameters(TestWithParameters test) throws InitializationError {
        return new CustomRunnerWithParameters(test);
    }

}

public static class CustomRunnerWithParameters extends BlockJUnit4ClassRunnerWithParameters {
    private final Object[] parameters;

    @Override
    protected List<FrameworkMethod> computeTestMethods() {
        List<FrameworkMethod> tests = new ArrayList<FrameworkMethod>(super.computeTestMethods());
        Collections.shuffle(tests);
        return tests;
    }

    public CustomRunnerWithParameters(TestWithParameters test) throws InitializationError {
        super(test);
        parameters = test.getParameters().toArray(new Object[test.getParameters().size()]);
    }

    @Override
    public Object createTest() throws Exception {
        return getTestClass().getOnlyConstructor().newInstance(parameters);
    }
}