Кодовое покрытие Jacoco и модульных тестов с помощью android-gradle-plugin >= 1.1

Я недавно начал интегрировать android-gradle-plugin 1.1.0 в одном из моих проектов. Проект использует robolectric 2.4 запускать юнит-тесты.

Это многомодульный проект с очень сложными зависимостями (некоторые модули зависят от других модулей). Что-то вроде того:

--> application-module (dependsOn: module1, module2, module-core)
    --> module1 (dependsOn: module-core)
    --> module2 (dependsOn: module-core)
    --> module-core (dependsOn: module3, module4)
        --> module3 (library dependencies)
        --> module4 (library dependencies)

Для более четкой картины, пожалуйста, смотрите проект jacoco-example.

Я пытался интегрировать JaCoCo для генерации отчетов для модульных тестов, но мне кажется, что он работает только androidTests которые в основном инструментальные тесты.

После некоторого Google я наткнулся на несколько проектов на GitHub и других статьях, но они в основном ориентированы на предыдущие версии android-gradle-plugin или используете сторонние плагины, такие как android-unit-test например здесь.

Может быть, я потерял способность гуглить. Но может ли кто-нибудь указать мне направление, в котором я могу найти некоторые документы, касающиеся новых вещей в плагине для android gradle и как запустить задачу jacoco только для модульных тестов?

ОБНОВИТЬ

Принял сценарий из примера Неника:

apply plugin: "jacoco"

configurations {
    jacocoReport
}

task jacocoReport(dependsOn: 'testDebug') << {
    ant {
        taskdef(name:'jacocoreport',
                classname: 'org.jacoco.ant.ReportTask',
                classpath: configurations.jacocoReport.asPath)

        mkdir dir: "${buildDir}/test-coverage-report"
        mkdir dir: "${buildDir}/reports/jacoco/test/"

        jacocoreport {
            executiondata = files("${buildDir}/jacoco/testDebug.exec")

            structure(name: "${rootProject.name}") {
                classfiles {
                    fileset (dir: "${buildDir}/intermediates/classes/debug") {
                        //exclude(name: '**/*_*.class')
                        exclude(name: '**/R.class')
                        exclude(name: '**/R$*.class')
                        exclude(name: '**/BuildConfig.class')
                    }
                }

                sourcefiles {
                    fileset dir: "src/main/java"
                    fileset dir: "${buildDir}/generated/source/buildConfig/debug"
                    fileset dir: "${buildDir}/generated/source/r/debug"
                }
            }

            xml destfile: "${buildDir}/reports/jacoco/test/jacocoTestReport.xml"
            html destdir: "${buildDir}/test-coverage-report/"
        }
    }
}

dependencies {
    jacocoReport 'org.jacoco:org.jacoco.ant:0.7.2.201409121644'
}

После этого ./gradlew jacocoReport выполняет и генерирует отчет, но показывает 0 (ноль) тестовое покрытие, что невозможно, потому что тестируется как минимум половина всех классов.

UPDATE_2

Опробовал этот пример. Добавление следующей задачи в один из моих файлов сборки Gradle:

task jacocoTestReport(type:JacocoReport, dependsOn: "testDebug") {
    group = "Reporting"
    description = "Generate Jacoco coverage reports"

    classDirectories = fileTree(
            dir: "${buildDir}/intermediates/classes/debug",
            excludes: ['**/R.class',
                       '**/R$*.class',
                       '**/*$ViewInjector*.*',
                       '**/BuildConfig.*',
                       '**/Manifest*.*']
    )

    sourceDirectories = files("${buildDir.parent}/src/main/java")
    additionalSourceDirs = files([
            "${buildDir}/generated/source/buildConfig/debug",
            "${buildDir}/generated/source/r/debug"
    ])
    executionData = files("${buildDir}/jacoco/testDebug.exec")

    reports {
        xml.enabled = true
        html.enabled = true
    }
}

Та же проблема, отчеты генерируются, но охват кода все еще равен нулю.

UPDATE_3

Похоже, что задача из UPDATE_2 работала, но только для модуля с apply plugin: 'com.android.application' (Отчеты сгенерированы правильно). Но для модулей, которые являются библиотеками Android (apply plugin: 'com.android.library') отчеты показывают нулевое покрытие, хотя модули содержат больше тестов, чем модуль приложения.

UPDATE_4

Создан простой пример проекта, который демонстрирует мою проблему. В настоящее время, если вы запускаете ./gradlew jacocoReport отчет генерируется, но тестовое покрытие для проектов модулей не отображается. Смотрите эту ссылку

Краткое примечание: Когда тесты проходили под AndroidUnitTests (whiteout JUnit 4 и Robolectric), отчеты JaCoCo показывали охват всех модулей.

Есть идеи?

9 ответов

Решение

После этого я решил создать для этого плагин Gradle с открытым исходным кодом.

Root build.gradle

buildscript {
    repositories {
        mavenCentral() // optional if you have this one already
    }
    dependencies {
        classpath 'com.vanniktech:gradle-android-junit-jacoco-plugin:0.8.0'
    }
}

apply plugin: 'com.vanniktech.android.junit.jacoco'

Тогда просто выполните

./gradlew jacocoTestReportDebug

Он запустит тесты JUnit в режиме отладки, а затем выдаст вывод Jacoco в форме xml и html в соответствующем каталоге сборки.

Он также поддерживает вкусы. Имея 2 аромата красного и синего, эти задачи будут созданы

  • jacocoTestReportRedDebug
  • jacocoTestReportBlueDebug
  • jacocoTestReportRedRelease
  • jacocoTestReportBlueRelease

После некоторого дополнительного поиска, который я наткнулся на этот проект, мне пришлось внести некоторые изменения, чтобы решение могло работать для моего типа проекта, но теперь отчеты о покрытии тестов генерируются правильно.

Я перенес принятые изменения в мой пример репозитория github на случай, если у кого-то возникнет аналогичная проблема в будущем.

Предупреждение: это взлом! Используя вашу конфигурацию выше, я собрал хак для переключения плагина Android между приложением и библиотекой в ​​зависимости от выбранных задач сборки. Это хорошо работает для меня, потому что я не заканчиваю фиксацию кода с установленным режимом приложения.

// dynamically change the android plugin to application if we are running unit tests or test reports.
project.ext.androidPlugin = 'com.android.library'
for (String taskName : project.gradle.startParameter.taskNames) {
    if (taskName.contains('UnitTest') || taskName.contains('jacocoTestReport')) {
        project.ext.androidPlugin = 'com.android.application'
        break
    }
}

logger.lifecycle("Setting android pluging to ${project.ext.androidPlugin}")
apply plugin: project.ext.androidPlugin

...

apply plugin: 'jacoco'

configurations {
    jacocoReport
}

task jacocoTestReport(type:JacocoReport, dependsOn: "testDebug") {
    group = "Reporting"
    description = "Generate Jacoco coverage reports"

    classDirectories = fileTree(
            dir: "${buildDir}/intermediates/classes/debug",
            excludes: ['**/R.class',
                       '**/R$*.class',
                       '**/*$ViewInjector*.*',
                       '**/BuildConfig.*',
                       '**/Manifest*.*']
    )

    sourceDirectories = files("${buildDir.parent}/src/main/java")
    additionalSourceDirs = files([
            "${buildDir}/generated/source/buildConfig/debug",
            "${buildDir}/generated/source/r/debug"
    ])
    executionData = files("${buildDir}/jacoco/testDebug.exec")

    reports {
        xml.enabled = true
        html.enabled = true
    }
}

Будем надеяться, что команда android tools исправит это в ближайшее время.

Я настроил свои модульные тесты на Gradle 1.2, используя этот пост в блоге. Затем я собрал информацию, которую нашел здесь и в других местах, чтобы добавить покрытие кода для независимых модулей вместо всего проекта. В моем модуле библиотеки build.gradle файл, я добавил следующее:

apply plugin: 'jacoco'

def jacocoExcludes = [
        'com/mylibrary/excludedpackage/**'
]

android {
    ...
}

android.libraryVariants.all { variant ->
    task("test${variant.name.capitalize()}WithCoverage", type: JacocoReport, dependsOn: "test${variant.name.capitalize()}") {
        group = 'verification'
        description = "Run unit test for the ${variant.name} build with Jacoco code coverage reports."

        classDirectories = fileTree(
                dir: variant.javaCompile.destinationDir,
                excludes: rootProject.ext.jacocoExcludes.plus(jacocoExcludes)
        )
        sourceDirectories = files(variant.javaCompile.source)
        executionData = files("${buildDir}/jacoco/test${variant.name.capitalize()}.exec")

        reports {
            xml.enabled true
            xml.destination "${buildDir}/reports/jacoco/${variant.name}/${variant.name}.xml"
            html.destination "${buildDir}/reports/jacoco/${variant.name}/html"
        }
    }
}

И в моем проекте build.gradle Файл, который я добавил общий исключает:

ext.jacocoExcludes = [
    'android/**',
    '**/*$$*',
    '**/R.class',
    '**/R$*.class',
    '**/BuildConfig.*',
    '**/Manifest*.*',
    '**/*Service.*'
]

Кроме того, похоже, что покрытие кода для модульных тестов может быть встроено в будущем выпуск 144664

Наконец-то я смог увидеть покрытие кода тестами JUnit в Android Studio 1.1.

jacoco.gradle

apply plugin: 'jacoco'

jacoco {
    toolVersion "0.7.1.201405082137"
}

def coverageSourceDirs = [
        "$projectDir/src/main/java",
]

task jacocoTestReport(type: JacocoReport, dependsOn: "testDebug") {
    group = "Reporting"
    description = "Generate Jacoco coverage reports after running tests."
    reports {
        xml.enabled = true
        html.enabled = true
    }
    classDirectories = fileTree(
            dir: './build/intermediates/classes/debug',
            excludes: ['**/R*.class',
                       '**/*$InjectAdapter.class',
                       '**/*$ModuleAdapter.class',
                       '**/*$ViewInjector*.class'
            ]
    )
    sourceDirectories = files(coverageSourceDirs)
    executionData = files("$buildDir/jacoco/testDebug.exec")
    // Bit hacky but fixes https://code.google.com/p/android/issues/detail?id=69174.
    // We iterate through the compiled .class tree and rename $$ to $.
    doFirst {
        new File("$buildDir/intermediates/classes/").eachFileRecurse { file ->
            if (file.name.contains('$$')) {
                file.renameTo(file.path.replace('$$', '$'))
            }
        }
    }
}

а затем в файле build.gradle модуля (я положил его между android а также dependencies):

apply from: '../jacoco.gradle'

Также в defaultConfig блок из android, Я добавил это (не знаю, если это необходимо, но я получил это из этого блога):

android {
    defaultConfig {
        testHandleProfiling true
        testFunctionalTest true
    }
}

Наслаждаться.

Вы можете попробовать использовать этот плагин Gradle: https://github.com/arturdm/jacoco-android-gradle-plugin

По сути, все, что вам нужно сделать, это применить его так:

buildscript {
  repositories {
    jcenter()
  }
  dependencies {
    classpath 'com.dicedmelon.gradle:jacoco-android:0.1.1'
  }
}

apply plugin: 'com.android.library' // or 'com.android.application'
apply plugin: 'jacoco-android'

В результате вы должны получить JacocoReport задание для каждого варианта. Запустите команду ниже, чтобы сгенерировать отчеты о покрытии кода для всех из них.

$ ./gradlew jacocoTestReport

Я столкнулся с точно такой же проблемой, как ты. Сегодня сделал полностью удаленную андроид студию, андроид sdk, gradle. Затем переустановите все. После этого я просто добавил в приложение build.gradle.

debug {testCoverageEnabled true} Затем я запускаю./gradlew connectedChec. Все работает отлично. Android студия по умолчанию Jacoco работает нормально для меня. Я думаю, что также возможно создать Задачу jacocoTestReport и затем создать покрытие кода. Я не знаю, почему студия gradle и android ранее не работала.

Я решаю проблемы с JaCoCo и заставляю его работать с последним подключаемым модулем Android 1.1.3

Проект с последними скриптами Gradle: https://github.com/OleksandrKucherenko/meter

Рекомендации:

Как прикрепить собственную реализацию вместо Mocks в модульных тестах Android Studio? https://plus.google.com/117981280628062796190/posts/8jWV22mnqUB

Небольшая подсказка для всех, кто пытается использовать покрытие JaCoCo в сборках Android... неожиданная находка!!! https://plus.google.com/117981280628062796190/posts/RreU44qmeuP

Отчет JaCoCo XML/HTML для юнит-тестов https://plus.google.com/u/0/+OleksandrKucherenko/posts/6vNWkkLed3b

Пожалуйста, создайте пример, и я могу взглянуть. Я думаю, это какая-то пропущенная конфигурация пути.

  • включить все файлы покрытия (*.exec)
  • добавьте все ваши исходные пути (модуль /src/main/java)
  • добавить все пути классов (модуль / сборка / промежуточные / классы / отладка)

вот два примера, как это могло бы выглядеть

Другие вопросы по тегам