Continous testing with JUnit and Gradle

One of the features I like when working on a small library is continuous testing. I used to setup my IDE to re-run my tests whenever a file changes. This article describes how to setup this kind of testing with Gradle and JUnit.

Continuous tests

Gradle can turn any build into a continuous build. Just run any target you want with the --continuous flag:

1
gradle test --continuous

Whenever a test file changes, Gradle will re-compile the whole project and run all tests. With the current Gradle changes to incremental build and the enhancements to compilation avoidance, the compile steps are usually very fast. This switch is usually enough for a small project.

Re-running only changed tests

However the tests run-time can be long and running only changed tests would improve productivity. In that case, Gradle AFAIK doesn’t offer anything out of the box. But it’s quite easy to add this functionality using a custom incremental task.

First, let’s start by extending the Gradle’s Test task and adding with the incremental inputs argument:

1
2
3
4
5
6
7
class TestWatcher extends Test {
@TaskAction
void executeTests(IncrementalTaskInputs inputs) {
super.executeTests()
}
}

Well this doesn’t do anything, but run the tests. But it shows that it’s possible to override the original @TaskAction. The inputs argument now gives us the ability to distinguish between two states:

  1. Something before the test task changed, therefore the task can’t be run incrementally
  2. Only the test classes changed, and in that case it will give us the list of changed files.

It also gives us the files which have been removed, but we don’t care about them. So let’s enhance the task:

1
2
3
4
5
6
7
8
9
10
11
12
class TestWatcher extends Test {
@TaskAction
void executeTests(IncrementalTaskInputs inputs) {
if (inputs.incremental) {
inputs.outOfDate { InputFileDetails change ->
// setup only the changed files for testing
}
}
super.executeTests()
}
}

In case the build is incremental at this point, we get our list of changes. To set them up for testing, we can use the Gradle’s ability to run only tests that pass a filter. The filter takes a class name in package.class format, but the incremental outputs give us files. We’ll have to change the file names into class names and strip the absolute path:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
class TestWatcher extends Test {
@TaskAction
void executeTests(IncrementalTaskInputs inputs) {
if (inputs.incremental) {
def outputDir = this.project.sourceSets['test'].output.classesDir.absolutePath
this.filter.includePatterns = []
inputs.outOfDate { InputFileDetails change ->
def candidate = change.file.absolutePath
if (candidate.endsWith('.class')) {
candidate = candidate
.replace('.class', '')
.replace(outputDir, '')
.substring(1)
.replace(File.separator, '.')
this.filter.includePatterns += candidate
}
}
}
super.executeTests()
}
}

The first replace strips the file extension. The second then removes the path to the folder. The substring removes the first file separator and the last replace changes all file separators into dots. The classes then populate the set of filter patterns in filter.includePattern.

And that’s it. Add the task to the main gradle build file:

1
2
3
4
5
task testWatcher(type: TestWatcher) {
testLogging {
showStandardStreams = true
}
}

And run the build with:

1
gradle testWatcher --continuous

When you change any of the classes, the tests will be re-run. If you change only a single test class, only that class will be run.

See also