I'm using JUnit 4.4 and Maven and I have a large number of long-running integration tests.
When it comes to parallelizing test suites there are a few solutions that allow me to run each test method in a single test-class in parallel. But all of these require that I change the tests in one way or another.
I really think it would be a much cleaner solution to run X different test classes in X threads in parallel. I have hundreds of tests so I don't really care about threading individual test-classes.
Is there any way to do this?
Use maven plugin:
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>2.7.1</version>
<configuration>
<parallel>classes</parallel>
<threadCount>5</threadCount>
</configuration>
</plugin>
</plugins>
</build>
From junit 4.7 it's now possible to run tests in parallel without using TestNG. Actually it has been possible since 4.6, but there are a number of fixes being made in 4.7 that will make it a viable option. You may also run parallel tests with spring, which you can read about here
Inspired by JUnit's experimental ParallelComputer runner I've built my own ParallelSuite and ParallelParameterized runners. Using these runners one can easily parallelize test suites and parameterized tests.
ParallelSuite.java
public class ParallelSuite extends Suite {
public ParallelSuite(Class<?> klass, RunnerBuilder builder) throws InitializationError {
super(klass, builder);
setScheduler(new RunnerScheduler() {
private final ExecutorService service = Executors.newFixedThreadPool(4);
public void schedule(Runnable childStatement) {
service.submit(childStatement);
}
public void finished() {
try {
service.shutdown();
service.awaitTermination(Long.MAX_VALUE, TimeUnit.NANOSECONDS);
} catch (InterruptedException e) {
e.printStackTrace(System.err);
}
}
});
}
}
ParallelParameterized.java
public class ParallelParameterized extends Parameterized {
public ParallelParameterized(Class<?> arg0) throws Throwable {
super(arg0);
setScheduler(new RunnerScheduler() {
private final ExecutorService service = Executors.newFixedThreadPool(8);
public void schedule(Runnable childStatement) {
service.submit(childStatement);
}
public void finished() {
try {
service.shutdown();
service.awaitTermination(Long.MAX_VALUE, TimeUnit.NANOSECONDS);
} catch (InterruptedException e) {
e.printStackTrace(System.err);
}
}
});
}
}
Usage is simple. Just change #RunWith annotations value to one of these Parallel* classes.
#RunWith(ParallelSuite.class)
#SuiteClasses({ATest.class, BTest.class, CTest.class})
public class ABCSuite {}
tempus-fugit offers something similar, check the docs for details. It relies on JUnit 4.7 and you just mark your test to #RunWith(ConcurrentTestRunner).
Cheers
You can check out the open source library - Test Load Balancer. It does exactly what you ask for - run different test classes in parallel. This integrates at the ant-junit level so that you do not have to change your tests in anyway. I am one of the authors of the library.
Also, think about not running them in threads as you may need a process level sandbox. For example, if you are hitting a DB in your integration tests, you do not want one test to fail because another test added some data in a different thread. Most of the times, tests are not written with this in mind.
Finally, how have solved this problem till now?
You can run the tests in parallel using ParallelComputer provided by Junit itself. Here's a small snippet to get you started.
Class[] cls = { TestCase1.class, TestCase2.class };
Result result = JUnitCore.runClasses(ParallelComputer.classes(), cls);
List<Failure> failures = result.getFailures();
This will help when you need to run tests from code as it has no dependencies on Maven or any other build management tools.
Please note that, this will run all test cases in parallel, if you have any dependencies between different test cases it might result in false positives. You SHOULD NOT have interdependent tests anyway.
TestNG can do that (this was my first reflex - then I saw you're already having a lot of testcases).
For JUnit, look at parallel-junit.
Another choice: Punner, a new parallel junit runner and maven plugin. You don't have to change your code, copy it to your pom.xml:
<!-- Disable default surefire based testing -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>2.20</version>
<configuration>
<skip>true</skip>
</configuration>
</plugin>
<plugin>
<groupId>com.github.marks-yag</groupId>
<artifactId>punner-maven-plugin</artifactId>
<version>${version}</version>
<configuration>
</configuration>
<executions>
<execution>
<id>test</id>
<phase>test</phase>
<goals>
<goal>test</goal>
</goals>
</execution>
</executions>
</plugin>
Punner can run test methods in parallel, can keep test outputs separately and clean.
Punner will reduce your mvn console outputs, like this:
[INFO] --- punner-maven-plugin:0.9.13:test (test) # ipc ---
[INFO] Punner report directory: /Users/guile/workspace/ipc/target/punner-reports
[INFO]
[INFO] com.github.yag.ipc.IPCTest.testConnectionHandler.............. PASSED
[INFO] com.github.yag.ipc.IPCTest.testSequence....................... PASSED
[INFO] com.github.yag.ipc.IPCTest.testPartialContent................. PASSED
[INFO] com.github.yag.ipc.IPCTest.testResponseContent................ PASSED
[INFO] com.github.yag.ipc.IPCTest.testPingPong....................... PASSED
[INFO] com.github.yag.ipc.IPCTest.testServerClose.................... PASSED
[INFO] com.github.yag.ipc.IPCTest.testServerSideHeartbeatTimeout..... PASSED
[INFO] com.github.yag.ipc.IPCTest.testClientSideHeartbeatTimeout..... PASSED
[INFO] com.github.yag.ipc.IPCTest.testClientSideHeartbeat............ PASSED
[INFO] com.github.yag.ipc.IPCTest.testClientReconnect................ PASSED
[INFO]
[INFO] Tests run: 10, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 10.952 sec, Time saved: 25.919 sec.
Punner produce surefire compatible outputs, you can also get raw log data and a markdown format report from reports directory:
➜ ipc git:(develop) ll target/punner-reports
total 104
-rw-r--r-- 1 guile staff 11K Oct 15 23:07 TEST-com.github.yag.ipc.IPCTest.xml
-rw-r--r-- 1 guile staff 298B Oct 15 23:07 com.github.yag.ipc.IPCTest.txt
drwxr-xr-x 12 guile staff 384B Oct 8 00:50 logs
-rw-r--r-- 1 guile staff 33K Oct 15 23:07 report.md
Punner is my personal project, I written Punner to speed up unit test phase of some other projects such as IPC framework, fine-grained locking, journal service, distributed workflow engine, etc. It saved a lot of my waiting time.
Punner don't support some advanced feature yet. I'm very glad if you could try it and give me some feedback.
You can change your test to be TestNg test in a minute (you just need to change imports), TestNG is the best in parallel testing.
You could try Gridgain that lets you run distribute your tests across a compute grid.
Related
I use the Surefire Maven plugin
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>2.18.1</version>
<configuration>
<argLine>-Dhttp.proxyHost=${http.proxyHost}
-Dhttp.proxyPort=${http.proxyPort}</argLine>
</configuration>
</plugin>
to run multiple JBehave tests with JUnitStories
public class SystemTest extends JUnitStories {
protecetd List<String> storyPaths() {
// returns a list of multiple JBehave files
}
}
and get a single-line report that summarizes the whole test suite:
-------------------------------------------------------------------------------
Test set: <path>.SystemTest
-------------------------------------------------------------------------------
Tests run: 1, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 12.803 sec -
in <path>.SystemTest
Can I configure JUnitStories or Surefire such that I get one line for each file given by storyPaths()?
After trying around with the configuration for a while, I gave up and wrote a custom JBehave reporter that writes multiple JUnit result files, one for each story file.
This question already has an answer here:
Maven parallel test output
(1 answer)
Closed 5 years ago.
I am using Java util logging (JUL) in a Junit test class with JUL working in default config (i.e. printing to console).
Logs statements from methods annotated with #BeforeClass and #AfterClass are getting printed when they are executed but logs in '#Test' methods are printed only after all test execution is finished.
Not sure what exactly is wrong because the same were working earlier.
Logger instantiation:
private static Logger logger = Logger.getLogger(MainIntegrationTest.class.getName());
Logger use:
logger.info("start test");
The test cases are run using maven. I noticed that this started happening only after I started running the test classes in parallel using this surefire configuration:
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>2.20</version>
<configuration>
<parallel>classes</parallel>
<useUnlimitedThreads>true</useUnlimitedThreads>
</configuration>
</plugin>
The logs are printed at test execution time if I run the test classes in serial fashion.
The test cases are run using maven. I noticed that this started happening only after I started running the test classes in parallel using this surefire configuration
The output would become scrambled if all tests were writing to console concurrently. For concurrent execution the results have to be buffered for the whole run and then written to the console as one chunk. The only safe time to write to the console is when the test is complete.
Even if the buffering was performed line by line this could lead to interleaved messages in the console which could be hard to read or even misdirect you as you don't know the source of the line.
I have a diificult ClassDefNotFound problem (see here)
I run my unit tests through maven surefire plugin.
I would like to print out my unit test classpath at runtime. The following code only outputs one entry, namely the surefire jar. (I guess surefire has its own classloader and is using reflection.)
#Test
public void testGetClasspathTest()
{
ClassLoader cl = ClassLoader.getSystemClassLoader();
URL[] urls = ((URLClassLoader)cl).getURLs();
for(URL url: urls){
TestSS.getLogger().debug(url.getFile());
}
}
Can someone suggest a way to get the full runtime classpath from within a junit test?
the answer is simple:
mvn -e -X install
This provides full debug output including test runtime classpath
I have an Eclipse RCP Project with multiple plugins. I am writing plain JUnit tests (no dependencies to Eclipse/UI) as separate fragments to the plugin-under-test.
When using Mockito and trying to mock an interface from another plugin (which is exported correctly; I can use the interface in my code), I get a SecurityException related to class signing:
org.mockito.exceptions.base.MockitoException:
Mockito cannot mock this class: interface ch.sbb.polar.client.communication.inf.service.IUserService
Mockito can only mock visible & non-final classes.
If you're not sure why you're getting this error, please report to the mailing list.
at org.mockito.internal.runners.JUnit45AndHigherRunnerImpl$1.withBefores(JUnit45AndHigherRunnerImpl.java:27)
[...]
Caused by: org.mockito.cglib.core.CodeGenerationException: java.lang.reflect.InvocationTargetException-->null
at org.mockito.cglib.core.AbstractClassGenerator.create(AbstractClassGenerator.java:238)
[...]
Caused by: java.lang.reflect.InvocationTargetException
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
[...]
Caused by: java.lang.SecurityException: Signers of 'ch.sbb.polar.client.communication.inf.service.IUserService$$EnhancerByMockitoWithCGLIB$$a8bfe723' do not match signers of other classes in package
at java.lang.ClassLoader.checkPackageSigners(ClassLoader.java:361)
at java.lang.ClassLoader.defineClass(ClassLoader.java:295)
... 40 more
When I run the tests as "JUnit Plugin tests", i.e. with an OSGi environment, everything works as expected. But I'd like to use the plain JUnit execution because of speed; in the class under test, I don't need the OSGi environment.
Does anybody know a way to do that?
As is mentioned in the comments, the root cause is that the Eclipse Orbit package of Mockito (which I had added to my target platform) is signed, and because of a bug in the underlying CGLIB, you cannot mock unsigned classes/interfaces with a signed Mockito.
See https://code.google.com/p/mockito/issues/detail?id=393 for the most detailed description. The bug is fixed in CGLIB head, but has not yet appeared in a release. Mockito only uses released versions as dependencies, so the fix is not yet in Mockito, with an unknown (to me) timeline, as when this will be in.
Workaround: Provide unsigned Mockito in separate bundle
The workaround is to package the Mockito JAR (and its dependencies) in its own bundle and export the necessary API packages.
When using Maven Tycho, JUnit, Hamcrest, and Mockito, the only way I was able to make this work and resolve all dependency / classpath / classloader issues correctly was the following way:
Create Maven module with the following entries in the pom.xml:
<packaging>eclipse-plugin</packaging>
[...]
<dependencies>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
<version>1.10.19</version>
</dependency>
</dependencies>
[...]
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-dependency-plugin</artifactId>
<executions>
<execution>
<id>copy-test-libs</id>
<goals>
<goal>copy-dependencies</goal>
</goals>
<configuration>
<outputDirectory>lib</outputDirectory>
<stripVersion>true</stripVersion>
<includeScope>runtime</includeScope>
</configuration>
</execution>
</executions>
</plugin>
Use following entries in the MANIFEST.MF:
Bundle-ClassPath: lib/mockito-core.jar,
lib/objenesis.jar
Export-Package: org.mockito,
org.mockito.runners
Require-Bundle: org.junit;bundle-version="4.11.0";visibility:=reexport,
org.hamcrest.library;bundle-version="1.3.0";visibility:=reexport,
org.hamcrest.core;bundle-version="1.3.0";visibility:=reexport
And finally in your unit test fragment, add this new bundle as a dependency.
I ran into this same issue and was able to resolve it by using a more recent Orbit repository which pulls Mockito 2.x:
http://download.eclipse.org/tools/orbit/downloads/drops/R20181128170323/?d
This repository contains Mockito 2.23.0 which uses Byte Buddy instead of CGLIB.
In my target, I simply pull mockito-core 2.23.0 and Byte Buddy Java Agent 1.9.0 from the Orbit repository above.
<unit id="org.mockito" version="2.23.0.v20181106-1534"/>
<unit id="org.mockito.source" version="2.23.0.v20181106-1534"/>
<unit id="net.bytebuddy.byte-buddy-agent" version="1.9.0.v20181106-1534"/>
<unit id="net.bytebuddy.byte-buddy-agent.source" version="1.9.0.v20181106-1534"/>
I want to run only a subset of my unit tests, the ones defined by a specific #Category.
So I read several SO questions, such as this one (which is exactly what I am looking for), and also this one.
The solution of my problem seems to be provided by the ClasspathSuite project. So I started to write the NewTest and OldTest interfaces that will define my test categories. Then, I created the AllTests suite:
#RunWith(ClasspathSuite.class)
public class AllTests { }
After that, I created a AllNewTests suite:
#RunWith(Categories.class)
#IncludeCategory(NewTest.class)
#SuiteClasses( { AllTests.class })
public class AllNewTests { }
Finally, I create two JUnit classes, one per category:
#Category(NewTest.class)
public class SomeNewTests {
// some tests...
}
#Category(OldTest.class)
public class SomeOldTests {
// some tests...
}
Now, if I run AllTests, Eclipse launches all the tests of my project, while Maven fails as no test are found:
mvn test -Dtest=AllTests
...
-------------------------------------------------------
T E S T S
-------------------------------------------------------
Running my.company.AllTests
Tests run: 0, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.093 sec
There are no tests to run.
If I run AllNewTests (which is the correct thing to do, right?), in Eclipse everything is fine (i.e. it only run the tests annoted with #Category(NewTest.class)) but Maven returns an error:
mvn test -Dtest=AllNewTests
...
-------------------------------------------------------
T E S T S
-------------------------------------------------------
Running my.company.AllNewTests
Tests run: 1, Failures: 0, Errors: 1, Skipped: 0, Time elapsed: 0.125 sec <<< FAILURE!
Results :
Tests in error:
initializationError(my.company.AllNewTests)
Tests run: 1, Failures: 0, Errors: 1, Skipped: 0
[INFO] ------------------------------------------------------------------------
[INFO] BUILD FAILURE
[INFO] ------------------------------------------------------------------------
The exception thrown is the following:
org.junit.runner.manipulation.NoTestsRemainException
at org.junit.runners.ParentRunner.filter(ParentRunner.java:256)
at org.junit.experimental.categories.Categories.<init>(Categories.java:142)
at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:39)
at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:27)
at java.lang.reflect.Constructor.newInstance(Constructor.java:513)
at org.junit.internal.builders.AnnotatedBuilder.buildRunner(AnnotatedBuilder.java:35)
at org.junit.internal.builders.AnnotatedBuilder.runnerForClass(AnnotatedBuilder.java:24)
at org.junit.runners.model.RunnerBuilder.safeRunnerForClass(RunnerBuilder.java:57)
at org.junit.internal.builders.AllDefaultPossibilitiesBuilder.runnerForClass(AllDefaultPossibilitiesBuilder.java:29)
at org.junit.runners.model.RunnerBuilder.safeRunnerForClass(RunnerBuilder.java:57)
at org.junit.internal.requests.ClassRequest.getRunner(ClassRequest.java:24)
at org.apache.maven.surefire.junit4.JUnit4TestSet.execute(JUnit4TestSet.java:33)
at org.apache.maven.surefire.junit4.JUnit4Provider.executeTestSet(JUnit4Provider.java:146)
at org.apache.maven.surefire.junit4.JUnit4Provider.invoke(JUnit4Provider.java:97)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
at java.lang.reflect.Method.invoke(Method.java:597)
at org.apache.maven.surefire.booter.ProviderFactory$ClassLoaderProxy.invoke(ProviderFactory.java:103)
at $Proxy0.invoke(Unknown Source)
at org.apache.maven.surefire.booter.SurefireStarter.invokeProvider(SurefireStarter.java:145)
at org.apache.maven.surefire.booter.SurefireStarter.runSuitesInProcess(SurefireStarter.java:70)
at org.apache.maven.surefire.booter.ForkedBooter.main(ForkedBooter.java:69)
My question is what I did wrong?
Technical details: Java 6, Maven 3.0.2, JUnit 4.8.1, Surefire plugin 2.7.1, cpsuite-1.2.5
As an update: as of Surefire plugin v2.11, JUnit 4.8+ style categories are now supported.
The release notes for Surefire v2.11 mention the new feature. The surefire:test goal can is configured using groups.
I have solved my problem by creating my own JUnit Runner, that extends the Suite.
The idea is close to the principle by the Classpath Suite project, i.e. looking for the classes existing in the classpath, and keep only the ones that are annotated with a given annotation (#NewTest for example).
If you are interested, you can read the full story on my blog.
After reading some blog posts and stackoverflow questions, I finally was able to make this work with the surefire plugin, as user1034382 answered. In my case with version 2.17 of maven-surefire-plugin.
Just to add my two cents, the more up-to-date explanation can be found here:
Using JUnit Categories to group tests
But you may run with the following surefire plugin problem:
[ERROR] java.lang.RuntimeException: Unable to load category:
That can be fixed with this other stackoverflow question/answer:
Where should I put interface class for Junit #Category?
My answer is just to gather all these info here and avoid googling/reading to many diferent solutions. Al least, this worked to me.