Gradle build interdependence between compilation and integration test
https://softwareengineering.stackexchange.com/questions/396120
-
01-03-2021 - |
Question
Scenario
After having written an integration test in in JUnit 5, that executes the compiled project.jar
, I used to manually compile the project after modifications, then manually execute the integration test. However, it is part of an click once installation, which automates the project compilation and execution using Gradle.
And to build the project.jar
with Gradle whilst evaluating JUnit integration test can be seen as a chicken and egg dilema; I compile the project so that I can run the integration test so that I can compile the project.
Solution
And gradle allows a build command to be run without testing the Unit-tests: gradle build -x tests
hence I can compile the project without tests, then compile the project again using: gradle build
which evaluates the integration test with the previously compiled version.
However, I feel like it may defeat the purpose of testing if you compile and run an untested compilation of project.jar
for an integration test, in order to compile a tested project.jar
Question
What is a better way/protocol to deal with the compilation-integration test interdependence?
Adressing Comments
The project structure consists of the Maven Standard Directory Layout supplemented with separate /src/integTest/java
and /src/integTest/resources
folders. The complete project directory tree consists of:
├───.gradle
│ ├───5.4
│ │ ├───executionHistory
│ │ ├───fileChanges
│ │ ├───fileContent
│ │ ├───fileHashes
│ │ ├───javaCompile
│ │ └───vcsMetadata-1
│ ├───5.5.1
│ │ ├───executionHistory
│ │ ├───fileChanges
│ │ ├───fileContent
│ │ ├───fileHashes
│ │ ├───javaCompile
│ │ └───vcsMetadata-1
│ ├───buildOutputCleanup
│ └───vcs-1
├───.settings
├───bin
│ ├───default
│ │ └───customsortserver
│ ├───main
│ │ └───customsortserver
│ └───test
│ └───customsortserver
├───build
│ ├───classes
│ │ └───java
│ │ └───main
│ ├───generated
│ │ └───sources
│ │ └───annotationProcessor
│ │ └───java
│ │ └───main
│ └───tmp
│ └───compileJava
├───gradle
│ └───wrapper
└───src
├───integTest
│ ├───java
│ │ └───customsortserver
│ └───resources
│ ├───originalData
│ ├───testDataSets
│ │ ├───backlog
│ │ └───pending
│ ├───testOutput
│ ├───wslCommandScripts
│ └───wslLaunchers
├───main
│ ├───java
│ │ └───customsortserver
│ └───resources
└───test
├───java
│ └───customsortserver
└───resources
Where the /src/integTest/resources/wslLaunchers
contain a powershell script that executes the compiled .jar
file half way in the integration test.
Solution
Gradle builds the jar file in the jar
task, and runs the integration tests in a task that you've defined (and I've assumed is called integTest
, but change as needed).
To make sure the jar file is available when integration tests run, we can add a task dependency. This will ensure that the jar
task always runs before the integTest
task. One possible syntax is:
integTest.dependsOn jar
Once we have the jar file, we need its location in order to use it. This is build/libs/[projectname].jar
relative to the Gradle project root. You could hard-code that into your Powershell script, or it might be more elegant (and less fragile) to e.g. pass a system property through from Gradle which can then be picked up in your integration test class.
Proof of concept code: https://github.com/StevenEddies/test-use-jar-test
To prove that it uses the jar from the same Gradle run, first run ./gradlew clean
, then double check no jar files exist in any subfolders, then run ./gradlew integTest
and see that the integration tests (which look for the class file in the jar) pass.
This is the example build.gradle
file for the proof of concept:
apply plugin: 'java'
apply plugin: 'eclipse'
group = 'uk.me.eddies'
repositories {
jcenter()
}
dependencies {
compile 'org.slf4j:slf4j-api:1.7.21'
runtime 'org.slf4j:slf4j-simple:1.7.21'
testCompile 'junit:junit:4.12'
testCompile 'org.hamcrest:hamcrest-all:1.3'
}
sourceSets {
integTest {
java {
compileClasspath += main.output
runtimeClasspath += main.output
}
}
}
configurations {
integTestCompile.extendsFrom testCompile
integTestRuntime.extendsFrom testRuntime
}
task integTest(type:Test) {
group = 'verification'
testClassesDirs = project.sourceSets.integTest.output.classesDirs
classpath = project.sourceSets.integTest.runtimeClasspath
check.dependsOn it
dependsOn jar
}