Appium Appium Android Coverage Guide

恒温 for Appium 中文文档小组 · August 23, 2017 · Last by pluie-cloudy replied at September 29, 2017 · 1381 hits
本帖已被设为精华帖!

Android Coverage Guide

Requirement

  • Only Emulators or rooted phones are acceptable
  • Need to add UiAutomator2 support in your apps. That is to say, you need to implement a subclass of Instrumentation. Instrumentation will be responsible to collect your coverage.
  • Need to implement a BroadcastReceiver to export coverage to disk files. This is because Instrumentation only collects data into memory.

Project Structure

You may need the following structure of files:

src/main/java/com/example/pkg
|____ MainActivity.java Your main activity
|____ InstrumentActivityListener.java A customized interface for exporting coverage to files
|____ InstrumentActivity.java Activity launched for coverage. But in most cases, this should be same as MainActivity. The only difference is that it will include a `InstrumentActivityListener` to export coverage data to disk files.
|____ JacocoInstrumentation.java The instrument class created by you. Also it needs to implement `InstrumentActivitylistener`.
|____ EndEmmaBroadCast.java A broadcast receiver which will be invoked by appium-uiautomator2-driver at the end of testing. You need implementing logic to invoke InstrumentActivityListener so as to export coverage to files.

Configure followings in your caps:

  • automationName : uiautomator2 (case irrelevant)
  • androidCoverage : {package}/{instrumentation class}, in our example, com.example.pkg/com.example.pkg.JacocoInstrumentation
  • appWaitActivity : the FQCN of the activity of InstrumentActivity, in our example, com.example.pkg.InstrumentActivity
  • appWaitPackage : {package},in our example, com.example.pkg
    androidCoverageEndIntent : The action of the broadcast receiver to invoke the exporting of coverage data to files, in our example com.example.pkg.END_EMMA

Methodology

Appium (appium-uiautomator2-driver) will launch app via command like:adb shell am instrument -e coverage true -w com.example.pkg/com.example.pkg.JacocoInstrumentation

After testing is done, Appium (appium-uiautomator2-driver) will execute adb shell am broadcast -a com.example.pkg.END_EMMA to export coverage to files(If you implement such export in the broadcast receiver)

Example

[1] Appium Testing Project - Configure Caps

Please refer to ** "Project Structure" -> "Configure followings in your caps" **

[2] Android Project

Define instrumentation class and broadcast receiver in AndroidManifest.xml:

<instrumentation
android:name="com.example.pkg.instrumentation.JacocoInstrumentation"
android:targetPackage="com.example.pkg" >
</instrumentation>

<!-- adb shell am broadcast -a com.example.pkg.END_EMMA -->
<receiver android:name="com.example.pkg.EndEmmaBroadcast" >
<intent-filter>
<action android:name="com.example.pkg.END_EMMA" />
</intent-filter>
</receiver>

Then, EndEmmaBroadcast.java

package com.example.pkg;

import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.os.Process;

// adb shell am broadcast -a com.example.pkg.END_EMMA
public class EndEmmaBroadcast extends BroadcastReceiver {
InstrumentActivityListener activityListener;

public void setInstrumentActivityListener(InstrumentActivityListener listener){
this.activityListener = listener;
}
@Override
public void onReceive(Context context, Intent intent) {
if(this.activityListener!=null){
activityListener.onActivityEnd();
}
// once coverage is dumped, the processes is ended.
Process.killProcess(Process.myPid());
}
}

After that, JacocoInstrumentation.java

package com.example.pkg;

import android.app.Activity;
import android.app.Instrumentation;
import android.content.Intent;
import android.content.IntentFilter;
import android.os.Bundle;
import android.os.Looper;
import android.util.Log;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;

public class JacocoInstrumentation extends Instrumentation implements InstrumentActivityListener {
public static String TAG = "JacocoInstrumentation:";
private static String DEFAULT_COVERAGE_FILE_PATH = null;
private final Bundle mResults = new Bundle();
private Intent mIntent;
private static final boolean LOGD = true;
private boolean mCoverage = true;
private String mCoverageFilePath;

public JacocoInstrumentation() {
}
@Override
public void onCreate(Bundle arguments) {
Log.d(TAG, "onCreate(" + arguments + ")");
super.onCreate(arguments);
// bad notation, better use NAME+TimeSeed because you might generate more than 1 corage file
DEFAULT_COVERAGE_FILE_PATH = getContext().getFilesDir().getPath().toString() + "/coverage.ec";
File file = new File(DEFAULT_COVERAGE_FILE_PATH);
if(!file.exists()){
try{
file.createNewFile();
}catch (IOException e){
Log.d(TAG,"File Exception :"+e);
e.printStackTrace();}
}
if(arguments != null) {
mCoverageFilePath = arguments.getString("coverageFile");
}
mIntent = new Intent(getTargetContext(), InstrumentActivity.class);
mIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
start();
}
@Override
public void onStart() {
super.onStart();
Looper.prepare();
// Register broadcast receiver and start InstrumentActivity
InstrumentActivity activity = (InstrumentActivity) startActivitySync(mIntent);
EndEmmaBroadcast broadcast = new EndEmmaBroadcast();
activity.setInstrumentActivityListener(this);
broadcast.setInstrumentActivityListener(this);
activity.registerReceiver(broadcast, new IntentFilter("com.example.pkg.END_EMMA"));
}
private String getCoverageFilePath() {
if (mCoverageFilePath == null) {
return DEFAULT_COVERAGE_FILE_PATH;
} else {
return mCoverageFilePath;
}
}
private void generateCoverageReport() {
Log.d(TAG, "generateCoverageReport():" + getCoverageFilePath());
OutputStream out = null;
try {
out = new FileOutputStream(getCoverageFilePath(), false);
Object agent = Class.forName("org.jacoco.agent.rt.RT")
.getMethod("getAgent")
.invoke(null);
out.write((byte[]) agent.getClass().getMethod("getExecutionData", boolean.class)
.invoke(agent, false));
} catch (Exception e) {
Log.d(TAG, e.toString(), e);
} finally {
if (out != null) {
try {
out.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}

@Override
public void onActivityEnd() {
if (LOGD) Log.d(TAG, "onActivityFinished()");
if (mCoverage) {
generateCoverageReport();
}
finish(Activity.RESULT_OK, mResults);
}
}

Then, InstrumentActivityListener.java

package com.example.pkg;

public interface InstrumentActivityListener {
void onActivityEnd();
}

InstrumentActivity.java (This is optional, you can use MainActivity)

package com.example.pkg;
import android.app.Instrumentation;
import android.os.Bundle;
import android.util.Log;


public class InstrumentActivity extends MainActivity {
public static String TAG = "IntrumentedActivity";
private InstrumentActivityListener listener;

public void setInstrumentActivityListener(InstrumentActivityListener listener) {
this.listener = listener;
}

// Generate output report when the activity is destroyed
@Override
public void onDestroy() {
super.onDestroy();
Log.d(TAG, "onDestroy()");
super.finish();
if (listener != null) {
listener.onActivityEnd();
}
}
}

Finally, the most important part is gradle:

....


apply plugin: 'jacoco' // add plugin for jacoco

...

android {
...
defaultConfig {
...
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
}

buildTypes {
release {
...
}
debug{
testCoverageEnabled = true
}
}
}

dependencies {
...
//uiautomator
androidTestCompile 'com.android.support.test.uiautomator:uiautomator-v18:2.1.0'
}

[3] Now, build apk and run Appium tests!

The coverage.ec will be generated at /data/data/com.example.pkg/files. Pull it out.

[4] About generating HTML reports

To get the HTML report of coverage.ec, you need following steps:

  • [1] pull it into file system by adb pull
  • [2] create the following task in your gradle file:
def coverageSourceDirs = [
'./src/main/java'
]

task jacocoTestReport(type: JacocoReport) {
group = "Reporting"
description = "Generate Jacoco coverage reports after running tests."
reports {
xml.enabled = true
html.enabled = true
}
classDirectories = fileTree(
dir: './build/intermediates/classes/debug',
excludes: ['**/R*.class',
'**/*$InjectAdapter.class',
'**/
*$ModuleAdapter.class',
'**/*$ViewInjector*.class'
])
sourceDirectories = files(coverageSourceDirs)
// NOTE: Put your ec file here
executionData = files("SOME PATH/coverage.ec")

doFirst {
new File("$buildDir/intermediates/classes/").eachFileRecurse { file ->
if (file.name.contains('$$')) {
file.renameTo(file.path.replace('$$', '$'))
}
}
}
}
附言 2  ·  August 24, 2017

Android Coverage Guide

Requirement

  • Only Emulators or rooted phones are acceptable
  • Need to add UiAutomator2 support in your apps. That is to say, you need to implement a subclass of Instrumentation. Instrumentation will be responsible to collect your coverage.
  • Need to implement a BroadcastReceiver to export coverage to disk files. This is because Instrumentation only collects data into memory.
附言 3  ·  August 24, 2017

Android Coverage Guide

Requirement

  • Only Emulators or rooted phones are acceptable
  • Need to add UiAutomator2 support in your apps. That is to say, you need to implement a subclass of Instrumentation. Instrumentation will be responsible to collect your coverage.
  • Need to implement a BroadcastReceiver to export coverage to disk files. This is because Instrumentation only collects data into memory.
如果觉得我的文章对您有用,请随意打赏。您的支持将鼓励我继续创作!
共收到 3 条回复 时间 点赞

Only Emulators or rooted phones are acceptable
😥

思寒_seveniruby 将本帖设为了精华贴 23 Aug 19:12

和之前一个帖子获取后post到服务器很像,但是不明白为什么要rooted devices

楼主 问个问题 请问下appium给wda发送请求的代码在哪个js里面呀 我现在自己用java模拟向wda发送http请求 遇到中文的就是乱码

simple 专栏文章:[精华帖] 社区历年精华帖分类归总 中提及了此贴 13 Dec 20:49
需要 Sign In 后方可回复, 如果你还没有账号请点击这里 Sign Up