* Update February 13th, 2012: Thanks to Ben Ripkens for updates to match the new Gradle API.
While searching online, I found many suggestions for how to add a new test target to a Gradle script. Most of them were wrong and others didn’t properly separate the integration test target from standard targets. After not finding a solution, I came up with one on my own.
This example sets up integration tests for Groovy .
Create a source set
This will separate the integration test code from other code, allowing it to be built separately.
- The classpath in the example gives integration tests access to all application and test classes
- The source location will be src/integrationTest/groovy
sourceSets {
integrationTest {
compileClasspath = sourceSets.main.output + configurations.testRuntime
runtimeClasspath = output + sourceSets.main.output + configurations.testRuntime
groovy {
srcDir 'src/integrationTest/groovy'
}
}
}
Add the target
task integrationTest(type: Test) {
testClassesDir = sourceSets.integrationTest.output.classesDir
classpath = sourceSets.integrationTest.runtimeClasspath
}
Occasionally, I see issues pop up on Java projects about builds not working properly. They usually look like, “The build worked yesterday, but it doesn’t work today. I was working on it, but I didn’t change anything related to the part that is failing.” or “I can’t figure out why, when I run this by itself, it passes. When I run it as a part of a full build, it fails.” Build errors are almost always caused by a misunderstanding of the function of “depends”.
How Not to Use Depends
This probably works, but only by chance.
<target name="test" depends="clean,compile,compile-test,test,service-test" />
Depends is not a list of tasks to be executed. It’s a list of dependencies that must be satisfied before a target can be completed. Execution is not guaranteed and side effects are common. Take a close look at what each of the tasks in the following script is doing.
<target name="functional-test" depends="start-server,run-tests,stop-server" />
<target name="service-test" depends="start-server,run-service-tests,stop-server" />
<target name="test" depends="clean,compile,compile-test,functional-test,service-test" />
To a human, the intent is obvious.
- Clean and compile
- Start server
- Run functional tests
- Stop server
- Start server
- Run service tests
- Stop server
But Ant will not execute those tasks. Since they were declared as dependencies, Ant evaluates them as such. The functional tests will run as you would expect. When it runs the service-test target, though, it will see the start and stop server tasks again. As far as it’s concerned, those dependencies have already been satisfied, so it will skip start-server and stop-server. Ant is evaluating the script correctly, but the script is wrong.
Dependencies are not tasks to be executed. They are dependencies. Once a dependency has been satisfied, it will not be executed again.
Antcall Is Not Evil
It exists for a reason. Use it when it is useful. Dependencies are dependencies, not task lists. In this case, it would be impractical to try to write the script to use depends attributes to cause events to happen correctly. It can be fixed very easily by using antcall. Each antcall is evaluated separately, so you don’t have to worry about side effects like the script above. It’s also easier to read. You can see what’s intended to happen before this target and you can see what this target is intended to do.
<target name="functional-test" depends="start-server,run-tests,stop-server" />
<target name="service-test" depends="start-server,run-service-tests,stop-server" />
<target name="test" depends="clean,compile,compile-test">
<antcall target="functional-test" />
<antcall target="service-test" />
</target>
Use Your Own Judgement
Obviously, antcall should not be used instead of depends. Depends is far more useful. But don’t throw a tool like antcall away just because it doesn’t seem as cool or concise or elegant.
While working on a project that uses Hibernate, my team was getting a little frustrated while trying to interrogate a tree structure in our data model since all collections are mapped as java.util.HashSets. We wanted a simple function that could print the tree to the log, so I took a free hour and wrote this. It was a little more complicated than I anticipated because it had to handle situations like these.
root
|
|- child
|
|- child
root
|
|- child
|
|- child
and the infamous (to me) …
root
|
|- child
| |
| |- child
| |
| |- child
|
|- child
There’s a subtle difference. Notice that in the middle one, the line for the first child stops because there aren’t any more children, but in the last one, the line continues? I could have created some kind of 2D text buffer and gone back to draw the line, but that would be boring. Here’s what I came up with. I don’t know whether it’s pretty, but it works!
/**
* Creates a tree representation of the node
* @param node The node, which may not be null
* @return A string containing the formatted tree
*/
public static String toStringTree(Node node) {
final StringBuilder buffer = new StringBuilder();
return toStringTreeHelper(node, buffer, new LinkedList<Iterator<Node>>()).toString();
}
private static void toStringTreeDrawLines(List<Iterator<Node>> parentIterators, boolean amLast) {
StringBuilder result = new StringBuilder();
Iterator<Iterator<Node>> it = parentIterators.iterator();
while (it.hasNext()) {
Iterator<Node> anIt = it.next();
if (anIt.hasNext() || (!it.hasNext() && amLast)) {
result.append(" |");
}
else {
result.append(" ");
}
}
return result.toString();
}
private static StringBuilder toStringTreeHelper(Node node, StringBuilder buffer, List<Iterator<Node>>
parentIterators) {
if (!parentIterators.isEmpty()) {
boolean amLast = !parentIterators.get(parentIterators.size() - 1).hasNext();
buffer.append("\n");
String lines = toStringTreeDrawLines(parentIterators, amLast);
buffer.append(lines);
buffer.append("\n");
buffer.append(lines);
buffer.append("- ");
}
buffer.append(node.toString());
if (node.hasChildren()) {
Iterator<Node> it = node.getChildNodes().iterator();
parentIterators.add(it);
while (it.hasNext()) {
Node child = it.next();
toStringTreeHelper(child, buffer, parentIterators);
}
parentIterators.remove(it);
}
return buffer;
}