之前已经写过一篇关于 [instrument 方式] Jacoco 统计 Android 端手工测试覆盖率,但是在实际使用过程中,自由度不够。
针对不同的测试类型,可能需要不同的写覆盖率文件触发方式。所以才有了本篇:修改源码的方式,定制化写代码覆盖率文件的触发条件。
看下文之前,首先考虑一个问题:把大象放进冰箱,一共分几步?
......
同样的,用 Jacoco 统计 Android 代码覆盖率,一共分 6 步:
src 目录下,新建一个 jacocotest package,放入 JacocoUtils.java 测试类
代码见:
package com.keniu.security.main.jacocotest;
import android.app.AlarmManager;
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import android.util.Log;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
public class JacocoUtils {
    static String TAG = "JacocoUtils";
    //ec文件的路径
    private static String DEFAULT_COVERAGE_FILE_PATH = "/mnt/sdcard/cleanmaster_coverage.ec";
    /**
     * 生成ec文件
     *
     * @param isNew 是否重新创建ec文件
     */
    public static void generateEcFile(boolean isNew) {
        String currentTimeStr = String.valueOf(System.currentTimeMillis());
//        DEFAULT_COVERAGE_FILE_PATH = "/mnt/sdcard/cleanmaster_coverage" + currentTimeStr + ".ec";
        Log.d(TAG, "生成覆盖率文件: " + DEFAULT_COVERAGE_FILE_PATH);
        OutputStream out = null;
        File mCoverageFilePath = new File(DEFAULT_COVERAGE_FILE_PATH);
        //create and delete coverage.ec
        try {
            if (isNew && mCoverageFilePath.exists()) {
                Log.d(TAG, "JacocoUtils_generateEcFile: 清除旧的ec文件");
//                mCoverageFilePath.delete();
            }
            if (!mCoverageFilePath.exists()) {
                mCoverageFilePath.createNewFile();
            }
            out = new FileOutputStream(mCoverageFilePath.getPath(), true);
            //反射:获取org.jacoco.agent.rt.IAgent
            Object agent = Class.forName("org.jacoco.agent.rt.RT"   )
                    .getMethod("getAgent")
                    .invoke(null);
            //反射:getExecutionData(boolean reset),获取当前执行数据,以jacoco二进制格式转储当前执行数据
            // getExecutionData(boolean reset),reset如果为true,则之后清除当前执行数据
            out.write((byte[]) agent.getClass().getMethod("getExecutionData", boolean.class)
                    .invoke(agent, false))  ;
        } catch (Exception e) {
            Log.e(TAG, "generateEcFile: " + e.getMessage());
        } finally {
            if (out == null)
                return;
            try {
                out.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}
apply plugin: 'jacoco'
jacoco {
    toolVersion = '0.7.4+'
}
如图所示:
说明:也可选择 release 版本打开覆盖率开关。
buildTypes {
    debug {
        /**打开覆盖率统计开关*/
 testCoverageEnabled = true
    }
}
如图所示:
触发条件可以根据覆盖率统计场景,选择合适的一个。
线程代码见:
package com.keniu.security.main.jacocotest.handler;
import android.os.Handler;
import android.os.Message;
import com.keniu.security.main.jacocotest.JacocoUtils;
public class MyThread implements Runnable {
    Handler handler = new Handler() {
        public void handleMessage(Message msg) {
            // 要做的事情
            JacocoUtils.generateEcFile(true);
        }
    };
    boolean jacocoBoolean = true;
    @Override
    public void run() {
        // TODO Auto-generated method stub
        while (jacocoBoolean) {
            try {
                Thread.sleep(10000);// 线程暂停10秒,单位毫秒
                Message message = new Message();
                message.what = 1;
                handler.sendMessage(message);// 发送消息
            } catch (InterruptedException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
        }
    }
    public void end() {
        jacocoBoolean = false;
    }
}
在 MainActivity 中新建线程并启动:
MyThread jacocoThread = new MyThread();
@Override
protected void onCreate(Bundle savedInstanceState) {
    ......
    //jacoco定时任务开始
 new Thread(jacocoThread).start();
   ......      
}
@Override
public boolean onKeyDown(int keyCode, KeyEvent event) {
if (keyCode == KeyEvent.KEYCODE_BACK) {
....
JacocoUtils.generateEcFile(true);
}
}
build / Rebuild Project
安装 app-debug.apk 至手机
def coverageSourceDirs = [
        './src/'
]
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/',
            excludes: ['**/R*.class',
                       '**/*$InjectAdapter.class',
                       '**/*$ModuleAdapter.class',
                       '**/*$ViewInjector*.class'
 ])
    sourceDirectories = files(coverageSourceDirs)
    executionData = files("$buildDir/outputs/cleanmaster_coverage.ec")
    doFirst {
        new File("$buildDir/intermediates/classes/").eachFileRecurse { file ->
            if (file.name.contains('$$')) {
                file.renameTo(file.path.replace('$$', '$'))
            }
        }
    }
}
然后设备上传 ec 文件到 Android 工程的 $buildDir/outputs/code-coverage/connected 目录下,并依次执行
gradle createDebugCoverageReport
gradle jacocoTestReport
解决方法:
按照如下方式设置即可:
debug {
    buildConfigField "boolean", "FORTEST", "false"
 // 启动代码压缩
 minifyEnabled true
 // 启用资源压缩
 shrinkResources true
 signingConfig signingConfigs.debug
    proguardFiles getDefaultProguardFile('proguard-android.txt'), 'kbrowser.pro'
 /**打开覆盖率统计开关*/
 testCoverageEnabled = true
}
如图所示:
    
     