Francesco Komauli
Developer
docker pull metahelicase/gradle:2.10
git clone https://bitbucket.org/fkomauli/integration-testing
cd integration-testing
docker run --rm -v `pwd`:/project -u `id -u`:`id -g` metahelicase/gradle:2.10 check
git clone https://bitbucket.org/fkomauli/integration-testing
cd integration-testing
gradle check
Missing gradle and JDK?
Modern build environment compatible with maven, ant, ivy, ...
Defines flexible and extensible build logic
by organizing dependent tasks in a
directed acyclic graph
Source sets separate sources by categories (main, test), not by source type
Atomic unit of build logic
build.gradle
Plugin<Project>
Task
META-INF/gradle-plugins/
org.gradle.testkit.runner.*
spock.lang.*
a gradle plugin is just a gradle project
written in groovy
(for fast prototyping)
or any JVM language
using the gradle API
An example stub plugin
plugins {
id 'groovy'
}
group = 'it.unimi.di'
version = '1.0-SNAPSHOT'
repositories {
jcenter()
}
dependencies {
compile localGroovy()
compile gradleApi()
}
rootProject.name = 'stub'
build.gradle
settings.gradle
package stub
import org.gradle.api.Plugin
import org.gradle.api.Project
class StubPlugin implements Plugin<Project> {
void apply(Project project) {
project.task('stub', type: StubTask)
}
}
package stub
import org.gradle.api.DefaultTask
import org.gradle.api.tasks.TaskAction
class StubTask extends DefaultTask {
@TaskAction
void stub() {
logger.lifecycle("Running stub task")
}
}
Declare the plugin class in the jar metadata
META-INF/gradle-plugins/it.unimi.di.stub.properties
implementation-class=stub.StubPlugin
with plugin extensions
Parameters are specified as
fields or methods of a class
package stub
class StubPluginExtension {
int repeat = 1
String message = 'Running stub task'
}
void apply(Project project) {
project.extensions.create('stub', StubPluginExtension)
...
}
@TaskAction
void stub() {
def message = project.stub.message
(1..project.stub.repeat).each {
logger.lifecycle(message)
}
}
Add the extension class to the plugin definition
and use the parameters inside the tasks
Extensions are configured inside the build script
...
apply plugin: 'it.unimi.di.stub'
stub {
repeat 3
message 'Custom message'
}
import org.gradle.testkit.runner.*
dependencies {
...
testCompile 'junit:junit:4.12'
testCompile gradleTestKit()
}
Import the gradle test kit package by adding it as test dependency to the build script
GradleRunner is a builder for test projects
It executes a build in a parallel process, then it returns a BuildResult object
to query the results
BuildResult result = GradleRunner.create()
.withProjectDir(...)
.withPluginClasspath(...)
.withArguments("$task", "$arg1", "$arg2", ...)
.build() // or .buildAndFail()
A template for plugin testing
@Rule
public final TemporaryFolder project = new TemporaryFolder()
@Before
void setup() {
def buildScript = project.newFile 'build.gradle'
buildScript.text = ... // common build settings setup
}
BuildResult run(String task) {
return GradleRunner.create()
.withProjectDir(project.root)
.withPluginClasspath(classpath())
.withArguments(task, '--stacktrace')
.build()
}
The test project's classpath should be
the runtime classpath of the plugin itself
List<File> classpath() {
def pluginTestClasspath = System.getProperty('plugin.test.classpath')
return pluginTestClasspath.readLines().collect { new File(it) }
}
test {
systemProperty 'plugin.test.classpath',
sourceSets.main.runtimeClasspath.join('\n')
}
Example tests from
https://bitbucket.org/fkomauli/integration-testing
static final String TASK = 'integration'
@Test
void 'unit tests are run before integration tests'() {
createPassingTestClassUnder 'test'
createPassingTestClassUnder 'integration'
BuildResult build = run TASK
assertNotNull(build.task(':test'));
}
@Test
void 'integration tests are not run if unit tests fail'() {
createFailingTestClassUnder 'test'
createPassingTestClassUnder 'integration'
BuildResult build = runFailing TASK
assumeTrue(FAILED == build.task(':test').outcome);
assertNull(build.task(":$TASK"))
}
Groovy is a language with a lighter syntax than Java and a richer library support for common operations with I/O
It is easy to design a custom DSL,
that can improve tests readability
Test builds are run in separate processes
Therefore there's no println debugging :P
Just joking, use .forwardOutput() on GradleRunner
with moderation
import spock.lang.*
void 'testing my feature'() {
given:
// the environment
when:
// performing an action
then:
// assertions
}
void 'testing a functional method'() {
expect:
// assertions
}
def 'max yields the greatest number out of two'() {
expect:
max(a, b) == c
where:
a << [5, 3, 7]
b << [1, 9, 7]
c << [5, 9, 7]
}
@Unroll
def 'max(#a, #b) yields #c'() {
expect:
max(a, b) == c
where:
a << [5, 3, 7]
b << [1, 9, 7]
c << [5, 9, 7]
}
Tests
max(3, 9) yields 9 0s passed
max(5, 1) yields 5 0.004s passed
max(7, 7) yields 7 0s passed
def 'subscriber should receive the message sent by the publisher'() {
given:
def publisher = new Publisher()
def subscriber = Mock(Subscriber)
publisher << subscriber
def message = ...
when:
publisher.send(message)
then:
1 * subscriber.receive(message)
}
By Francesco Komauli
Introduction to gradle plugin development