天天看点

android项目的JaCoCo代码覆盖率入门使用一、项目使用覆盖率初衷二、覆盖率概念三、JaCoCo四、工程实现覆盖率五、报告分析

一、项目使用覆盖率初衷

    由于公司开发项目业务类型是给企业做定制项目,开发周期短。开发人员在开发过程中往往在将项目提交给测试组测试时,没有进行全面项目测试,导致后面测试组测试出来一堆bug问题,甚至中断测试流程。为了解决大量开发和测试人员的时间,以及检测项目测试的代码是否测试覆盖的程度。在这种条件下,决定使用代码覆盖率来进行查看控制。

二、覆盖率概念

      代码覆盖率分析实际上一种度量方式,间接度量质量的方法的过程,是在保证测试质量的时候潜在保证实际产品的质量,在程序中寻找没有被测试用例测试过的地方的流程,创建新的测试用例来增加覆盖率的流程。

      代码覆盖分析是一种结构测试技术,属于白盒测试的范畴,结构化测试是以源代码的意图表现为依据来比较被测试程序行为的,与以需求规格为依据去比较被测程序行为的功能测试形成对比,结构化测试检查程序是如何工作的,以及代码结构和逻辑方面的潜在缺陷,而功能测试是不管程序内部是如何运作的,它只检查以及关心程序实现了什么。另外代码覆盖有时被称为“覆盖分析”、“测试覆盖分析”、“测试覆盖”、“覆盖监视器”等。

三、JaCoCo

JaCoCo简述

      JaCoCo是一个开源的覆盖率工具(官网地址: http://www.eclemma.org/JaCoCo/ ),它针对的开发语言是java,其使用方法很灵活,可以嵌入到Ant、Maven中;可以作为Eclipse插件,可以使用其JavaAgent技术监控Java程序等等。很多第三方的工具提供了对JaCoCo的集成,如sonar、Jenkins等。

      JaCoCo包含了多种尺度的覆盖率计数器,包含指令级覆盖(Instructions,C0coverage),分支(Branches,C1coverage)、圈复杂度(CyclomaticComplexity)、行覆盖(Lines)、方法覆盖(non-abstract methods)、类覆盖(classes),这里就详细介绍不属于入门使用范畴。

四、工程实现覆盖率

在app目录的gradle文件

apply plugin: 'com.android.application'
apply plugin: 'jacoco'//添加插件jacoco

jacoco {
    toolVersion = "0.7.9"//声明jacoco的版本号
}

android {
    compileSdkVersion 
    buildToolsVersion "25.0.2"
    defaultConfig {
        applicationId "com.weex.jasso"
        minSdkVersion 
        targetSdkVersion 
        versionCode 
        versionName "1.0"
        testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
    }
    buildTypes {
        debug {
            testCoverageEnabled = true//设置为true
        }
    }
}


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'//导入jacoco的版本包
    compile 'com.android.support.constraint:constraint-layout:+'
}

def coverageSourceDirs = [
        '../app/src/main/java'
]
//建立task任务将outputs/code-coverage/connected/目录下coverage.ec文件的生成覆盖率报告
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('$$', '$'))
            }
        }
    }
}
           

生成代码覆盖率的文件

1.  建立基础类BaseActivity

public abstract class BaseActivity extends Activity {
    public static String DEFAULT_COVERAGE_FILE_PATH = Environment.getExternalStorageDirectory()+"/hb/oe/";
    public static String TAG=Test1Activity.class.getName();
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        createFile(DEFAULT_COVERAGE_FILE_PATH,"coverage.ec");
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        closeFile();
    }

    public static void createFile(String path,String fileName){
        File file =null;
        if (path==null){
            new Exception("path no null");
        }
        if (fileName==null){
            file = new File(path);
        }else {
            file=new File(path,fileName);
        }
        if (!file.exists()) {
            try {
                file.createNewFile();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }

    public void closeFile(){
        OutputStream out = null;
        try {
            out = new FileOutputStream(DEFAULT_COVERAGE_FILE_PATH+"/coverage.ec", 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();
                }
            }
        }
    }
}
           

基础类在oncreate()创建coverage.ec文件用于写入代码覆盖率及方法createFile()方法,在界面关闭的onDestroy()生命周期写入覆盖数据方法closeFile();

2.  其他界面的activity继承基础类BaseActivity

      这里我为了演示写了Demo,写了两个类Test1Activity 和test2Activity

Test1Activity .class

public class Test1Activity extends BaseActivity {
    private Button mToastBtn;
    private Button mRequireBtn;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

    }


    public void require(){
        Toast.makeText(this,"网络请求",Toast.LENGTH_LONG).show();
    }

    public void toast(){
        Intent intent=new Intent(this,Test2Activity.class);
        startActivity(intent);
    }

    public void showTips(View view){
        Toast.makeText(this, "我是提示信息", Toast.LENGTH_SHORT).show();
    }


    public void showError(View view){
        Toast.makeText(this,"我正在抛出错误",Toast.LENGTH_SHORT).show();
        startActivity(new Intent(this,Test2Activity.class));
        finish();
    }

    public void showTest(){
        Toast.makeText(this,"测试",Toast.LENGTH_SHORT).show();
    }

}
           

Test2Activity.class

public class Test2Activity extends Activity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main2);
    }
}
           

在Test1Activity写了两个按钮点击事件,用于明确区别事件点击覆盖率;

3. 直接app运行到手机端

4. 生成报告

      将手机中的coverage.ec文件(上述代码有具体路径,可以自行日记打印出来,沿着相对路径去找)复制到build/outputs/code-coverage/connected下

android项目的JaCoCo代码覆盖率入门使用一、项目使用覆盖率初衷二、覆盖率概念三、JaCoCo四、工程实现覆盖率五、报告分析

5.生成报表的gradle指令

gradlew jacocoTestReport
           

还记得在gradle创建的task任务叫jacocoTestReport,就是将coverage.ec生成报表文件可视化。

五、报告分析

报表在build/reports/jacoco/jacocoTestReport/html下,右击查找index.html点击打开。

android项目的JaCoCo代码覆盖率入门使用一、项目使用覆盖率初衷二、覆盖率概念三、JaCoCo四、工程实现覆盖率五、报告分析
android项目的JaCoCo代码覆盖率入门使用一、项目使用覆盖率初衷二、覆盖率概念三、JaCoCo四、工程实现覆盖率五、报告分析
android项目的JaCoCo代码覆盖率入门使用一、项目使用覆盖率初衷二、覆盖率概念三、JaCoCo四、工程实现覆盖率五、报告分析

如上图所示,标记绿色的为分支覆盖充分,标记黄色的为部分分支覆盖(上图没有),标红色的为未执行该分支;

Demo的下载

小红的测试生涯                                                        http://www.dzwanli.com.cn/?p=1374

使用 Jacoco 实现 Android 端手工测试覆盖率统计      https://testerhome.com/topics/8554