Jacoco-精准测试
开源地址:https://github.com/seasonxie/JacocoAndroid
不依赖源码执行
接口测试也能直观看出覆盖度
知道什么样的用例保证哪块的代码,更好的精准度测试
1.实现jacoco Instrumentation操作(后面通过命令直接启动该instrument,最下面有),注意最后启动了 InstrumentedActivity
public class JacocoInstrumentation extends Instrumentation
@Override
public void onCreate(Bundle arguments) {
super.onCreate(arguments);
DEFAULT_COVERAGE_FILE_PATH = "/sdcard/cover.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();
}
2.在InstrumentedActivity onDestroy设置监听,当触发的时候触发生成jacoco覆盖率文件
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/> 权限
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();
}
}
}
3.触发监听的操作
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();
}
}
}
}
启动:
2.1注册instrumentation,通过instrumentation启动被测应用
adb shell am instrument com.jacs.zhaotang.jscscs/test.JacocoInstrumentation
<instrumentation
android:handleProfiling="true"
android:label="CoverageInstrumentation"
android:name="test.JacocoInstrumentation"
android:targetPackage="com.jacs.zhaotang.jscscs"/>
3.把生成的jacoco覆盖率文件放在指定文件夹,用gradle生成htmlreport
将该文件拖至入app根目录/build/outputs/code-coverage/connected下(文件夹没有的话可新建)
运行gradlew jacocoTestReport
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)
executionData = files("$buildDir/outputs/code-coverage/connected/coverage.ec")
doFirst {
new File("$buildDir/intermediates/classes/").eachFileRecurse { file ->
if (file.name.contains('$$')) {
file.renameTo(file.path.replace('$$', '$'))
}
}
}
}
4.report路径:jscscsappuild eportsjacocojacocoTestReport
通过广播驱动生成ec:
1.注册广播
public void onReceive(Context context, Intent intent) {
String url = intent.getStringExtra("url");
if (Constants.mListener != null) {
Constants.mListener.onActivityFinished();
Toast.makeText(context,"notnull", 0).show();
}else{
Toast.makeText(context,url, 0).show();
}
}
<receiver android:name="test.operationReceiver" android:enabled="true" android:exported="true">
<intent-filter>
<action android:name="com.jacs.zhaotang.jscscs.operationReceiver" />
</intent-filter>
</receiver>
发送广播:adb shell am broadcast -a com.jacs.zhaotang.jscscs.operationReceiver --es url 'sdsd'
解析xml:
需要提取的覆盖率类型
public static final String INSTRUCTION="INSTRUCTION";
public static final String BRANCH="BRANCH";
public static final String LINE="LINE";
public static final String COMPLEXITY="COMPLEXITY";
public static final String METHOD="METHOD";
public static final String[] COVERES={INSTRUCTION};
// 获取当前节点的所有属性节点
List<Attribute> list = node.attributes();
// 遍历属性节点
for (Attribute attr : list) {
if (node.getName().equals("counter")) { // 节点等于 counter
if (needCollect(attr.getText()) || is) { // 当属性等于INSTRUCTION拿出covered // INSTRUCTION // 拿出该covered
is = true;
}
if (attr.getName().equals("covered") && is) {
String p=getFristAttributeText(node.getParent());
data += node.getParent().getName() + " - "
+ p + " --- "
+ currentCovered+"-"+attr.getName() + " ----- " + attr.getText()
+ " ";
is = false;
break;
}
}
}
获取A和B两次代码的覆盖率
method - unRegisterObserver --- INSTRUCTION-covered ----- 14 method - getLastLocation --- INSTRUCTION-covered ----- 18 method - setCachedLocation --- INSTRUCTION-covered ----- 0 method - getCachedLocation --- INSTRUCTION-covered ----- 11 method - setCachedLocationTime --- INSTRUCTION-covered ----- 0 method - getCacheLocationTime --- INSTRUCTION-covered ----- 11 method - hasCachedLocation --- INSTRUCTION-covered ----- 10 method - <init> --- INSTRUCTION-covered ----- 6 method - onReceive --- INSTRUCTION-covered ----- 10 Spent 785 //消耗时间
打印A/B两次的不同覆盖值
A: class - com/android/browser/AutoLoginLinearLayout --- INSTRUCTION-covered ----- 20 B: class - com/android/browser/AutoLoginLinearLayout --- INSTRUCTION-covered ----- 0