8

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

甚至 jacoco gradle 插件的文档说:

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

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

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

谢谢本

4

2 回答 2

2

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

为您的 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 无法正确匹配它们,并且显示的代码覆盖率比实际低得多。如果有人找到解决方案,我会很高兴:-)

于 2015-10-01T11:16:17.333 回答
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

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

   **
 * 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 文件夹中创建)!

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

于 2016-08-03T06:05:12.650 回答