加入代码覆盖率的文件
从参考文章中下载文件,解压缩后有4个文件:
. ├── EmmaInstrumentation.java ├── FinishListener.java ├── InstrumentedActivity.java └── SMSInstrumentedReceiver.java |
提取前三个文件加入到我们的源码中,我们的包名为com.wuba.wuxian.android_0504,代码覆盖率的三个文件存放在com.wuba.wuxian.test 包中。
然后对三个文件做相应的修改,修改后的文件内容如下:
FinishListener(无修改):
public interface FinishListener { void onActivityFinished(); void dumpIntermediateCoverage(String filePath); } |
JacocoInstrumentation(原EmmaInstrumentation文件):
public class JacocoInstrumentation extends Instrumentation implements FinishListener { public static String TAG = "JacocoInstrumentation:"; private static String DEFAULT_COVERAGE_FILE_PATH = "/mnt/sdcard/coverage.ec"; private final Bundle mResults = new Bundle(); private Intent mIntent; private static final boolean LOGD = true; private boolean mCoverage = true; private String mCoverageFilePath; /** * Constructor */ public JacocoInstrumentation() { } @Override public void onCreate(Bundle arguments) { Log.d(TAG, "onCreate(" + arguments + ")"); super.onCreate(arguments); 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, "异常 : " + e); e.printStackTrace(); } } if (arguments != null) { //mCoverage = getBooleanArgument(arguments, "coverage"); mCoverageFilePath = arguments.getString("coverageFile"); } mIntent = new Intent(getTargetContext(), InstrumentedActivity.class); mIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); start(); } @Override public void onStart() { if (LOGD) Log.d(TAG, "onStart()"); super.onStart(); Looper.prepare(); InstrumentedActivity activity = (InstrumentedActivity) startActivitySync(mIntent); activity.setFinishListener(this); } private boolean getBooleanArgument(Bundle arguments, String tag) { String tagString = arguments.getString(tag); return tagString != null && Boolean.parseBoolean(tagString); } 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(); } } } } private String getCoverageFilePath() { if (mCoverageFilePath == null) { return DEFAULT_COVERAGE_FILE_PATH; } else { return mCoverageFilePath; } } private boolean setCoverageFilePath(String filePath){ if(filePath != null && filePath.length() > 0) { mCoverageFilePath = filePath; return true; } return false; } private void reportEmmaError(Exception e) { reportEmmaError("", e); } private void reportEmmaError(String hint, Exception e) { String msg = "Failed to generate emma coverage. " + hint; Log.e(TAG, msg, e); mResults.putString(Instrumentation.REPORT_KEY_STREAMRESULT, "\nError: " + msg); } @Override public void onActivityFinished() { if (LOGD) Log.d(TAG, "onActivityFinished()"); if (mCoverage) { generateCoverageReport(); } finish(Activity.RESULT_OK, mResults); } @Override public void dumpIntermediateCoverage(String filePath){ // TODO Auto-generated method stub if(LOGD){ Log.d(TAG,"Intermidate Dump Called with file name :"+ filePath); } if(mCoverage){ if(!setCoverageFilePath(filePath)){ if(LOGD){ Log.d(TAG,"Unable to set the given file path:"+filePath+" as dump target."); } } generateCoverageReport(); setCoverageFilePath(DEFAULT_COVERAGE_FILE_PATH); } } } |
InstrumentedActivity:
public class InstrumentedActivity extends MainActivity { public static String TAG = "InstrumentedActivity"; private FinishListener mListener; public void setFinishListener(FinishListener listener) { mListener = listener; } @Override public void onDestroy() { Log.d(TAG + ".InstrumentedActivity", "onDestroy()"); super.finish(); if (mListener != null) { mListener.onActivityFinished(); } } } |
修改AndroidManifest.xml
<?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.wuba.wuxian.android_0504" > <!-- To access Google+ APIs: --> <uses-permission android:name="android.permission.INTERNET" /> <!-- To retrieve OAuth 2.0 tokens or invalidate tokens to disconnect a user. This disconnect option is required to comply with the Google+ Sign-In developer policies --> <uses-permission android:name="android.permission.USE_CREDENTIALS" /> <!-- To retrieve the account name (email) as part of sign-in: --> <uses-permission android:name="android.permission.GET_ACCOUNTS" /> <!-- To auto-complete the email text field in the login form with the user's emails --> <uses-permission android:name="android.permission.READ_PROFILE" /> <uses-permission android:name="android.permission.READ_CONTACTS" /> <application android:allowBackup="true" android:icon="@mipmap/ic_launcher" android:label="@string/app_name" android:theme="@style/AppTheme" > <activity android:name=".MainActivity" android:label="@string/app_name" > <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity> <meta-data android:name="com.google.android.gms.version" android:value="@integer/google_play_services_version" /> <activity android:name=".GoActivity" android:label="@string/title_activity_go" > </activity> <activity android:label="InstrumentationActivity" android:name="com.wuba.wuxian.test.InstrumentedActivity" /> </application> <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/> <instrumentation android:handleProfiling="true" android:label="CoverageInstrumentation" android:name="com.wuba.wuxian.test.JacocoInstrumentation" android:targetPackage="com.wuba.wuxian.android_0504"/> </manifest> |
执行
首先构建APK,安装到手机上,默认android会打开应用,我们需要关闭,再通过一下命令打开应用:
adb shell am instrument com.wuba.wuxian.android_0504/com.wuba.wuxian.test.JacocoInstrumentation
打开应用后,你可以进行测试了
测试完成后,获得代码覆盖率数据
测试完成后,我们要收集代码覆盖率的数据了。其实就是要生成一个jacoco的文件,在本次实验中,我们定义的jacoco文件问coverage.ec文件,存放在应用数据目录下files文件夹下,而且生成数据的操作放在应用退出时。
你需要将这个coverage.ec文件dump到本地,然后利用代码结构生成报告,原生gradle-android-plugin也是这样一个流程。所以怎么生成报告我就不在详细介绍了。
总结
上面的方式是在不修改源码环境下做的一个类似于后门的方式获得代码覆盖率数据的方式,但是如果你想更加方便的去做这个,比如在应用中长按生成数据,你可以修改自己的源码,在长按事件中调用JacocoInstrumentation中generateCoverageReport方法就可以了。
上文内容不用于商业目的,如涉及知识产权问题,请权利人联系博为峰小编(021-64471599-8017),我们将立即处理。