简体   繁体   English

Android Gradle Jacoco:用于集成测试的离线工具

[英]Android Gradle Jacoco: offline instrumentation for integration tests

we are building an Android app which is tested by using Appium. 我们正在构建一个使用Appium进行测试的Android应用。 Now I would like to see the test coverage of our Appium tests. 现在,我想看看我们的Appium测试的测试范围。 I think this is possible, because Jacoco supports offline instrumentation ( http://www.eclemma.org/jacoco/trunk/doc/offline.html ). 我认为这是可行的,因为Jacoco支持离线检测( http://www.eclemma.org/jacoco/trunk/doc/offline.html )。

And even the documentation of the jacoco gradle plugin says: 甚至jacoco gradle插件的文档都说:

While all tasks of type Test are automatically enhanced to provide coverage information when the java plugin has been applied, any task that implements JavaForkOptions can be enhanced by the JaCoCo plugin. 当应用Java插件时,虽然Test类型的所有任务都会自动增强以提供覆盖信息,但是JaCoCo插件可以增强实现JavaForkOptions的任何任务。 That is, any task that forks Java processes can be used to generate coverage information. 也就是说,任何派生Java进程的任务都可以用来生成coverage信息。

see https://docs.gradle.org/current/userguide/jacoco_plugin.html 参见https://docs.gradle.org/current/userguide/jacoco_plugin.html

But how do I have to write the build.gradle so our acceptance debug flavor is instrumented and the exec file is written to the Smartphone when the Appium tests are executed or even manual test cases are executed? 但是,当执行Appium测试甚至执行手动测试用例时,我该如何编写build.gradle以便检测我们的验收调试风格并将exec文件写入智能手机? Because then I can extract the exec file and send it so SonarQube for further analysis. 因为这样我可以提取exec文件并将其发送给SonarQube进行进一步分析。

Thanks Ben 谢谢本

Finally I managed it to get it working and I want to share the solution with you: 最终,我设法使其正常运行,并希望与您分享解决方案:

enable instrumentation for your buildType and configure SonarQube accordingly eg 为您的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
    }
}

add the following to your AndroidManifest.xml 将以下内容添加到您的AndroidManifest.xml中

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

CoverageDataDumper should look like that: 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 );
      }
   }
}

Then run your Appium test cases with the acceptance flavor app (with instrumented classes). 然后使用验收风味应用程序(带检测类)运行您的Appium测试用例。 Before you call "Reset App" or "Close Application" make sure to call the following methods (just a draft, but I think you get the idea): 在调用“重置应用程序”或“关闭应用程序”之前,请确保调用以下方法(只是草稿,但我想您已经明白了):

// 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." );
  }
}

outputPath could be for example: /sdcard/Android/data/org.example.acceptance/files/coverage.ec outputPath可以是例如:/sdcard/Android/data/org.example.acceptance/files/coverage.ec

Now the Jacoco data is written to the Smartphone. 现在,Jacoco数据已写入智能手机。 Next we need to download that file. 接下来,我们需要下载该文件。 You can use 您可以使用

appiumDriver.pullFile( logFilePath );

Now you need to copy the file "jacoco-it.exec" (which should always be appended when you pull the file) into build/jacoco/jacoco-it.exec see gradle.build above and run 现在,您需要将文件“ jacoco-it.exec”(在拉出文件时始终附加 )复制到build / jacoco / jacoco-it.exec中,参见gradle.build并运行

gradlew sonarRunner

In SonarQube add the Integration Test Coverage Widget and you should see now some values... 在SonarQube中,添加集成测试覆盖率小部件,您现在应该看到一些值...

Unfortunately code coverage won't work if you are using retrolambda (as we do). 不幸的是,如果您使用的是Retrolambda,那么代码覆盖将无法正常工作(就像我们一样)。 Retrolambda will generate anonymous classes which are not part of the source files - so SonarQube cannot match them correctly and displays a much lower code coverage than it actually is. Retrolambda将生成不属于源文件的匿名类-因此SonarQube无法正确匹配它们,并且显示的代码覆盖率远低于其实际值。 If someone finds a solution for that, I would be very happy :-) 如果有人找到解决方案,我会很高兴的:-)

I solved this problem by adding broadcast receiver to the application you test! 我通过向您测试的应用程序添加广播接收器来解决了这个问题! (you can add the receiver only to debug folder cause no need it for to exist in main source) (您可以将接收器仅添加到调试文件夹,因为它不需要在主要源中存在)

 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());
    }
}
}

In manefist add (you can add this debug folder so it won't exist in main source) 在manefist add中(您可以添加此调试文件夹,以便它在主要源代码中不存在)

    <?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>

In the build.gradle of the application I added 在我添加的应用程序的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

        }
    }

you build your application as debug, than install and run it. 您将您的应用程序构建为调试版本,而不是安装并运行它。

send broadcast through ADB "adb shell am broadcast -a com.example.action" to create coverage.exec pull coverage from device - adb pull /mnt/sdcard/coverage.exec 通过ADB发送广播“ adb shell am广播-com.example.action”以从设备创建coverage.exec拉取覆盖范围-adb pull /mnt/sdcard/coverage.exec

after you run this you need to create the coverage from the file 运行此文件后,您需要从文件创建coverage

   **
 * 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')
}

this task is one way to create coverage files in coverageSourceDirs add all the locations of your applicaiton source code, so it will know which code to take and create coverage based on them executionData is the location where you put the coverage.exec you pulled from the device 此任务是一种在coverageSourceDirs中创建coverage文件的方法,它会添加应用程序源代码的所有位置,因此它将知道要采用的代码并基于它们创建Coverage。executionData是放置coverage.exec的位置。设备

Run the task the files will created for html and xml you can also add csv (notice it will be create in the build folder of the application)! 运行将为html和xml创建的文件的任务,您也可以添加csv(注意它将在应用程序的build文件夹中创建)!

Need to know, you must run the task against the same code you built your application debug version 需要知道的是,您必须针对与构建应用程序调试版本相同的代码运行任务

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

相关问题 Android studio(仪器仪表)的Gradle测试 - Gradle tests for Android studio (instrumentation) 结合Android工具测试和后端集成测试 - Combine Android instrumentation tests and backend integration tests 在 Jenkins (Gradle) 上运行 android 单元测试和仪器测试 - Run android unit tests & instrumentation tests on Jenkins (Gradle) 单元/仪器测试Android Gradle。 仪器测试不会运行 - Unit/Instrumentation tests Android Gradle. Instrumentation tests won't run 如何使用Gradle运行一组特定的Android检测测试? - How can I run a specific set of Android instrumentation tests with Gradle? 有没有办法只在 Android Gradle 项目中运行一组特定的仪器测试? - Is there a way to only run a specific set of instrumentation tests in an Android Gradle project? Android Gradle - SonarCloud 与 Jacoco 插件集成,覆盖率始终为 0.0% - Android Gradle - SonarCloud Integration with Jacoco Plugin, coverage is always 0.0% Jacoco和单元测试代码覆盖率使用android-gradle-plugin&gt; = 1.1 - Jacoco and Unit Tests Code Coverage with android-gradle-plugin >= 1.1 android jacoco 覆盖率在 gradle 中显示为 0%,但是有 95% 的测试覆盖了代码 - android jacoco coverage shows 0% with gradle however there are 95% tests covering code 风味和仪器测试-Gradle配置 - Flavors and instrumentation tests - gradle configuration
 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM