针对多模块应用的Android测试覆盖率报告

时间:2016-06-08 04:56:37

标签: android unit-testing automated-tests integration-testing android-espresso

我们有一个多模块应用程序。我们有3个图书馆项目和1个启动项目。

module1(Libraray) module2(Libraray)依赖于module1 module3(Libraray)依赖于module1

启动(没有任何源代码,它只是所有lib的启动器)取决于module1和模块2.

在module1中,我们使用facade模式访问模块2和模块3类。由于我们需要在Launch项目中编写所有测试用例,因为我们已经访问了启动项目中的所有类,因此我们可以访问所有类,并且测试用例不会因NoClassDefException而失败。

当我们在Launch项目中编写测试用例时,我们能够运行测试用例,并且我们将执行报告作为100%并创建一个index.html文件,其中包含测试用例的所有详细信息,但是当我尝试生成覆盖率报告然后它不显示覆盖率报告的任何数据。下面是我的gradle文件。

apply plugin: 'com.android.application'
apply plugin: 'jacoco'
android {
compileSdkVersion 22
buildToolsVersion "23.0.2"`

defaultConfig {
    applicationId "com.test.mobile"
    minSdkVersion 14
    targetSdkVersion 17
    multiDexEnabled true
    testApplicationId "com.test.mobile.test"
    testInstrumentationRunner 'android.support.test.runner.AndroidJUnitRunner'
}

repositories {
    mavenCentral()
}

buildTypes {

    release {
        minifyEnabled true
        proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-project.txt'
    }

    debug{
        testCoverageEnabled true
    }
}


dexOptions {
    preDexLibraries = false
    javaMaxHeapSize "4096M"
    jumboMode = true
    incremental false
}

afterEvaluate {
    tasks.matching {
        it.name.startsWith('dex')
    }.each { dx ->
        if (dx.additionalParameters == null) {
            dx.additionalParameters = []
        }
        dx.additionalParameters += '--multi-dex'
        dx.additionalParameters += "--main-dex-list=$projectDir\\multidex-main-dex-list.txt".toString()
    }
}}
dependencies {
compile project(':module2')
compile project(':module3')
compile "com.android.support.test.espresso:espresso-idling-resource:2.2.1"

// Dependencies for local unit tests
testCompile "junit:junit:4.12" exclude group: 'com.android.support', module: 'support-annotations'

testCompile "org.mockito:mockito-all:1.10.19" exclude group: 'com.android.support', module: 'support-annotations'
testCompile "org.hamcrest:hamcrest-all:1.3" exclude group: 'com.android.support', module: 'support-annotations'
testCompile "org.powermock:powermock-module-junit4:1.6.2" exclude group: 'com.android.support', module: 'support-annotations'
testCompile "org.powermock:powermock-api-mockito:1.6.2" exclude group: 'com.android.support', module: 'support-annotations'


// Android Testing Support Library's runner and rules
androidTestCompile "com.android.support.test:runner:0.4.1"  exclude group: 'com.android.support', module: 'support-annotations'
androidTestCompile "com.android.support.test:rules:0.4.1" exclude group: 'com.android.support', module: 'support-annotations'

// Espresso UI Testing dependencies.
androidTestCompile "com.android.support.test.espresso:espresso-core:2.2.1" exclude group: 'com.google.code.findbugs' exclude group: 'javax.annotation' exclude group: 'com.android.support', module: 'support-annotations' exclude module: 'javax.annotation-api'
androidTestCompile "com.android.support.test.espresso:espresso-contrib:2.2.1" exclude group: 'com.google.code.findbugs' exclude group: 'javax.annotation' exclude group: 'com.android.support', module: 'support-annotations' exclude module: 'javax.annotation-api' exclude group: 'com.android.support', module: 'support-v4'
androidTestCompile "com.android.support.test.espresso:espresso-intents:2.2.1" exclude group: 'com.google.code.findbugs' exclude group: 'javax.annotation' exclude group: 'com.android.support', module: 'support-annotations' exclude module: 'javax.annotation-api'}

task jacocoTestReport(type: JacocoReport, dependsOn: 'testDebugUnitTest') { def projects = new ArrayList() subprojects.each { prj -> projects.add(prj) }

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

jacocoClasspath = configurations['androidJacocoAnt']

def fileFilter = ['**/R.class', '**/R$*.class', '**/BuildConfig.*', '**/Manifest*.*', '**/*Test*.*', 'android/**/*.*']
def debugTree = fileTree(dir: "${buildDir}/intermediates/classes/debug", excludes: fileFilter)
def mainSrc = "${project.projectDir}/src/main/java"

sourceDirectories = files([mainSrc])
classDirectories = files([debugTree])
/*sourceDirectories = generateSourceFiles(projects)
classDirectories = generateClassDirs(projects)*/

executionData = files(["${buildDir}/jacoco/testDebugUnitTest.exec",
                       "${buildDir}/outputs/code-coverage/connected/coverage.ec"
])}

3 个答案:

答案 0 :(得分:7)

我有3个以gcm_demo,googleservices和networkcommunication命名的模块 所以在每个模块的build.gradle下 写

apply plugin: 'jacoco'
task jacocoRootReport(type: JacocoReport, dependsOn: ['gcm_demo:jacocoTestReport', 'googleservice:jacocoTestReport', 'networkcommunication:jacocoTestReport']) {
reports {
    xml.enabled = true
    html.enabled = true
}
sourceDirectories = files([tasks.getByPath("gcm_demo:jacocoTestReport").sourceDirectories,
                           tasks.getByPath("googleservice:jacocoTestReport").sourceDirectories,
                           tasks.getByPath("networkcommunication:jacocoTestReport").sourceDirectories])

classDirectories = files([tasks.getByPath("gcm_demo:jacocoTestReport").classDirectories,
                          tasks.getByPath("googleservice:jacocoTestReport").classDirectories,
                          tasks.getByPath("networkcommunication:jacocoTestReport").classDirectories])

executionData = files([tasks.getByPath("gcm_demo:jacocoTestReport").executionData,
                       tasks.getByPath("googleservice:jacocoTestReport").executionData,
                       tasks.getByPath("networkcommunication:jacocoTestReport").executionData])

}

现在在Project build.gradle中编写以下scrpit

gradlew clean jRR (short abbreviation)

执行使用

{project location}\build\reports\jacoco\jacocoRootReport\html\index.html

构建成功输出文件夹后

# urls.py
url(r'^(?P<app_label>[\w\-]+)/(?P<model_name>[\w\-]+)/$', DynamicListView.as_view(), name='list'),


# views.py    
class DynamicListView(LoginRequiredMixin, ListView):
      template_name = 'dynamic_list.html'

      @property
      def model(self):
        return apps.get_model(app_label=str(self.kwargs['app_label']), model_name=str(self.kwargs['model_name']))

      @property
      def app_label(self):
        return str(self.kwargs['app_label'])

      def get_queryset(self):
        queryset = super(DynamicListView, self).get_queryset()
        return queryset.filter(**self.request.GET.dict())

      def dispatch(self, request, *args, **kwargs):
        if request.user.is_superuser:
            return super(DynamicListView, self).dispatch(request, *args, **kwargs)

        else:
            # Set permissions here for non-staff users

它提供了UI和unitTest的完整项目范围

答案 1 :(得分:5)

这就是我们在顶级build.gradle中生成HTML覆盖率报告的内容:

def coverageSourceDirs = ['app/src/main/java', 'core/src/main/java', 'database/src/main/java']

def coverageExcludes = ['**/R.class',
                        '**/R$*.class',
                        '**/*$$ViewBinder*.*',
                        '**/inject/*',
                        '**/*$InjectAdapter.*',
                        '**/BuildConfig.*',
                        '**/Manifest*.*',
                        '**/Dagger*.*',
                        '**/*_Provide*Factory.*',
                        '**/*_Member*Injector.*',
                        '**/*_Factory.*']

def coverageClassDirectories = [fileTree(dir: 'app/build/intermediates/classes/debug', excludes: coverageExcludes),
                                fileTree(dir: 'core/build/intermediates/classes/debug', excludes: coverageExcludes),
                                fileTree(dir: 'database/build/intermediates/classes/debug', excludes: coverageExcludes)]

task jacocoRootReport(type: JacocoReport) {
    dependsOn "app:jacocoTestReport",
            "core:jacocoTestReport",
            "database:jacocoTestReport"

    additionalSourceDirs = files(coverageSourceDirs)
    sourceDirectories = files(coverageSourceDirs)
    classDirectories = files(coverageClassDirectories)
    executionData = files(tasks.getByPath("app:jacocoTestReport").executionData,
            tasks.getByPath("core:jacocoTestReport").executionData,
            tasks.getByPath("database:jacocoTestReport").executionData    
)

    reports {
        html.enabled = true
        xml.enabled = false
        csv.enabled = false
    }
    onlyIf = {
        true
    }

    doFirst {
        executionData = files(executionData.findAll {
            it.exists()
        })
    }
}

在每个子模块中:

android {
    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }

        debug {
            testCoverageEnabled = true
        }
    }
}

def coverageSourceDirs = ['src/main/java']

task jacocoTestReport(type: JacocoReport, dependsOn: "testJenkinsUnitTest") {
    group = "Reporting"

    description = "Generate Jacoco coverage reports"

    classDirectories = fileTree(dir: 'build/intermediates/classes/debug',
            excludes: ['**/R.class',
                       '**/R$*.class',
                       '**/*$$ViewBinder*.*',
                       '**/inject/*',
                       '**/*$InjectAdapter.*',
                       '**/BuildConfig.*',
                       '**/Manifest*.*',
                       '**/Dagger*.*',
                       '**/*_Provide*Factory.*',
                       '**/*_Member*Injector.*',
                       '**/*_Factory.*',
                       '**/PagerTitleStripV22*.*'])

    additionalSourceDirs = files(coverageSourceDirs)
    sourceDirectories = files(coverageSourceDirs)
    executionData = files('build/jacoco/testDebugUnitTest.exec')

    doFirst {
        new File("$buildDir/intermediates/classes/").eachFileRecurse { file ->
            if (file.name.contains('$$')) {
                file.renameTo(file.path.replace('$$', '$'))
            }
        }
    }

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

请注意,如果您使用Jenkins插件或Sonar进行覆盖率分析,则不需要它。

P.S。如果您有任何问题,请手动检查所有路径,我可能会输错。设置它确实很痛苦

答案 2 :(得分:1)

除了使用getByPath之外,您还可以使用变量来访问build.gradle本身的不同模块,例如$buildDir将带您到当前模块build文件夹。

第二,$project.projectDir.parent将进入父项目。示例$project.projectDir.parent/<sub-project-name>/outputs/code-coverage/connected/coverage.ec

您可以使用您的子项目名称:gcm_demo, googleservices or networkcommunication$project.projectDir.parent/gcm_demo/outputs/code-coverage/connected/coverage.ec

注意:确保使用正确的文件coverage.eccoverage.exec检查为您生成的文件

要打印所有路径,可以在build.gradle中使用以下任务: 按照定义

运行gradle paths

task paths { println "Printing the current module build: $buildDir" println "Printing the module directory: $project.projectDir" println "Printing the parent module: $project.projectDir.parent" }

这将帮助您在多模块android项目中使用目录和文件夹。我的问题是无法从子模块build.gradle

访问正确的文件夹和文件目录