I am trying to run my tests in parallel, and I have a use-case different from all others that I have been able to find.
My tests are laid out pretty straight-forward, something like the following:
src/test/java
+-features.areaA
| +-SomeStory.java
| +-AnotherStory.java
| ...
+-features.areaB
| +-DifferentStory.java
| +-OtherStory.java
| ...
...
The tests are written using serenity-bdd, which is a wrapper for selenium, and the test manager is junit4.
Each "area" represents some discreet area of the application under test. Tests within one area cannot run in parallel as they would clobber the data they are using. However, tests between different areas can certainly run in parallel as there are no collisions.
I tried to configure my maven-failsafe-plugin according to the documentation. Using parallel=suites
and any one of threadCount=4
, threadCountSuites=4
, or useUnlimitedThreads=true
, results in only one test being run at a time.
Is my understanding of "suites" wrong in the context of Failsafe plugin? Is it possible to parallelize tests so that entire packages are fed into VM threads one at a time, but classes within one package run sequentially?
Update:
maven-failsafe-plugin has no notion of "packages". The notion of "suites" is related to junit4 Suite.
To solve my problem above, I did the following:
In each "features.area" I created a "TestSuiteStub":
import org.junit.runner.RunWith;
import org.junit.runners.Suite;
/**
* This will be processed by groovy-maven-plugin.
*/
@RunWith(Suite.class)
@Suite.SuiteClasses({
/* add test Stories after this ANCHOR */
})
public class TestSuiteStub {
/* Suite holder */
}
I then used the groovy-maven-plugin with execute
goal and the following script:
// find all TestSuiteStub.java
new File("${project.build.testSourceDirectory}/features"). // exploits the "complication" in Maven interpolation and GStrings
traverse(type: groovy.io.FileType.FILES, nameFilter: ~/TestSuiteStub\.java/) { stub ->
println 'Using: ' + stub
// in the same dir as TestSuiteStub.java, find all *Story.java
def stories = new StringBuilder()
new File(stub.parent).
eachFileMatch(groovy.io.FileType.FILES, ~/.*Story\.java/) { story ->
stories.append story.name.replace('java', 'class')
stories.append ', ' // will leave a comma at end of list, but javac seems to be ok with that
}
println 'Found: ' + stories
// write out TestSuite.java
def suite = new File(stub.parent + '/TestSuite.java')
suite.delete()
println 'Writing: ' + suite
stub.eachLine() { line ->
if(line.contains('Stub'))
suite.append line.replace('TestSuiteStub', 'TestSuite') + System.getProperty('line.separator')
else
suite.append line + System.getProperty('line.separator')
if(line.contains('ANCHOR'))
suite.append stories + System.getProperty('line.separator')
}
}
This will process each of the TestSuiteStub and generate a TestSuite. So after running this (you can use mvn test-compile
to run this, without running your tests), I have something like:
src/test/java
+-features.areaA
| +-SomeStory.java
| +-AnotherStory.java
| ...
| +-TestSuiteStub.java
| +-TestSuite.java
+-features.areaB
| +-DifferentStory.java
| +-OtherStory.java
| ...
| +-TestSuiteStub.java
| +-TestSuite.java
...
where the first TestSuite will look something like:
import org.junit.runner.RunWith;
import org.junit.runners.Suite;
/**
* This class will be processed by groovy-maven-plugin.
*/
@RunWith(Suite.class)
@Suite.SuiteClasses({
/* add test Stories after this ANCHOR */
SomeStory.class, AnotherStory.class,
})
public class TestSuite {
/* Suite holder */
}
To make maven-failsafe-plugin pick this up, I configured it like this:
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-failsafe-plugin</artifactId>
<version>${failsafe.plugin.version}</version>
<configuration>
<includes>
<include>features.*.*Suite</include>
</includes>
<parallel>suites</parallel>
<threadCountSuites>4</threadCountSuites>
</configuration>
<executions>
<execution>
<goals>
<goal>integration-test</goal>
<goal>verify</goal>
</goals>
</execution>
</executions>
</plugin>
Lastly I also added *Suite.java
to my .gitignore
.