【问题标题】:Android Gradle Jacoco: offline instrumentation for integration testsAndroid Gradle Jacoco:集成测试的离线工具
【发布时间】:2015-09-07 16:33:55
【问题描述】:

我们正在构建一个使用 Appium 进行测试的 Android 应用。现在我想看看我们的 Appium 测试的测试覆盖率。 我认为这是可能的,因为 Jacoco 支持离线检测 (http://www.eclemma.org/jacoco/trunk/doc/offline.html)。

甚至 jacoco gradle 插件的文档说:

虽然所有 Test 类型的任务都会在应用 java 插件时自动增强以提供覆盖信息,但任何实现 JavaForkOptions 的任务都可以通过 JaCoCo 插件进行增强。也就是说,任何派生 Java 进程的任务都可以用来生成覆盖率信息。

https://docs.gradle.org/current/userguide/jacoco_plugin.html

但是我该如何编写 build.gradle 以便检测我们的验收调试风格,并在执行 Appium 测试甚至手动测试用例时将 exec 文件写入智能手机? 因为这样我就可以提取 exec 文件并将其发送给 SonarQube 以进行进一步分析。

谢谢 本

【问题讨论】:

    标签: android gradle offline jacoco test-coverage


    【解决方案1】:

    最后我设法让它工作了,我想与你分享解决方案:

    为您的 buildType 启用检测并相应地配置 SonarQube 例如

    ...
    apply plugin: 'jacoco'
    ...
    
    android {
        ...
        productFlavors {
            acceptance {
                applicationId packageName + ".acceptance"
                buildTypes {
                    debug {
                        testCoverageEnabled true
                    }
                }
            }
        }
    }
    
    
    sonarRunner {
        sonarProperties {
            property "sonar.host.url", "..."
            property "sonar.jdbc.url", sonarDatabaseUrl
            property "sonar.jdbc.driverClassName", sonarDatabaseDriverClassName
            property "sonar.jdbc.username", sonarDatabaseUsername
            property "sonar.jdbc.password", sonarDatabasePassword
    
            property "sonar.sourceEncoding", "UTF-8"
            property "sonar.sources", "src/main"
            property "sonar.tests", "src/test"
            property "sonar.inclusions", "**/*.java,**/*.xml"
            property "sonar.import_unknown_files", "true"
            property "sonar.java.binaries", "build/intermediates/classes/acceptance/debug"
            property "sonar.junit.reportsPath", "build/test-results/acceptanceDebug"
            property "sonar.android.lint.report", "build/outputs/lint-results.xml"
            property "sonar.java.coveragePlugin", "jacoco"
            property "sonar.jacoco.reportPath", "build/jacoco/testAcceptanceDebugUnitTest.exec"
            // see steps below on how to get that file:
            property "sonar.jacoco.itReportPath", "build/jacoco/jacoco-it.exec"
    
            property "sonar.projectKey", projectKey
            property "sonar.projectName", projectName
            property "sonar.projectVersion", appVersionName
        }
    }
    

    将以下内容添加到您的 AndroidManifest.xml

    <receiver
     android:name=".util.CoverageDataDumper"
     tools:ignore="ExportedReceiver">
     <intent-filter>
        <action android:name="org.example.DUMP_COVERAGE_DATA"/>
     </intent-filter>
    </receiver>
    

    CoverageDataDumper 应该如下所示:

    public class CoverageDataDumper extends BroadcastReceiver {
       private static final Logger LOG = LoggerFactory.getLogger( CoverageDataDumper.class );
    
       @Override
       public void onReceive( Context context, Intent intent ) {
          try {
             Class
                .forName( "com.vladium.emma.rt.RT" )
                .getMethod( "dumpCoverageData", File.class, boolean.class, boolean.class )
                .invoke( null,
                   new File( App.getContext().getExternalFilesDir( null ) + "/coverage.ec" ),
                   true, // merge
                   false // stopDataCollection
                );
          }
          catch ( Exception e ) {
             LOG.error( "Error when writing coverage data", e );
          }
       }
    }
    

    然后使用验收风格应用程序(带有检测类)运行您的 Appium 测试用例。在调用“重置应用程序”或“关闭应用程序”之前,请确保调用以下方法(只是一个草稿,但我想你明白了):

    // intent is "org.example.DUMP_COVERAGE_DATA"
    public void endTestCoverage( String intent ) {
      if ( driver instanceof AndroidDriver ) {
         ((AndroidDriver) driver).endTestCoverage( intent, "" );
      }
    }
    public void pullCoverageData( String outputPath ) {
      String coverageFilePath = (String) appiumDriver.getCapabilities().getCapability( "coverageFilePath" );
      if ( coverageFilePath != null ) {
         byte[] log = appiumDriver.pullFile( coverageFilePath );
         MobileAppLog.writeLog( new File( outputPath ), log );
      }
      else {
         throw new AppiumLibraryNonFatalException(
            "Tried to pull the coverage data, but the coverageFilePath wasn't specified." );
      }
    }
    

    输出路径可以是例如:/sdcard/Android/data/org.example.acceptance/files/coverage.ec

    现在 Jacoco 数据已写入智能手机。接下来我们需要下载该文件。你可以使用

    appiumDriver.pullFile( logFilePath );
    

    现在您需要将文件“jacoco-it.exec”(在拉取文件时应始终附加)复制到 build/jacoco/jacoco-it.exec 中,请参阅 gradle.build上面并运行

    gradlew sonarRunner
    

    在 SonarQube 中添加集成测试覆盖小部件,您现在应该会看到一些值...

    不幸的是,如果您使用 retrolambda(就像我们一样),代码覆盖将不起作用。 Retrolambda 将生成不属于源文件的匿名类 - 因此 SonarQube 无法正确匹配它们,并且显示的代码覆盖率比实际低得多。如果有人找到解决方案,我会很高兴:-)

    【讨论】:

    • 是我遗漏了什么,还是你在这里同时使用了 emma 和 jacoco?我有一个类似的问题,所以这个答案非常有趣 - 但我无法理解。
    【解决方案2】:

    我通过向您测试的应用程序添加广播接收器解决了这个问题! (您只能将接收器添加到调试文件夹,因为它不需要存在于主源中)

     public class CoverageReceiver extends BroadcastReceiver {
        private static final String EXEC_FILE_PATH = "/mnt/sdcard/coverage.exec";
        private static final String TAG = "CoverageJacoco";
        private static final String BROADCAST_RECEIVED_MESSAGE = "EndJacocoBroadcast broadcast received!";
        private static final String EMMA_CLASS = "com.vladium.emma.rt.RT";
        private static final String EMMA_DUMP_METHOD = "dumpCoverageData";
    @Override
    public void onReceive(Context context, Intent intent) {
        try {
            Log.d(TAG, BROADCAST_RECEIVED_MESSAGE);
            Class.forName(EMMA_CLASS)
                    .getMethod(EMMA_DUMP_METHOD, File.class, boolean.class,
                            boolean.class)
                    .invoke(null, new File(EXEC_FILE_PATH), true,
                            false);
        } catch (Exception e) {
            Log.d(TAG, e.getMessage());
        }
    }
    }
    

    在 manefist 中添加(你可以添加这个调试文件夹,这样它就不会存在于主源中)

        <?xml version="1.0" encoding="utf-8"?>
    <manifest xmlns:android="http://schemas.android.com/apk/res/android" >
    
    
        <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
    
    
        <application>
    
            <receiver android:name=".CoverageReceiver">
                <intent-filter>
                    <action android:name="com.example.action" />
                </intent-filter>
            </receiver>
        </application>
    

    在我添加的应用程序的 build.gradle 中

    apply plugin: 'jacoco'
    
    jacoco {
        toolVersion = "0.7.4+"
    }
    
    model {
        android {
            compileSdkVersion 23
            buildToolsVersion "23.0.2"
        defaultConfig {
            applicationId "com.example.app"
            minSdkVersion.apiLevel 23
            targetSdkVersion.apiLevel 23
            versionCode 12
            versionName "1.11"
    
        }
        buildTypes {
    
            debug {
                testCoverageEnabled true
    
            }
        }
    

    您将应用程序构建为调试,而不是安装并运行它。

    通过ADB发送广播“adb shell am broadcast -a com.example.action”创建coverage.exec 从设备拉覆盖 - adb pull /mnt/sdcard/coverage.exec

    运行后,您需要从文件中创建覆盖范围

       **
     * This task is used to create a code coverage report via the Jcoco tool.
     */
    task jacocoTestReport(type: JacocoReport) {
        def coverageSourceDirs = [
                'src/main/java',               
        ]
        group = "Reporting"
        description = "Generates Jacoco coverage reports"
        reports {
            csv.enabled false
            xml{
                enabled = true
                destination "${buildDir}/jacoco/jacoco.xml"
            }
            html{
                enabled true
                destination "${buildDir}/jacocoHtml"
            }
        }
        classDirectories = fileTree(
                dir: 'build/intermediates/classes',
                excludes: ['**/R.class',
                           '**/R$*.class',
                           '**/BuildConfig.*',
                           '**/Manifest*.*',
                           '**/*Activity*.*',
                           '**/*Fragment*.*'
                ]
        )
        sourceDirectories = files(coverageSourceDirs)
        executionData = files('build/coverage.exec')
    }
    

    此任务是创建覆盖文件的一种方法 在coverageSourceDirs 中添加您的应用程序源代码的所有位置,因此它将知道要采用哪些代码并根据它们创建覆盖范围 executionData 是您放置从设备中提取的 coverage.exec 的位置

    运行任务 这些文件将为 html 和 xml 创建,您还可以添加 csv(注意它将在应用程序的 build 文件夹中创建)!

    需要知道,您必须针对构建应用程序调试版本的相同代码运行任务

    【讨论】:

      猜你喜欢
      • 2017-05-13
      • 1970-01-01
      • 1970-01-01
      • 2017-05-05
      • 1970-01-01
      • 2020-10-16
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多