Grails Code Coverage
In this guide you'll learn how to improve your code coverage using Clover.
Authors: Sergio del Amo
Grails Version: 3.3.0
1 Grails Training
Grails Training - Developed and delivered by the folks who created and actively maintain the Grails framework!.
2 Getting Started
Atlassian Clover provides Java and Groovy developers a reliable source for code coverage analysis.
Since April 11, 2017, Clover is Open Source.
Grails 3 uses the Gradle Build System for build related tasks, such as compilation, running tests, and producing binary distributions of your project.
In this guide we are going to install a Gradle plugin to get code coverage of our Grails application.
2.1 What you will need
To complete this guide, you will need the following:
-
Some time on your hands
-
A decent text editor or IDE
-
JDK 1.7 or greater installed with
JAVA_HOME
configured appropriately
2.2 How to complete the guide
To get started do the following:
-
Download and unzip the source
or
-
Clone the Git repository:
git clone https://github.com/grails-guides/grails-code-coverage.git
The Grails guides repositories contain two folders:
-
initial
Initial project. Often a simple Grails app with some additional code to give you a head-start. -
complete
A completed example. It is the result of working through the steps presented by the guide and applying those changes to theinitial
folder.
To complete the guide, go to the initial
folder
-
cd
intograils-guides/grails-code-coverage/initial
and follow the instructions in the next sections.
You can go right to the completed example if you cd into grails-guides/grails-code-coverage/complete
|
3 Writing the Application
3.1 Gradle Clover Plugin
We are going to use the Gradle Clover Plugin to generating a code coverage report using Clover.
We are going to create a gradle file to keep the Clover configuration in one place
buildscript {
repositories {
jcenter()
}
dependencies {
classpath 'com.bmuschko:gradle-clover-plugin:2.1.1'
}
}
apply plugin: 'com.bmuschko.clover'
dependencies {
clover 'org.openclover:clover:4.2.0'
}
clover {
licenseLocation = File.createTempFile('clover', '.license').absolutePath (1)
excludes = ['**/Application.groovy', (2)
'**/BootStrap.groovy',
'**/UrlMappings.groovy',
'**/*GrailsPlugin.groovy',
'**/*Mock.groovy',
]
testIncludes = ['**/*Spec.groovy'] (3)
report { (4)
html = true
xml = true
}
}
1 | Although, Clover is open source, you need to create a dummy license file. |
2 | We don’t want certain files to pollute our code coverage reports. |
3 | We want to include our Spock specifications as test files. |
4 | We want reports in both XML and HTML |
We are going to reference this file from the ROOT build.gradle
buildscript {
repositories {
mavenLocal()
maven { url "https://repo.grails.org/grails/core" }
}
dependencies {
classpath "org.grails:grails-gradle-plugin:$grailsVersion"
classpath "com.bertramlabs.plugins:asset-pipeline-gradle:2.14.2"
classpath "org.grails.plugins:hibernate5:${gormVersion-".RELEASE"}"
classpath 'com.bmuschko:gradle-clover-plugin:2.1.1' (1)
}
}
version "0.1"
group "grails.code.coverage"
apply plugin:'groovy'
apply plugin:"eclipse"
apply plugin:"idea"
apply plugin:"war"
apply plugin:"org.grails.grails-web"
apply plugin:"org.grails.grails-gsp"
apply plugin:"asset-pipeline"
apply from: "${project.projectDir}/gradle/clover.gradle" (2)
repositories {
mavenLocal()
maven { url "https://repo.grails.org/grails/core" }
}
dependencies {
compile "org.springframework.boot:spring-boot-starter-logging"
compile "org.springframework.boot:spring-boot-autoconfigure"
compile "org.grails:grails-core"
compile "org.springframework.boot:spring-boot-starter-actuator"
compile "org.springframework.boot:spring-boot-starter-tomcat"
compile "org.grails:grails-web-boot"
compile "org.grails:grails-logging"
compile "org.grails:grails-plugin-rest"
compile "org.grails:grails-plugin-databinding"
compile "org.grails:grails-plugin-i18n"
compile "org.grails:grails-plugin-services"
compile "org.grails:grails-plugin-url-mappings"
compile "org.grails:grails-plugin-interceptors"
compile "org.grails.plugins:cache"
compile "org.grails.plugins:async"
compile "org.grails.plugins:scaffolding"
compile "org.grails.plugins:events"
compile "org.grails.plugins:hibernate5"
compile "org.hibernate:hibernate-core:5.1.5.Final"
compile "org.grails.plugins:gsp"
compile 'org.codehaus.groovy:groovy-all:2.4.10'(3)
console "org.grails:grails-console"
profile "org.grails.profiles:web"
runtime "org.glassfish.web:el-impl:2.1.2-b03"
runtime "com.h2database:h2"
runtime "org.apache.tomcat:tomcat-jdbc"
runtime "com.bertramlabs.plugins:asset-pipeline-grails:2.14.2"
testCompile "org.grails:grails-gorm-testing-support"
testCompile "org.grails.plugins:geb"
testCompile "org.grails:grails-web-testing-support"
testRuntime "org.seleniumhq.selenium:selenium-htmlunit-driver:2.47.1"
testRuntime "net.sourceforge.htmlunit:htmlunit:2.18"
}
bootRun {
jvmArgs('-Dspring.output.ansi.enabled=always')
addResources = true
}
assets {
minifyJs = true
minifyCss = true
}
1 | Add the plugin as a build script depedency |
2 | Apply the build file with Clover configuration. |
3 | Currently, you need to add the groovy dependency for Clover Gradle plugin to work. |
3.2 Code and test
We have a domain class
package demo
class Person {
String name
boolean active
}
We have also a service which encapsulates the GORM queries against the previous domain class.
package demo
import grails.transaction.Transactional
@Transactional
class PersonGormService {
@Transactional(readOnly = true)
List<Person> findAllActive() {
Person.where { active == true }.list()
}
}
We have a Groovy POJO.
package demo
import groovy.transform.Canonical
import groovy.transform.CompileStatic
@Canonical
@CompileStatic
class Name {
String firstName
String lastName
}
The business logic of this app is in the NameService
which
guesses the firstName
and lastName
of each of the Person
instances.
package demo
import groovy.transform.CompileStatic
@CompileStatic
class NameService {
PersonGormService personGormService
List<Name> findAllPersonNames() {
personGormService.findAllActive().collect { Person person ->
nameWithFullName(person.name)
}
}
Name nameWithFullName(String fullname) {
if ( fullname.contains('del ') ) {
return nameWithSeparator(fullname, 'del')
}
return nameWithSeparator(fullname, ' ')
}
Name nameWithSeparator(String fullname, String separator) {
String firstName
String lastName
String[] arr = fullname.split(separator)
if ( arr.length > 0 ) {
firstName = "${arr[0]}${separator}".toString()
int from = firstName.length()
lastName = fullname.substring(from, fullname.length())
} else {
firstName = fullname
}
new Name(firstName: firstName?.trim(), lastName: lastName?.trim())
}
}
We have written a test for this service:
package demo
import grails.testing.services.ServiceUnitTest
import spock.lang.Specification
class NameServiceSpec extends Specification implements ServiceUnitTest<NameService> {
def "findAllPersonNames differentiates between names containing the substring 'del'"() {
given:
service.personGormService = Stub(PersonGormService) {
findAllActive() >> [
// new Person(name: 'Sergio del Amo', active: true), (1)
new Person(name: 'Graeme Rocher', active: true),]
}
when:
List<Name> names = service.findAllPersonNames()
then:
names.each { Name name ->
assert [
new Name(firstName: 'Sergio del', lastName: 'Amo'),
new Name(firstName: 'Graeme', lastName: 'Rocher')
].contains(name)
}
}
}
1 | Uncomment this line to increase your code coverage. |
4 Running the Application
To generate the code coverage report use the ./gradlew cloverGenerateReport
command
which will generate the reports under: build/reports/clover/html/index.html
Explore your report and improve the project coverage: