Most developers know how painful is accidental, unnecessary run of tests using the application context. It may come in the context of Jenkins CI jobs, accidental build runs or just developing.
How to minimize the probability of unintended runs of slow tests?
Separating things
If your tests run slowly you could separate long, complex and not frequently used ones from those which are fast to speed up your local deployment.
Usually unit tests are fast and even if you run them by mistake, you won’t feel uncomfortable. Integration, contract and acceptance tests can take several minutes if the project is huge. That’s why it makes sense to separate integration and unit tests.
Project structure
Non-modular java project structure in src
folder usually looks like this:
All unit and integration tests are in test
source. Our build.gradle
file looks like this:
plugins {
id 'org.springframework.boot' version '2.1.3.RELEASE'
id 'java'
}
apply plugin: 'groovy'
apply plugin: 'io.spring.dependency-management'
group = 'com.inspeerity'
version = '0.0.1-SNAPSHOT'
sourceCompatibility = '1.11'
repositories {
mavenCentral()
}
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-web'
testRuntime("org.codehaus.groovy:groovy-all:2.5.2")
testImplementation('org.springframework.boot:spring-boot-starter-test')
testImplementation("org.spockframework:spock-spring:1.2-groovy-2.5")
}
Adding new test source directory
After creating integration/groovy
directory in src
, you may notice that IDE doesn’t recognize it as test sources directory:
We need to add another test source and resource directory to Gradle project.
Let’s add the following lines to the build.gradle
file:
sourceSets {
integration {
groovy.srcDir "$projectDir/src/integration/groovy"
resources.srcDir "$projectDir/src/integration/resources"
compileClasspath += main.output + test.output
runtimeClasspath += main.output + test.output
}
}
After refreshing gradle project you’ll see that integration/groovy
is treated as source:
We need to add dependencies to our integration tests sources:
configurations {
integrationImplementation.extendsFrom testImplementation
integrationRuntime.extendsFrom testRuntime
}
Since now you can move all your integration tests to proper folder, and all of them will have all dependencies which are defined for test
task.
Don’t forget to change testImplementation
to integrationImplementation
for each dependency that is used only by integration tests.
Defining integration task
The last thing left is to define the integration test task, to work with sources defined in integration
section:
task integrationTest(type: Test) {
testClassesDirs = sourceSets.integration.output.classesDirs
classpath = sourceSets.integration.runtimeClasspath
}
check.dependsOn integrationTest
The dependsOn
line forces to run integrationTest
task while running check
task which is executed every build.
Conclusion
With this knowledge you can separately execute integration and unit tests using proper task name.
The next step you can do is to prepare separate Jenkins tasks that will:
- Execute unit tests after each push to avoid blocking Jenkins workers
- Run all tests before merge, to make sure everything works.
Would you like to talk with our experts about custom software solutions for your business?