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