Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Collect and run all junit tests in parallel with each test class in its own JVM (parallelization by class, not by method)

Problem
I've a bunch of junit tests (many with custom runners such as PowerMockRunner or JUnitParamsRunner) all under some root package tests (they are in various subpackages of tests at various depths).

I'd like to collect all the tests under package tests and run each test class in a different JVM, in parallel. Ideally, the parallelization would be configurable, but a default of number_of_cores is totally fine as well. Note that I do not want to run each method in its own JVM, but each class.

Background
I'm using PowerMock combined with JUnitParams via annotations @RunWith(PowerMockRunner.class) and @PowerMockRunnerDelegate(JUnitParamsRunner.class) for many of my tests. I have ~9000 unit tests which complete in an "ok" amount of time but I've an 8-core CPU and the systems is heavily underutilized with the default single-test-at-a-time runner. As I run the tests quite often, the extra time adds up and I really want to run the test classes in parallel.

Note that, unfortunately, in a good number of the tests I need to mock static methods which is part of the reason I'm using PowerMock.

What I've Tried
Having to mock static methods makes it impossible to use something like com.googlecode.junittoolbox.ParallelSuite (which was my initial solution) since it runs everything in the same JVM and the static mocking gets all interleaved and messed up. Or so it seems to me at least based on the errors I get.

I don't know the JUnit stack at all, but after poking around, it appears that another option might be to try to write and inject my own RunnerBuilder -- but I'm not sure if I can even spawn another JVM process from within a RunnerBuilder, unlikely. I think the proper solution would be some kind of harness that lives as a gradle task.

I also JUST discovered some Android Studio (Intellij's) test options but the only available fork option is method which is not what I want. I am currently exploring this solution so perhaps I will figure it out but I thought I'd ask the community in parallel since I haven't had much lock yet.

UPDATE: Finally was able to get Android Studio (Intellij) to collect all my tests using options Test Kind: All in directory (for some reason the package option did not do recursive searching) and picking fork mode Class. However, this still runs each test class found sequentially and there are no options that I see about parallelization. This is so close to what I want but not quite... :(

like image 228
Creos Avatar asked Jul 02 '16 17:07

Creos


People also ask

Can we do parallel testing in JUnit?

Yes, You can. Show activity on this post. JUnit Toolbox provides JUnit runners for parallel execution of tests.

Does JUnit execute in parallel?

In JUnit the feature files are run in parallel rather than scenarios, which means all the scenarios in a feature file will be executed by the same thread. You can use either Maven Surefire or Failsafe plugin to execute the runners.

Are unit tests run in parallel?

By default, unittest-parallel runs unit tests on all CPU cores available.


2 Answers

Instead of using Intellij's (Android Studio) built-in JUnit run configurations, I noticed that Android Studio has a bunch of pre-build gradle tasks some of which refer to testing. Those however, exhibited the same sequential execution problem. I then found Run parallel test task using gradle and added the following statement to my root build.gradle file:

subprojects {
    tasks.withType(Test) {
        maxParallelForks = Runtime.runtime.availableProcessors()
    }
}

This works great, my CPU is now pegged to 100% (for most of the run, as the number of outstanding test classes becomes < avail processors obviously utilization goes down).

The downside to this solution is that it does not integrate with Android Studio's (Intellij) pretty junit runner UI. So while the gradle task is progressing I cannot really see the rate of test completion, etc. At the end of the task execution, it just spits out the total runtime and a link to an HTML generated report. This is a minor point and I can totally live with it, but it would be nice if I could figure out how to improve the solution to use the the JUnit runner UI.

like image 135
Creos Avatar answered Sep 20 '22 12:09

Creos


Maybe this was not possible when the question posted but now you can do it easily in android studio.

I am using gradle build tools: 'com.android.tools.build:gradle:2.2.3'

And I added the following in my root build.gradle file.

allprojects {
    // ...
    tasks.withType(Test) {
        maxParallelForks = Runtime.runtime.availableProcessors()
    }
}

Now, I have multiple Gradle Test Executor runners for my tests. The more cores of your running machine, the mores executors you have!

Thanks for sharing your original answer!

like image 24
madlymad Avatar answered Sep 21 '22 12:09

madlymad