• Android Jacoco 代码覆盖率测试入门 | 白盒测试


    本文作者 @XINXI ** ** 为 霍格沃兹测试学院优秀学员兼助教

    前言

    代码覆盖(Code Coverage)是软件测试中的一种度量,描述程式中源代码被测试的比例和程度,所得比例称为代码覆盖率。

    在做单元测试时,代码覆盖率常常被拿来作为衡量测试好坏的指标,甚至,用代码覆盖率来考核测试任务完成情况,比如,代码覆盖率必须达到 80% 或
    90%。于是乎,测试人员会费尽心思设计案例覆盖代码。

    关于代码覆盖率的意义,Martin Fowler
    大佬(《重构》作者)曾经写文章指出:把测试覆盖作为质量目标没有任何意义,但我们应该把它作为一种发现未被测试覆盖的代码的手段。

    正文

    最近同事搞了一个基于 Jacoco 统计 Android 代码覆盖率测试的功能,可以统计每天手工测试的代码覆盖率。抱着好奇的心态,自己也学习一下
    Jacoco ,陆陆续续搞了三天终于有点结果了。

    本文介绍仅仅在源码中加入少量代码就可以完成代码覆盖率覆测试.

    代码配置

    build.gradle

    在app目录下的 build.gradle 配置 jacoco

    apply plugin: 'jacoco'  
    jacoco {  
        toolVersion = "0.7.9"  
    }  
      
      
    dependencies {  
        compile fileTree(include: ['*.jar'], dir: 'libs')  
        androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', {  
            exclude group: 'com.android.support', module: 'support-annotations'  
        })  
        compile 'com.android.support:appcompat-v7:25.1.1'  
        compile 'org.jacoco:org.jacoco.core:0.7.9'  
        compile 'com.android.support.constraint:constraint-layout:+'  
    }  
      
    def coverageSourceDirs = [  
            '../app/src/main/java'  
    ]  
      
    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('$$', '$'))  
                }  
            }  
        }  
    }  
    

    写入 ec 文件

    自定义一个 JacocoUtils 类,可以根据反射拿到方法、类的执行代码,写入到 .ec 文件:

      public static void generateEcFile(boolean isNew) {  
    //        String DEFAULT_COVERAGE_FILE_PATH = NLog.getContext().getFilesDir().getPath().toString() + "/coverage.ec";  
            Log.d(TAG, "生成覆盖率文件: " + DEFAULT_COVERAGE_FILE_PATH);  
            OutputStream out = null;  
            File mCoverageFilePath = new File(DEFAULT_COVERAGE_FILE_PATH);  
            try {  
                if (isNew && mCoverageFilePath.exists()) {  
                    Log.d(TAG, "JacocoUtils_generateEcFile: 清除旧的ec文件");  
                    mCoverageFilePath.delete();  
                }  
                if (!mCoverageFilePath.exists()) {  
                    mCoverageFilePath.createNewFile();  
                }  
                out = new FileOutputStream(mCoverageFilePath.getPath(), true);  
      
                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));  
                Log.d(TAG,"写入" + DEFAULT_COVERAGE_FILE_PATH + "完成!" );  
            } catch (Exception e) {  
                Log.e(TAG, "generateEcFile: " + e.getMessage());  
                Log.e(TAG,e.toString());  
            } finally {  
                if (out == null)  
                    return;  
                try {  
                    out.close();  
                } catch (IOException e) {  
                    e.printStackTrace();  
      
                }  
            }  
        }  
    

    使用 Application 生成 ec

    继承 Application 类,重写 onTrimMemory 方法,系统会根据不同的内存状态来回调

    系统提供的回调有:  
    Application.onTrimMemory()  
    Activity.onTrimMemory()  
    Fragement.OnTrimMemory()  
    Service.onTrimMemory()  
    ContentProvider.OnTrimMemory()  
    OnTrimMemory的参数是一个int数值,代表不同的内存状态:  
    TRIM_MEMORY_COMPLETE:内存不足,并且该进程在后台进程列表最后一个,马上就要被清理  
    TRIM_MEMORY_MODERATE:内存不足,并且该进程在后台进程列表的中部。  
    TRIM_MEMORY_BACKGROUND:内存不足,并且该进程是后台进程。  
    TRIM_MEMORY_UI_HIDDEN:内存不足,并且该进程的UI已经不可见了。   
    

    可以根据

    level == TRIM_MEMORY_UI_HIDDEN

    来确定 App 已经至于后台,此时调用 generateEcFile 方法.

    //判断是否是后台  
    @Override  
    public void onTrimMemory(int level) {  
        super.onTrimMemory(level);  
        if (level == TRIM_MEMORY_UI_HIDDEN) {  
            isBackground = true;  
            notifyBackground();  
        }  
    }  
      
    private void notifyBackground() {  
        // This is where you can notify listeners, handle session tracking, etc  
        Log.d(TAG, "切到后台");  
        JacocoUtils.generateEcFile(true);  
    }  
    

    操作步骤

    给予app读写sdcard权限

    因为我的是简单的demo代码,启动没有弹窗询问读写sdcard权限,
    Android6.0以后是动态获取权限了,所以需要手动去设置中把sdcard权限打开,实际项目应该不存在手动打开的步骤.

    手工执行

    安装app->操作app->app至于后台->分析ec文件.

    自动化执行

    可以结合 monkey 和 UI 自动化,我简单写了个 shell 脚本.从编译 app、启动app、app 至于后台、自动展示 Jacoco 报告

    #!/usr/bin/env bash  
    #当前在环境为Project/app目录  
      
    apk_path=`pwd`/app/build/outputs/apk/app-debug.apk  
    report_path=`pwd`/reporter/index.html  
      
    echo "打包app"  
    gradle assembleDebug  
    adb uninstall com.weex.jasso  
    echo "安装app"  
    adb install ${apk_path}  
    echo "启动app"  
    adb shell am start -W -n com.weex.jasso/.Test1Activity -a android.intent.action.MAIN -c android.intent.category.LAUNCHER -f 0x10200000  
    sleep 2  
    echo "关闭app"  
    adb shell am force-stop com.weex.jasso  
      
    rm -rf `pwd`/new.ec  
    rm -rf `pwd`/report  
    adb pull /sdcard/jacoco/coverage.ec `pwd`/new.ec  
      
    macaca coverage -r java -f `pwd`/new.ec -c `pwd`/app/build/intermediates/classes/debug -s `pwd`/app/src/main/java --html `pwd`/reporter  
    echo "jacoco报告地址:"${report_path}  
    open -a "/Applications/Safari.app" ${report_path}  
    

    效果

    macaca coverage 生产报告

    使用gradle的jacocoTestReport也可以生产报告,也是大多人使用的方式,本文就不做介绍了,主要介绍使用macaca coverage方法.

    macaca
    coverage可以生成jacoco报告,不仅可以生成Android项目,也可以生产iOS、web项目.具体使用请查看https://macacajs.github.io/zh/coverage.

    安装macaca-coverage命令:  
      
    npm i macaca-cli -g  
    macaca coverage -h  
    npm i macaca-coverage --save-dev  
    
    
    
    macaca coverage命令:  
    macaca coverage -r java -f `pwd`/new.ec -c `pwd`/app/build/intermediates/classes/debug -s `pwd`/app/src/main/java --html `pwd`/reporter  
    

    项目代码

    https://github.com/xinxi1990/jacocodemo.git  
      
    在项目根目录有个jacaco_test.sh,可以完成自动化测试.  
    

    参考资料

    推荐阅读

    霍格沃兹测试学院

    测试开发工程师的黄埔军校

    点一下好看,就少一个 Bug

    来霍格沃兹测试开发学社,学习更多软件测试与测试开发的进阶技术,知识点涵盖web自动化测试 app自动化测试、接口自动化测试、测试框架、性能测试、安全测试、持续集成/持续交付/DevOps,测试左移、测试右移、精准测试、测试平台开发、测试管理等内容,课程技术涵盖bash、pytest、junit、selenium、appium、postman、requests、httprunner、jmeter、jenkins、docker、k8s、elk、sonarqube、jacoco、jvm-sandbox等相关技术,全面提升测试开发工程师的技术实力
    QQ交流群:484590337
    公众号 TestingStudio
    点击获取更多信息

  • 相关阅读:
    Swift新手教程3-字符串String
    BZOJ 2006 NOI2010 超级钢琴 划分树+堆
    Makefile学习(一)[第二版]
    leetcode 26 -- Remove Duplicates from Sorted Array
    谈谈我眼中的泛型
    oracle表空间查询维护命令大全之三(暂时表空间)史上最全
    超大表盘手表 : 超大表盘手表图片及搭配,超大表盘手表价格-美丽说
    Linux下安装Python3.3.0
    朝阳公园朝阳公园水下猎人潜水艇团购:水下猎人潜艇观光+水下梭镖打鱼套票!体验前所未有的水下休闲娱乐,探寻海底世界-聚齐北京团购
    隐形选购指南_亿超眼镜网
  • 原文地址:https://www.cnblogs.com/hogwarts/p/15820985.html
Copyright © 2020-2023  润新知