Build tools, Unit tests, and Code coverage
The base application I generated from the Spring Boot Initializer was a Maven project. I had the option of Maven or Gradle, and I made a quick choice based on my knowledge at the time. You see, I’m a PHP developer. I’m used to there being one dominant package manager, Composer, and not having to make choices about which one to use. My experience with Gradle and Maven has been that Gradle is for Android projects and Maven for Server-side things. It’s not actually that clear cut, it’s just that Google has chosen Gradle to be the official build tool for Android, and maven is the original package manager so older projects run on maven.
I did experience a little bit of this package manager duality when developing JavaScript applications using either the Node Package Manager, or the stand-in replacement Yarn, developed by Facebook’s React team because npm was too slow, but yarn used the same package.json file as npm, so switching between the two only meant losing your package lock file.
Yarn’s biggest claim to fame is speed, and it seems that is what Gradle brings to the table also. Again, as a PHP developer I’m used to my code being interpreted at runtime, rather than being compiled with every code change, and it hadn’t even entered my mind that build speed would be a factor, but golly was I bored watching my application build over and over again to fix simple mistakes I’d made in my code.
The gradle website presents a very convincing demonstration of how much faster Gradle builds are, and that it actively avoids doing unnecessary work. It sings the praises of it’s “work avoidance mechanism”, and shouldn’t we all? I was sold on the promise of faster builds, and the result didn’t disappoint. Have a look here https://gradle.org/maven-vs-gradle/ if you’re interested in more reliable information.
Converting
I mentioned earlier the conversion from npm to yarn for an existing project was as simple as running `yarn` instead of `npm i`, but it’s not like that for Gradle, it’s a totally different structure. Maven has it’s pom.xml file that looks a bit like this
And Gradle has a syntax that reminds me a bit of json and a bit of yaml. Apparently it’s a superset of Java and it’s called Groovy.
plugins { id 'application' id 'groovy-base' } dependencies { implementation project(':utilities') testImplementation 'org.codehaus.groovy:groovy-all:2.5.11' testImplementation 'org.spockframework:spock-core:1.3-groovy-2.5' } application { mainClass = 'org.gradle.sample.app.Main' }
The thing is that now with the introduction of Kotlin, everyone’s moving from Groovy DSL to Kotlin DSL which uses Kotlin syntax. I guess this is better, but what it means is that documentation is completely fragmented between maven XML, and the two different Gradle formats, and if you ever ask someone for help there’s a 66% chance they’ll tell you in some other syntax and you’ll have to covert it.
plugins { application `groovy-base` } dependencies { implementation(project(":utilities")) testImplementation("org.codehaus.groovy:groovy-all:2.5.11") testImplementation("org.spockframework:spock-core:1.3-groovy-2.5") } application { mainClass.set("org.gradle.sample.app.Main") }
Apparently if you just run gradle in a maven repo, Gradle will do the coversion for you. Unforunately this creates a Groovey DSL, and all the kotlin docs give you instructions in Kotlin DSL, plus we need different plugins for Gradle, so it was easier in the end to re-generate the base project from the Spring Boot Initializer and copy in the new files. I ended up with this commit and this DSL
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile plugins { id("org.springframework.boot") version "2.3.3.RELEASE" id("io.spring.dependency-management") version "1.0.10.RELEASE" kotlin("jvm") version "1.3.72" kotlin("plugin.spring") version "1.3.72" kotlin("plugin.jpa") version "1.3.72" } group = "com.chris-young" version = "0.0.1-SNAPSHOT" java.sourceCompatibility = JavaVersion.VERSION_11 repositories { mavenCentral() } dependencies { implementation("org.springframework.boot:spring-boot-starter-data-jpa") implementation("org.springframework.boot:spring-boot-starter-mustache") implementation("org.springframework.boot:spring-boot-starter-web") implementation("com.fasterxml.jackson.module:jackson-module-kotlin") implementation("org.jetbrains.kotlin:kotlin-reflect") implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8") developmentOnly("org.springframework.boot:spring-boot-devtools") runtimeOnly("com.h2database:h2") testImplementation("org.springframework.boot:spring-boot-starter-test") { exclude(group = "org.junit.vintage", module = "junit-vintage-engine") } } tasks.withType { useJUnitPlatform() } tasks.withType { kotlinOptions { freeCompilerArgs = listOf("-Xjsr305=strict") jvmTarget = "11" } }
Code coverage
For code coverage I want to use a service called coveralls. I have chosen a Gradle plugin that will upload reports to that service. To generate those reports we’ll use a Gradle plugin that produces coverage reports from unit tests called Jacoco.
To add the coverage report generator, we simply need to add the plugin here
plugins { id("org.springframework.boot") version "2.3.3.RELEASE" id("io.spring.dependency-management") version "1.0.10.RELEASE" id("java") id("jacoco") ... }
The jacoco test report comes out looking like this.
This HTML report is very handy for local development – Next we need to enable XML report generation for export into coveralls.
tasks { jacocoTestReport { reports { xml.isEnabled = true } } }
And then we’ll add the library to export to coveralls. Because this is a public repository, there’s no configuration required.
plugins { ... id("jacoco") id("com.github.kt3k.coveralls") version "2.10.2" ... }
Lastly we’ll add a travis file (.travis.yml) to enable CI builds.
language: java jdk: - oraclejdk11 cache: directories: - $HOME/.gradle/caches/ - $HOME/.gradle/wrapper/ install: - ./gradlew assemble script: - ./gradlew test build after_script: - ./gradlew jacocoTestReport coveralls
Theoretically all that’s needed is language: java and travis will automatically detect the build.gradle file and use gradle to build and test the repo, but since we want to use a newer version of Java and Gradle than the ones that ship with travis, and we want to do some specific step at the end, it’s better if we are a bit more specific about all the build steps.
Now when we push to GitHub, Travis CI will run the build script and upload the test reports to Coveralls.
resolvconf install_jdk Installing oraclejdk11 0.00s git.checkout 0.44s$ git clone --depth=50 --branch=main https://github.com/darkbluesun/auth-demo-auth.git darkbluesun/auth-demo-auth 0.01s$ export TERM=dumb cache.1 Setting up build cache $ java -Xmx32m -version openjdk version "11.0.2" 2019-01-15 OpenJDK Runtime Environment 18.9 (build 11.0.2+9) OpenJDK 64-Bit Server VM 18.9 (build 11.0.2+9, mixed mode) $ javac -J-Xmx32m -version javac 11.0.2 install 21.04s$ ./gradlew assemble 25.22s$ ./gradlew test build > Task :compileKotlin UP-TO-DATE > Task :compileJava NO-SOURCE > Task :processResources UP-TO-DATE > Task :classes UP-TO-DATE > Task :compileTestKotlin > Task :compileTestJava NO-SOURCE > Task :processTestResources NO-SOURCE > Task :testClasses UP-TO-DATE > Task :test 2020-09-13 02:10:57.345 INFO 4737 --- [extShutdownHook] j.LocalContainerEntityManagerFactoryBean : Closing JPA EntityManagerFactory for persistence unit 'default' 2020-09-13 02:10:57.345 INFO 4737 --- [extShutdownHook] .SchemaDropperImpl$DelayedDropActionImpl : HHH000477: Starting delayed evictData of schema as part of SessionFactory shut-down' 2020-09-13 02:10:57.356 INFO 4737 --- [extShutdownHook] j.LocalContainerEntityManagerFactoryBean : Closing JPA EntityManagerFactory for persistence unit 'default' 2020-09-13 02:10:57.358 INFO 4737 --- [extShutdownHook] .SchemaDropperImpl$DelayedDropActionImpl : HHH000477: Starting delayed evictData of schema as part of SessionFactory shut-down' Hibernate: drop table if exists user CASCADE Hibernate: drop sequence if exists hibernate_sequence 2020-09-13 02:10:57.587 INFO 4737 --- [extShutdownHook] o.s.s.concurrent.ThreadPoolTaskExecutor : Shutting down ExecutorService 'applicationTaskExecutor' 2020-09-13 02:10:57.592 INFO 4737 --- [extShutdownHook] com.zaxxer.hikari.HikariDataSource : HikariPool-1 - Shutdown initiated... 2020-09-13 02:10:57.623 INFO 4737 --- [extShutdownHook] j.LocalContainerEntityManagerFactoryBean : Closing JPA EntityManagerFactory for persistence unit 'default' 2020-09-13 02:10:57.625 INFO 4737 --- [extShutdownHook] .SchemaDropperImpl$DelayedDropActionImpl : HHH000477: Starting delayed evictData of schema as part of SessionFactory shut-down' 2020-09-13 02:10:57.635 INFO 4737 --- [extShutdownHook] com.zaxxer.hikari.HikariDataSource : HikariPool-1 - Shutdown completed. 2020-09-13 02:10:57.638 INFO 4737 --- [extShutdownHook] o.s.s.concurrent.ThreadPoolTaskExecutor : Shutting down ExecutorService 'applicationTaskExecutor' 2020-09-13 02:10:57.639 INFO 4737 --- [extShutdownHook] com.zaxxer.hikari.HikariDataSource : HikariPool-2 - Shutdown initiated... 2020-09-13 02:10:57.645 INFO 4737 --- [extShutdownHook] com.zaxxer.hikari.HikariDataSource : HikariPool-2 - Shutdown completed. > Task :bootJar UP-TO-DATE > Task :inspectClassesForKotlinIC UP-TO-DATE > Task :jar SKIPPED > Task :assemble UP-TO-DATE > Task :check > Task :build BUILD SUCCESSFUL in 24s 6 actionable tasks: 2 executed, 4 up-to-date The command "./gradlew test build" exited with 0. before_cache.1 0.00s$ rm -f $HOME/.gradle/caches/modules-2/modules-2.lock before_cache.2 0.00s$ rm -fr $HOME/.gradle/caches/*/plugin-resolution/ cache.2 store build cache after_script 11.19s$ ./gradlew jacocoTestReport coveralls Done. Your build exited with 0.
Once the coverage report is uploaded it looks like this:
Here’s the full commit on Github.
Next I’ll add some actual functionality and get that sweet 100% coverage.