zoukankan      html  css  js  c++  java
  • Android-jacoco代码覆盖率:单元测试覆盖率+功能测试覆盖率

    参考:https://docs.gradle.org/current/dsl/org.gradle.testing.jacoco.tasks.JacocoCoverageVerification.html

    gradle库下载:https://maven.aliyun.com/mvn/view

    案例参考来源:https://www.jianshu.com/p/1a4a81f09526

    https://www.jianshu.com/p/1a4a81f09526

    其他:https://testerhome.com/topics/8329

    这几天折腾了很久,主要是现在的案例都是基于gradle3.1.3版本,我不想用旧版本的,查了一些资料,自己改了下代码,可以用了。

    前情:

    之前听说Android可以用jacoco+monkey做代码覆盖率测试,以前只做过一个spring的jacoco的单元测试覆盖率的demo,没想过Android可以将功能和jacoco联合在一起,这几天很闲就搞了一下。

    准备工作:

    要有Android项目源码,不用修改项目主体的核心代码,但是需要写一些jacoco的代码,主要是利用instrument在acitivity结束时记录代码覆盖率;

    具体内容分两块:

    一,Android项目的单元测试代码覆盖率:

    利用AndroidStudio自带的task来查看当前AndroidTest文件夹下的单元测试用例覆盖率情况

    编辑build.gradle

    android {
        ...
        defaultConfig {
             ...
            testInstrumentationRunnerArguments clearPackageData: 'true'
    //        执行instrumentation测试时清除缓存
        }
        buildTypes {
            debug {
                testCoverageEnabled = true
                /**打开覆盖率统计开关
                 */
            }
        }
    

      

    安装debug包

    执行AndroidTest的覆盖率测试并输出报告

    执行日志是这样的:

      这里摘取的是执行AndroidTest单元测试的片段,通过adb发送instrument命令到手机,执行测试,获取覆盖率数据,并从手机中down下来:

    Executing tasks: [createDebugAndroidTestCoverageReport]
    ...
    > Task :app:connectedDebugAndroidTest
    ...
    05:40:39 V/ddms: execute: running am instrument -w -r   -e coverageFile /data/data/com.patech.testApp/coverage.ec -e coverage true -e clearPackageData true com.patech.testApp.test/androidx.test.runner.AndroidJUnitRunner
    ...
    05:40:41 V/InstrumentationResultParser: com.patech.testApp.EspressoTest:
    ...
    05:40:58 V/InstrumentationResultParser: Time: 17.669
    05:40:58 V/InstrumentationResultParser: 
    05:40:58 V/InstrumentationResultParser: OK (5 tests)
    05:40:58 V/InstrumentationResultParser: 
    05:40:58 V/InstrumentationResultParser: 
    05:40:58 V/InstrumentationResultParser: Generated code coverage data to /data/data/com.patech.testApp/coverage.ec
    05:40:58 V/InstrumentationResultParser: INSTRUMENTATION_CODE: -1
    ...
    05:40:59 I/XmlResultReporter: XML test result file generated at D:androidStudioMyApplicationappuildoutputsandroidTest-resultsconnectedTEST-VOG-AL10 - 9-app-.xml. Total tests 5, passed 5, 
    05:40:59 V/ddms: execute 'am instrument -w -r   -e coverageFile /data/data/com.patech.testApp/coverage.ec -e coverage true -e clearPackageData true com.patech.testApp.test/androidx.test.runner.AndroidJUnitRunner' on 'APH0219430006864' : EOF hit. Read: -1
    ...
    05:40:59 D/com.patech.testApp.coverage.ec: Downloading com.patech.testApp.coverage.ec from device 'APH0219430006864'
    ...
    

      

    执行完毕后查看build下的reports的详情

     

    二.编写jacoco+instrument的代码,执行功能测试后,在本地生成ec文件,传到pc端后解析成html格式,查看功能测试操作的代码覆盖率执行情况

    1.编写FinishListener接口

    public interface FinishListener {
        void onActivityFinished();
        void dumpIntermediateCoverage(String filePath);
    }
    

      

    编写jacocoInstrumentation方法,实现上面这个接口,网上抄来的,实现了执行完成后生成覆盖率文件并保存到手机本地:

    package com.patech.test;
    
    import android.app.Activity;
    import android.app.Instrumentation;
    import android.content.Intent;
    import android.os.Bundle;
    import android.os.Looper;
    import android.util.Log;
    
    import com.patech.testApp.InstrumentedActivity;
    
    import java.io.File;
    import java.io.FileNotFoundException;
    import java.io.FileOutputStream;
    import java.io.IOException;
    import java.io.OutputStream;
    import java.lang.reflect.InvocationTargetException;
    
    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;
        //LOGD 调试用布尔
        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);
            //DEFAULT_COVERAGE_FILE_PATH = getContext().getFilesDir().getPath() + "/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) {
                mCoverageFilePath = arguments.getString("coverageFile");
            }
    
            mIntent = new Intent(getTargetContext(), InstrumentedActivity.class);
            mIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
            start();
        }
    
        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 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 (FileNotFoundException e) {
                Log.d(TAG, e.toString(), e);
            } catch (IOException e) {
                e.printStackTrace();
            } catch (InvocationTargetException e) {
                e.printStackTrace();
            } catch (NoSuchMethodException e) {
                e.printStackTrace();
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            } catch (ClassNotFoundException e) {
                e.printStackTrace();
            } finally {
                if (out != null) {
                    try {
                        out.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            }
        }
    
        public void UsegenerateCoverageReport() {
            generateCoverageReport();
        }
    
        private boolean setCoverageFilePath(String filePath){
            if (filePath != null && filePath.length() > 0) {
                mCoverageFilePath = filePath;
            }
            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_IDENTIFIER,"
    Error: " + msg);
        }
    
        @Override
        public void onActivityFinished() {
            if (LOGD) {
                Log.d(TAG,"onActivityFinished()");
            }
            finish(Activity.RESULT_OK,mResults);
        }
    
        @Override
        public void dumpIntermediateCoverage(String filePath) {
            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);
            }
        }
    }
    

      

    2.修改AndroidManifest.xml文件,添加往手机读写的权限,以及instrument的设置,该标签与application标签同级:

        <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
        <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" /> <!-- Jacoco权限 -->
        <uses-permission android:name="android.permission.USE_CREDENTIALS" />
        <uses-permission android:name="android.permission.GET_ACCOUNTS" />
        <uses-permission android:name="android.permission.READ_PROFILE" />
        <uses-permission android:name="android.permission.READ_CONTACTS" />
    
        <instrumentation
            android:name="com.patech.test.JacocoInstrumentation"
            android:handleProfiling="true"
            android:label="CoverageInstrumentation"
            android:targetPackage="com.patech.testApp" />
    

    3.编写jacoco.gradle,用于解析ec,转换成html或者其他格式的报告:

    apply plugin: 'jacoco'
    //https://docs.gradle.org/current/userguide/jacoco_plugin.html
    jacoco {
        toolVersion = "0.8.4"
    }
    task jacocoTestReport(type: JacocoReport) {
        group = "Reporting"
        description = "Generate Jacoco coverage reports after running tests."
        def debugTree = fileTree(dir: "${buildDir}/intermediates/javac/debug",
    //            includes: ["**/*Presenter.*"],
                excludes: ['**/R*.class',
                           '**/*$InjectAdapter.class',
                           '**/*$ModuleAdapter.class',
                           '**/*$ViewInjector*.class'
                ])//指定类文件夹、包含类的规则及排除类的规则,这里我们生成所有Presenter类的测试报告
        def coverageSourceDirs = "${project.projectDir}/src/main/java" //指定源码目录
        def reportDirs="$buildDir/outputs/reports/jacoco/jacocoTestReport"
        reports {
            xml.enabled = true
            html.enabled = true
        }
    //    destinationFile=file(reportDirs)
        classDirectories = files(debugTree)
        sourceDirectories = files(coverageSourceDirs)
        executionData = files("$buildDir/outputs/code-coverage/connected/coverage.ec")
    }
    

    4.连接手机,安装apk后执行adb语句,通过jacoco开启应用:

    adb shell am instrument -w -r  com.patech.testApp/com.patech.testcoverage.test.JacocoInstrumentation
    

    5.可以在手机上开始做功能测试了,测试完毕后导出ec文件:

    adb pull mnt/sdcard/coverage.ec C:UsersuserDesktop	estReportjacoco
    

    6.将ec文件放入build/outputs/code-coverage/connected下

     执行jacocoTestReport的task

     在build/reports/jacoco/jacocoTestReport下查看解析的报告

    查看报告:

     

      

  • 相关阅读:
    第十一次作业
    第十次作业
    第九次作业
    第八次作业
    第七次作业
    第六次作业
    第五次作业
    java第三次作业
    Java第二次作业
    Java第一次作业
  • 原文地址:https://www.cnblogs.com/zhizhiyin/p/11493392.html
Copyright © 2011-2022 走看看