首页 文章

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

提问于
浏览
7

我们正在构建一个使用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进行进一步分析 .

谢谢Ben

2 回答

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

    然后使用Accept flavor app(带有检测类)运行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." );
      }
    }
    

    outputPath可以是例如:/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中添加Integration Test Coverage Widget,您现在应该看到一些值...

    Unfortunately code coverage won't work if you are using retrolambda (as we do). 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. If someone finds a solution for that, I would be very happy :-)

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

    在我添加的应用程序的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中创建覆盖文件的一种方法,添加应用程序源代码的所有位置,因此它将知道要采用哪些代码并根据它们创建覆盖执行dataData是您放置coverage的c的位置 . 设备

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

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

相关问题