显示导航

Grails 代码覆盖率

本指南将向您讲述如何使用 Clover 提高代码覆盖率。

作者:Sergio del Amo

Grails 版本 3.3.0

1 Grails 培训

Grails 培训 - 由创建并积极维护 Grails 框架的工作人员开发并提供!。

2 开始使用

Atlassian Clover 为 Java 和 Groovy 开发人员提供了用于代码覆盖率分析的可靠来源。

自 2017 年 4 月 11 日以来,Clover 已开源

Grails 3 使用 Gradle 构建系统用于与构建相关的任务(如编译、运行测试,以及生成项目的二进制分发)。

在本指南中,我们将安装一个 Gradle 插件以获取 Grails 应用程序的代码覆盖率。

2.1 你需要什么

要完成此指南,你需要以下内容

  • 一些时间

  • 一个合适的文本编辑器或 IDE

  • 安装了 JDK 1.7 或更高版本并正确配置了 JAVA_HOME

2.2 如何完成指南

请执行以下操作开始:

Grails 指南代码库包含两个文件夹

  • initial 初始项目。通常是一个简单的 Grails 应用程序,其中包含一些附加代码,让你可以快速上手。

  • complete 完成的示例。这是完成指南中提供的步骤并对 initial 文件夹应用这些更改的结果。

要完成指南,请转到 initial 文件夹

  • cdgrails-guides/grails-code-coverage/initial

并遵循后续章节中的说明。

如果你在 cd 中进入 grails-guides/grails-code-coverage/complete,你可以直接进入完成的示例

3 编写应用程序

3.1 Gradle Clover 插件

我们将使用Gradle Clover 插件使用 Clover 生成代码覆盖率报告。

我们将创建一个 gradle 文件来将 Clover 配置保留在同一个地方

gradle/clover.gradle
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 虽然 Clover 是开源的,但你需要创建一个虚拟许可证文件。
2 我们不希望某些文件污染我们的代码覆盖率报告。
3 我们希望将 Spock 规范包含为测试文件。
4 我们希望以 XML 和 HTML两种形式生成报告

我们准备从此 ROOT build.gradle 引用这个文件

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 将此插件添加为一个构建脚本依赖项
2 使用 Clover 配置应用此构建文件。
3 当前,你需要为 Clover Gradle 插件添加 groovy 依赖项才能使其工作。

3.2 代码和测试

我们有一个域类

grails-app/domain/demo/Person.groovy
package demo

class Person {
    String name
    boolean active
}

我们还有一个服务,它封装了针对先前域类的 GORM 查询。

grails-app/services/demo/PersonGormService.groovy
package demo

import grails.transaction.Transactional

@Transactional
class PersonGormService {

    @Transactional(readOnly = true)
    List<Person> findAllActive() {
        Person.where { active == true }.list()
    }
}

我们有一个 Groovy POJO。

src/main/groovy/demo/Name.groovy
package demo

import groovy.transform.Canonical
import groovy.transform.CompileStatic

@Canonical
@CompileStatic
class Name {
    String firstName
    String lastName
}

这个应用程序的业务逻辑在 NameService 中,它猜测每个 Person 实例的 firstNamelastName

grails-app/services/demo/NameService.groovy
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())
    }
}

我们为此服务编写了一个测试

src/test/groovy/demo/NameServiceSpec.groovy
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 取消对这个行的注释以增加你的代码覆盖率。

4 运行应用程序

要生成代码覆盖率报告,请使用 ./gradlew cloverGenerateReport 命令,它将在 build/reports/clover/html/index.html 下生成报告

探索你的报告并改善项目覆盖率

report

5 你需要 Grails 帮助吗?

Object Computing, Inc. (OCI) 赞助了本指南的创编。他们提供各类咨询和支持服务。

OCI 是 Grails 的之家

认识团队