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

时间:2015-06-22 09:23:30

标签: android gradle offline jacoco test-coverage

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

甚至jacoco gradle插件的文档都说:

  

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

请参阅https://docs.gradle.org/current/userguide/jacoco_plugin.html

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

由于 本

2 个答案:

答案 0 :(得分:1)

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

为您的buildType启用检测并相应地配置SonarQube e.g。

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

然后使用Accept flavor app(带有检测类)运行Appium测试用例。在你打电话之前#34;重置应用程序&#34;或&#34;关闭申请&#34;确保调用以下方法(只是草稿,但我认为你明白了):

// 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可以是例如:/sdcard/Android/data/org.example.acceptance/files/coverage.ec

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

appiumDriver.pullFile( logFilePath );

现在你需要复制文件&#34; jacoco-it.exec&#34; (当你拉文件时应该追加)到build / jacoco / jacoco-it.exec中,请参阅上面的gradle.build并运行

gradlew sonarRunner

在SonarQube中添加Integration Test Coverage Widget,你现在应该看到一些值......

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

答案 1 :(得分:0)

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

 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

运行此操作后,您需要从文件

创建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')
}

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

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

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