Android Gradle Jacoco: offline instrumentation for integration tests

后端 未结 2 1603
北海茫月
北海茫月 2020-12-31 16:59

we are building an Android app which is tested by using Appium. Now I would like to see the test coverage of our Appium tests. I think this is possible, because Jacoco suppo

2条回答
  •  攒了一身酷
    2020-12-31 17:42

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

    add the following to your AndroidManifest.xml

    
     
        
     
    
    

    CoverageDataDumper should look like that:

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

    Now the Jacoco data is written to the Smartphone. 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

    gradlew sonarRunner
    

    In SonarQube add the Integration Test Coverage Widget and you should see now some values...

    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 :-)

提交回复
热议问题