The discussion of testing with Gradle takes two primary directions. The first is the simple testing of Java classes with existing test frameworks like JUnit and TestNG. The second is a full automation of the testing pipeline, including separating integration tests from unit test and the leveraging of more advanced testing frameworks like Spock and Geb.
The simplest JUnit example is almost
entirely supplied by the java
Gradle
plug-in. It adds the test
task to the
build graph and needs only the appropriate JUnit JAR to be added to the
classpath to fully activate test execution. This is demonstrated in
Example 5-1.
apply
plugin:
'
java
'
repositories
{
mavenCentral
()
}
dependencies
{
testCompile
'
junit:junit:
4.8
.
2
'
}
The report from the execution of the JUnit tests is quite handsome compared to its non-Gradle counterparts, as you can see in Figure 5-1. It offers a summary of the tests that were executed, succeeded, and failed.
When JUnit tests reach a certain level of proliferation within a
project, there is a motivation to run them in parallel to get the
results faster. However, there would be a great overhead to running
every unit test in its own JVM. Gradle provides an
intelligent compromise in that it offers a maxParallelForks
that governs the maximum
simultaneous JVMs that are spawned.
In the same area of testing, but with a different motivation, is
the forkEvery
setting. Tests, in
their quest to touch everything and exercise as much as possible, can
cause unnatural pressure on the JVM’s memory allocation. In short, it is
what Java developers term a “leak”. It can merely be the loading of
every class causing the problem. This isn’t really a leak since the
problem stems from the fact that loaded class definitions are not
garbage collected but instead are loaded into permgen space. The
forkEvery
setting causes a
test-running JVM to close and be replaced by a brand new one after the
specified number of tests have run under an instance.
Though these two settings have very different goals, they may
often be seen used in combination when a project has a large battery of
tests. The use of forkEvery
and
maxParallelForks
is shown in Example 5-2.
apply
plugin:
'
java
'
repositories
{
mavenCentral
()
}
dependencies
{
testCompile
'
junit:junit:
4.8
.
2
'
}
test
{
maxParallelForks
=
5
forkEvery
=
50
}
In Example 5-2, we leverage the test
closure that was provided by the java
plug-in as a scoping syntax for
assignment of new values to the forkEvery
and maxParallelForks
variables.
Example 5-3 executes a Gradle build that uses the two governing parallelism settings as set in Example 5-2.
$ gradle test :createTests :compileJava UP-TO-DATE :processResources UP-TO-DATE :classes UP-TO-DATE :compileTestJava :processTestResources UP-TO-DATE :testClasses > Building > :test > 760 tests completed
When the Example 5-2 tests should spin up
multiple JVMs for unit tests, we can validate this behavior by running
the jps
command as shown in Example 5-4.
$ jps 90861 GradleWorkerMain 90862 GradleWorkerMain 90863 GradleWorkerMain 90865 GradleWorkerMain 90864 GradleWorkerMain 90731 GradleMain
We can see that five GradleWorkerMain
instances are indeed busy,
running tests as per our Example 5-2
specifications.
A more recent testing framework, TestNG, has been gaining
traction in the Java space as of late. It is also an easy testing
framework to use with Gradle. Only the modest addition of the useTestNG() call to the
test
closure is needed in addition to
the refactoring of the unit test
class.
package
org
.
gradle
.
example
.
simple
;
import
org.gradle.example.simple.Person
;
import
org.testng.annotations.*
;
import
static
org
.
testng
.
AssertJUnit
.*;
public
class
TestPerson
{
private
Person
p
=
null
;
@BeforeClass
public
void
setUp
()
{
p
=
new
Person
();
p
.
setAge
(
20
);
p
.
setName
(
"Fird Birfle"
);
p
.
setSalary
(
195750.22
);
}
@Test
(
groups
=
{
"fast"
})
public
void
testPerson
()
{
assertEquals
(
215325.242
,
p
.
calculateBonus
(),
0.01
);
assertEquals
(
"The Honorable Fird Birfle"
,
p
.
becomeJudge
());
assertEquals
(
30
,
p
.
timeWarp
());
}
@Test
(
groups
=
{
"slow"
})
public
void
testPersonAgain
()
{
Person
p
=
new
Person
();
p
.
setAge
(
30
);
p
.
setName
(
"Bird Firfle"
);
p
.
setSalary
(
32001.99
);
p
.
wasteTime
();
}
}
apply
plugin:
'
java
'
repositories
{
mavenCentral
()
}
test
{
useTestNG
()
}
dependencies
{
testCompile
'
org
.
testng
:
testng:
6.0
.
1
'
}
Lastly, when tests start to bifurcate into both unit and integration tests, which among many differentiators, typically means short and long running respectively, a need to also have the tool respect those separations arises. Gradle handles this with aplomb with a simple specification of a pattern of tests to include in each grouping as shown in Example 5-7.
apply
plugin:
'
java
'
repositories
{
mavenCentral
()
}
dependencies
{
testCompile
'
junit:junit:
4.8
.
2
'
}
test
{
include
'
**/
Test
*.*
'
}
task
integrationTest
(
type:
Test
,
dependsOn:
"test"
)
<<
{
include
'
**/
IntegrationTest
*.*
'
}
This same goal of separating unit and integration tests could also
be accomplished with a different technique of separate sourceSet
entries. The sourceSet and configuration distinction (one for unitTest
and one for integrationTest
) has JAR dependency precision,
but also more verbosity. This is similar to the technique employed by
the Gradle plug-in
in that it has a distinct set of files
(src/main/groovy) and dependencies (like
the Groovy core library).
Spock, as the homepage states, is a “testing and specification framework for Java and Groovy applications.” In a sea of similar tools, it distinguishes itself from the others by a very expressive DSL that reads nearly like natural English writing.
class
HelloSpock
extends
spock
.
lang
.
Specification
{
def
"length of Spock's and his friends' names"
()
{
expect:
name
.
size
()
==
length
where:
name
|
length
"Spock"
|
5
"Kirk"
|
4
"Scotty"
|
6
}
}
A Spock specification, since run under the umbrella of JUnit, can be executed by the mere addition of Spock’s JAR to Gradle’s dependencies.
apply
plugin:
"groovy"
repositories
{
mavenCentral
()
}
dependencies
{
groovy
(
"org.codehaus.groovy:groovy-all:1.7.5"
)
testCompile
"org.spockframework:spock-core:0.5-groovy-1.7"
// dependencies used by examples in this project
// (not required for using Spock)
testRuntime
"com.h2database:h2:1.2.147"
}
The standard results for JUnit executions also apply to Spock, providing a consistent summary of both traditional and specification-style test execution results, as shown in Figure 5-2.
Geb can be thought of as the furthest reaches of test automation on the JVM with Groovy-based control of web browsers for testing what is primarily thought of as a task for Selenium/WebDriver. There are a growing number of developers writing about and demonstrating this powerful combination of driving a browser from JUnit all the way to specification formats like easyB.
Gradle can easily drive a Geb and easyB combination with just a few additional dependencies and an Ant task defintion, as shown in Example 5-10.
apply
plugin:
'
groovy
'
repositories
{
mavenCentral
()
}
dependencies
{
// groovy 'org.codehaus.groovy:groovy:1.7.10'
testCompile
(
'
org
.
easyb
:
easyb:
0.9
.
8
'
)
testCompile
'
org
.
codehaus
.
geb
:
geb
-
core:
0.5
.
1
'
testCompile
'
org
.
codehaus
.
geb
:
geb
-
easyb:
0.5
.
1
'
testCompile
'
org
.
seleniumhq
.
selenium
:
selenium
-
htmlunit
-
driver:
2.0
a7
'
}
test
.
doLast
{
ant
.
taskdef
(
name:
"easyb"
,
classname:
"org.easyb.ant.BehaviorRunnerTask"
,
classpath:
sourceSets
.
test
.
runtimeClasspath
.
asPath
)
ant
.
easyb
(
classpath:
sourceSets
.
test
.
runtimeClasspath
.
asPath
,
failureProperty:
'
easyb_failed
'
)
{
report
(
location:
"${project.testResultsDir}/story.html"
,
format:
"html"
)
behaviors
(
dir:
"src/test/stories"
)
{
include
(
name:
"**/*.story"
)
}
}
ant
.
fail
(
if
:
'
easyb_failed
'
,
message:
'
Failures
in
easyb
stories
'
)
}
The easyB specification file reads like plain English in its execution of web browser driving events and querying of the page responses.
using
"geb"
scenario
"scripting style"
,
{
when
"we go to google"
,
{
go
"http://google.com"
}
then
"we are at google"
,
{
page
.
title
.
shouldBe
"Google"
}
when
"we search for chuck"
,
{
$
(
"input"
,
name:
"q"
).
value
(
"chuck norris"
)
$
(
"input"
,
value:
"Google Search"
).
click
()
}
then
"we are now at the results page"
,
{
page
.
title
.
shouldEndWith
"Google Search"
}
and
"we get straight up norris"
,
{
$
(
"li.g"
,
0
).
find
(
"a.l"
).
text
().
shouldStartWith
"Chuck Norris"
}
}
And, at the end of the Geb via easyB execution, a very pleasant results page (Figure 5-3) describes the stories that were run, like a richer version of the JUnit test report.
The review and examples of testing with Gradle shows that the traditional JUnit testing approach is slightly less ceremonious than with other build tools. However, Gradle truly shines in that the more advanced forms of separating integration tests, running Spock specifications, or driving the browser via Geb are handled with absolutely simplicity. As more complex software, larger code bases, and increased automation drive developers toward these more advanced testing tools, Gradle is poised to offer the most convenient use. This will continue to drive Gradle’s adoption as a software craftsperson’s build and automation tool.