美文网首页
[定制触发条件] jacoco 统计 Android 代码覆盖率

[定制触发条件] jacoco 统计 Android 代码覆盖率

作者: 测试在路上 | 来源:发表于2020-06-13 03:05 被阅读0次

    1、前言

    之前已经写过一篇关于 [instrument 方式] Jacoco 统计 Android 端手工测试覆盖率,但是在实际使用过程中,自由度不够。
    针对不同的测试类型,可能需要不同的写覆盖率文件触发方式。所以才有了本篇:修改源码的方式,定制化写代码覆盖率文件的触发条件。

    2、概述

    看下文之前,首先考虑一个问题:把大象放进冰箱,一共分几步?
    ......
    同样的,用Jacoco统计Android代码覆盖率,一共分6步:

    1. 编写生成覆盖率文件coverage.ec的类和方法
    2. build.gradle(app)新增jacoco插件
    3. 打开覆盖率统计开关
    4. 覆盖率生成条件监听
    5. 安装debug版本
    6. 服务器生成jacoco报告

    3、具体步骤

    3.1 编写生成覆盖率文件coverage.ec的类和方法

    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();
                }
            }
        }
    
    }
    

    3.2 build.gradle(app)新增jacoco插件

    apply plugin: 'jacoco'
    
    jacoco {
        toolVersion = '0.7.4+'
    }
    

    如图所示:

    image

    3.3 打开覆盖率统计开关

    说明:也可选择release版本打开覆盖率开关。

    buildTypes {
        debug {
            /**打开覆盖率统计开关*/
     testCoverageEnabled = true
        }
    }
    

    如图所示:

    image

    3.4 覆盖率生成条件监听

    触发条件可以根据覆盖率统计场景,选择合适的一个。

    (1)可新建线程定时触发

    线程代码见:

    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();
       ......      
    }
    

    (2)加在监听设备按键的地方,如果连续2次点击设备back键,app已置于后台,则调用生成覆盖率方法。

    @Override
    public boolean onKeyDown(int keyCode, KeyEvent event) {
        if (keyCode == KeyEvent.KEYCODE_BACK) {
            ....
            JacocoUtils.generateEcFile(true);
        }
    }
    

    3.5 安装debug版本

    build / Rebuild Project

    安装app-debug.apk至手机

    3.6 服务器生成jacoco报告

    (1)在build.gradle(project)新增jacocoTestReport

    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('$$', '$'))
                }
            }
        }
    }
    

    2)上传ec文件并生成报告

    然后设备上传ec文件到Android工程的$buildDir/outputs/code-coverage/connected目录下,并依次执行

    gradle createDebugCoverageReport
    gradle jacocoTestReport

    3.7 可能遇到问题

    (1)rebuild Project时卡在app:transformClassesWithDexForDebug

    解决方法:

    按照如下方式设置即可:

    debug {
        buildConfigField "boolean", "FORTEST", "false"
     // 启动代码压缩
     minifyEnabled true
     // 启用资源压缩
     shrinkResources true
     signingConfig signingConfigs.debug
        proguardFiles getDefaultProguardFile('proguard-android.txt'), 'kbrowser.pro'
     /**打开覆盖率统计开关*/
     testCoverageEnabled = true
    }
    

    如图所示:

    image

    相关文章

      网友评论

          本文标题:[定制触发条件] jacoco 统计 Android 代码覆盖率

          本文链接:https://www.haomeiwen.com/subject/qenotktx.html