实用Jacoco代码覆盖率Android集成与使用

来源:互联网 发布:nginx禁止域名访问 编辑:程序博客网 时间:2024/06/08 01:06

Jacoco集成步骤

 

1. 在gradle中引入jacoco加入以下:

apply plugin: 'jacoco'

....

jacoco{

    toolVersion = "0.7.1.201405082137"

}

android {

    buildTypes {

            debug {

                testCoverageEnabled = true

            }

 


 

2.接下来加入UI测试脚本

androidTestCompile 'com.jayway.android.robotium:robotium-solo:5.1'

3.在gradle文件中加入如下脚本,来对生成的.ec文件和编译产生的.class文件进行整合形成代码覆盖率报告。

//这里定义为你想要进行代码覆盖率统计的java目录

defcoverageSourceDirs = [

        '../app/src/main/java'

]

 

 

taskjacocoTestReport(type: JacocoReport) {

    group = "Reporting"

description = "Generate Jacoco coverage reportsafter running tests."

//这里为生成报告的类型

    reports {

        xml.enabled = true

        html.enabled = true

}

 

//这里为编译后产生的.class文件目录,excludes为你不想进行代码覆盖率统计的文件

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

            }

        }

    }

}

 

整合后,完整的gradle文件如下:

apply plugin: 'jacoco'



jacoco {
    toolVersion = "0.7.4+"
}

android {
    compileSdkVersion 23
    buildToolsVersion "23.0.2"

    defaultConfig {
        applicationId "com.zdj.method2"
        minSdkVersion 15
        targetSdkVersion 23
        versionCode 1
        versionName "1.0"
    }
    buildTypes {
        debug {
            testCoverageEnabled = true
        }
    }
}

dependencies {
    compile fileTree(dir: 'libs', include:['*.jar'])
    compile 'com.android.support:appcompat-v7:23.2.1'
    testCompile 'org.robolectric:robolectric:3.0-rc3'

}


def coverageSourceDirs = [
          'F:/Myproject/app/src/main/java'
]

task jacocoTestReport(type: JacocoReport) {
    group = "Reporting"
    description = "Generate Jacococoverage reports after running tests."
    reports {
        xml.enabled = true
        xml.destination="F:/Myproject/reports/jacoco.xml"
        html.enabled = true
        html.destination="F:/Myproject/reports/jacoco.html"

    }
    classDirectories = fileTree(
            dir: 'F:/Myproject/app/build/intermediates/classes/debug',
            excludes: ['**/R*.class',
                       '**/*$InjectAdapter.class',
                       '**/*$ModuleAdapter.class',
                       '**/*$ViewInjector*.class'
            ])
    sourceDirectories =files(coverageSourceDirs)


    executionData = files("F:/Myproject/coverage.ec")

    doFirst {
        new File("F:/Myproject/app/build/intermediates/classes/").eachFileRecurse{ file ->
            if (file.name.contains('$$')){
                file.renameTo(file.path.replace('$$','$'))
            }
        }
    }

}

4.接下来进入java的设置
将如下代码加入到自己的包中
public class JacocoInstrumentation extends Instrumentation {
    public static String TAG = "JacocoInstrumentation:";
    private static String DEFAULT_COVERAGE_FILE_PATH = "/mnt/sdcard/DingtoneDebug/coverage.ec";


    private static JacocoInstrumentation sInstrumentation;


    private final Bundle mResults = new Bundle();


    private Intent mIntent;
    private static final boolean LOGD = true;


    private boolean mCoverage = true;


    private String mCoverageFilePath;




    /**
     * Constructor
     */
    public JacocoInstrumentation() {


    }


    /**
     * Called when the instrumentation is starting, before any application code
     * has been loaded.  Usually this will be implemented to simply call
     * {@link #start} to begin the instrumentation thread, which will then
     * continue execution in
     */
    @Override
    public void onCreate(Bundle arguments) {
        Log.d(TAG, "onCreate(" + arguments + ")");
        super.onCreate(arguments);


        sInstrumentation = this;


        if (arguments != null) {
            //参数在dos里输入 -e coverage true -e coverageFile ....
            mCoverage = getBooleanArgument(arguments, "coverage");
            mCoverageFilePath = arguments.getString("coverageFile");
        }
        //将第一个加载的activity设置给intent
        mIntent = new Intent(getTargetContext(), getLaunchActiviyClass());
        mIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
        //启动一个新的线程,这个线程会调用哦你start();
        start();
    }


    @Override
    public void onStart() {
        if (LOGD)
            Log.d(TAG, "onStart()");
        super.onStart();
        Looper.prepare();
        //与第一个加载的activity同步启动
        startActivitySync(mIntent);


    }


    public static void generateJacocoReport() {


        if (sInstrumentation != null) {
            sInstrumentation.startGenerateCoverageReport();
        }
    }




    /**
     * 获取dos中输入的Boolean参数,如果没有输入则返回false
     * @param arguments
     * @param tag
     * @return
     */
    private boolean getBooleanArgument(Bundle arguments, String tag) {
        String tagString = arguments.getString(tag);
//        return tagString != null && Boolean.parseBoolean(tagString);
        return true;
    }




    /**
     * 获得文件路径,可自定义
     * @return
     */
    private String getCoverageFilePath() {
        if (mCoverageFilePath == null) {
//            SimpleDateFormat sDateFormat = new SimpleDateFormat("yyyy-MM-dd-HH-mm-ss", Locale.US);
//            String date = sDateFormat.format(new java.util.Date());
            return SDCardUtil.RootDirPathOnSdcard + "coverage.ec";
        } else {
            return mCoverageFilePath;
        }
    }




    /**
     * 产生报告,Out=new FileOutputStream(path,true)
     * 第二个参数一定要为true,否则不能持续收集,每次都会覆盖掉上一次
     */
    private void generateCoverageReport() {
        Log.d(TAG, "generateCoverageReport():" + getCoverageFilePath());
        OutputStream out = null;
        try {
            out = new FileOutputStream(getCoverageFilePath(), 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));
        } catch (Exception e) {
            Log.d(TAG, e.toString(), e);
        } finally {
            if (out != null) {
                try {
                    out.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }




    private boolean setCoverageFilePath(String filePath) {
        if (filePath != null && filePath.length() > 0) {
            mCoverageFilePath = filePath;
            return true;
        }
        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_STREAMRESULT, "\nError: "
                + msg);
    }


    private void startGenerateCoverageReport() {


        if (LOGD)
            Log.d(TAG, "onActivityFinished()");
        if (mCoverage) {
            generateCoverageReport();
        }
        finish(Activity.RESULT_OK, mResults);
    }




    protected Class<?> getLaunchActiviyClass() {
        return SplashActivity.class;
    }


}


5.在manifest文件中加入如下:
<instrumentation
    android:name="com.zdj.method2.test.JacocoInstrumentation"
    android:handleProfiling="true"
    android:label="CoverageInstrumentation"
    android:targetPackage="com.zdj.method2" />


6.最后在你想要结束测试的地方,调用该类的 generateJacocoReport()的方法,一般是放在主界面按下Back键的时候调用该方法来结束测试,生成代码覆盖率统计。
参考链接:
http://www.dzwanli.com.cn/?p=1374
http://blog.csdn.net/itfootball/article/details/45618609


Android Jacoco使用步骤

1.安装应用到手机,然后关闭应用。

2.在cmd命令行通过输入以下命令来打开应用

Adbshell am instrument -e coverage true -wme.dingtone.app.im.debug/me.dingtone.app.im.instrumentation.JacocoInstrumentation

3.执行测试,

4.测试结束后,点back退出应用到后台

5.执行 adb pull/sdcard/DingtoneDebug/coverage.ecf:/project/dingtone_lib/build/outputs/code-coverage/connected/coverage.ec  将文件从手机sdcard复制到自己的dingtone项目下

图1. 我的项目目录

6.到达工程目录cd f:/project(我的dingtone工程目录是f:/project)

7.执行gradlew jacocoTestReport

8.成功后测试报告存储在:F:\project\dingtone_lib\build\reports\jacoco\jacocoTestReportt\html\index.html,根据报告可以分析代码的执行率。红色标记的是没有执行的代码。

 




 

                                                                                            

 

注:

(目前需要有Dingtone的项目工程文件)

经测试可多次进行代码覆盖率统计,每次在主界面按下back键会结束统计,控制台会输出INSTRUMENTATION_CODE: -1。通过back结束测试后,可通过步骤2重新开始测试。每一次的测试报告不会覆盖上一次的,会累加到上一次中。

测试过程中可以通过home进入后台,可以通过图标直接返回App进行测试。

有的手机可能会收集不到代码覆盖率,如果出现覆盖率为0的现象,可以换一只手机(经测试魅族手机可能不支持)。


0 0