Niby wsio wspaniale działa, i można „żyć długo i pomyślnie”, ale jak coś nie chce zafungować, a niby ładnie skonfigurowane. Wtedy tylko cię lekki szlak trafia jak ciągle widzisz „Cannot infer Groovy class path because no Groovy Jar was found on class path”…
A więc po krótce, po co i jak. Jeżeli używamy spring inicjalizatora – https://start.spring.io/, to jak generujemy Kotlinowy projekt i jako build wybieramy Gradle to generuje nam się build.gradle.kts
zamiast zwykłego build.gradle
. Ma on trochę inną strukturę oraz inną składnię. Dodatkowo chciałem wydzielić osobno testy integracyjne oraz unit testy, dlatego taka zabawa z własnym buildem.
Dobrą praktyką jest mieć osobno wydzielone unit testy, a osobno testy integracyjne, dlaczego:
- Unit test – z założenia ma być to test, który wykonuje się w izolacji od zewnętrznych systemów oraz innych komponentów aplikacji (np. baza danych, dysk, inne metody). Ma to być szybki test i powinien testować w izolacji jedną niezależną ścieżkę (logiczną, metody, biznesową). Zgodnie z piramidą testów „Unit testów” powinno być koło 70% wszystkich testów, i Unit test nie oznacza testowanie jednej metody. Dane do działania unit testa można mockować lub generować, nie mogą to być dane ładowane z zewnętrznych elementów systemu i np. w unit testach nie podnosimy context springa.
- Testy integracyjne (integration test) – według piramidy testów powinno być ich koło 20%. Testy integracyjne testują w założeniu większe obszary aplikacji, kilka modułów, kilka logicznych ścieżek.
W standardowej aplikacji typuControler -> Service -> Repository
test integracyjny może testować podaną ścieżkę, gdzie zapis danych nie będzie odbywał się do bazy produkcyjnej czy testowej, ale do bazy uruchomionej na czas testu, które po jego zakończeniu zostanie usunięta (np. baza typu H2 czy uruchomiona w kontenerze dockera). W testach integracyjnych możemy uruchamiać context springa czy emulować zewnętrzne serwisy http przy pomocy np. wiremocka czy używać innych narzędzi.
Dobra po omówieniu dlaczego warto rozdzielać typy testów jedna przydatna rzecz. Jeżeli chcemy w gradle wyświetlić pewne elementy to można użyć metody println, poniżej przykładowy task co na konsoli wyświetla zmienne gradle:
task print() {
doLast {
println 'configurations: ' + configurations
println 'sourceSets: ' + sourceSets.main
println 'sourceSets: ' + sourceSets.test
println 'sourceSets: ' + sourceSets.integration
}
}
A tutaj kompletny przykład działającego gradle.build gdzie są zdefiniowane podstawowe dependency, a osobno można uruchomić unit testy gradlew test
, testy integracyjne gradlew integration
, a nawet wydrukować zmienne gradle gradlew print
Cały przykładowy kod gradle.build:
plugins {
id 'java'
id 'groovy'
id 'application'
id 'org.springframework.boot' version '2.5.3'
id 'io.spring.dependency-management' version '1.0.11.RELEASE'
id 'org.jetbrains.kotlin.jvm' version '1.5.21'
id 'org.jetbrains.kotlin.plugin.spring' version '1.5.21'
}
group = "pl.net.shad.example"
version = "0.0.1-SNAPSHOT"
java.sourceCompatibility = JavaVersion.VERSION_11
repositories {
mavenCentral()
}
sourceSets {
integration {
java.srcDir project.file('src/integration/java')
resources.srcDir project.file('src/integration/resources')
resources.srcDir project.sourceSets.test.resources
resources.srcDir project.sourceSets.main.resources
project.plugins.withType(GroovyPlugin) {
groovy.srcDir project.file('src/integration/groovy')
}
compileClasspath = sourceSets.main.output +
configurations.compileClasspath +
configurations.testCompileClasspath +
configurations.testRuntimeClasspath
runtimeClasspath = output + compileClasspath
}
}
configurations {
integrationCompile.extendsFrom testCompile
integrationRuntime.extendsFrom testRuntime
}
dependencies {
//Kotlin
implementation "com.fasterxml.jackson.module:jackson-module-kotlin"
implementation "org.jetbrains.kotlin:kotlin-reflect"
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8"
// Spring
implementation "org.springframework.boot:spring-boot-starter-actuator"
implementation "org.springframework.boot:spring-boot-starter-web"
developmentOnly "org.springframework.boot:spring-boot-devtools"
// Unit tests
testImplementation group: 'org.codehaus.groovy', name: 'groovy', version: '3.0.8'
testImplementation group: 'org.codehaus.groovy', name: 'groovy-all', version: '3.0.8', ext: 'pom'
testImplementation group: 'org.spockframework', name: 'spock-core', version: '2.0-groovy-3.0'
//Integration tests
testImplementation group: 'org.spockframework', name: 'spock-spring', version: '2.0-groovy-3.0'
testImplementation group: 'org.springframework.boot', name: 'spring-boot-starter-test', version: '2.5.3'
testImplementation group: 'cglib', name: 'cglib-nodep', version: '3.3.0'
}
task integration(type: Test) {
description = 'Runs the integration tests.'
group = 'verification'
testClassesDirs = sourceSets.integration.output.classesDirs
classpath = sourceSets.integration.runtimeClasspath
outputs.upToDateWhen { false }
mustRunAfter test
useJUnitPlatform()
}
test {
useJUnitPlatform()
}
task print() {
doLast {
println 'configurations: ' + configurations
println 'sourceSets: ' + sourceSets.main
println 'sourceSets: ' + sourceSets.test
println 'sourceSets: ' + sourceSets.integration
println 'java: ' + java
println 'resources: ' + resources
println 'project: ' + project
println 'project.plugins: ' + project.plugins
println 'project.plugins.withType(GroovyPlugin): ' + project.plugins.withType(GroovyPlugin)
println 'dependencies: ' + dependencies
}
}
Możliwość komentowania jest wyłączona.