JaCoCo测试代码覆盖率

一.背景介绍
在产品客户端的测试过程中,新功能测试以及回归测试在手工测试的情况下,即便是测试用例再详尽,难免也可能会有疏漏之处。故使用代码覆盖率工具Jacoco作为手工测试代码覆盖率的统计.

解决的问题: 通过查看测试代码覆盖率客观数据,来进一步完善业务测试场景,完善测试用例.

二.使用方法
1. 由于Android本身集成了Jacoco工具,故在其他工程如果需要生成代码测试覆盖率,则仅需要添加如下配置

buildTypes {
    debug {
        testCoverageEnabled true
        ... ...
    }
}

2. 如果针对工程添加配置,在build apk时出现死循环锁( Waiting to acquire shared lock on daemon addresses registry),针对此种情况,需要增加如下配置:

configurations.all {
    resolutionStrategy {
        force 'org.jacoco:org.jacoco.report:0.7.4.201502262128'
        force 'org.jacoco:org.jacoco.core:0.7.4.201502262128'
    }
}

app/build.gradle 增加配置(仅仅针对build不成功工程)

3. project/app/build.gradle 增加配置,此配置用于生成代码覆盖率报告

/*Java Code Coverage*/
apply plugin: 'jacoco'
 
def coverageSourceDirs = [
        '../src/com/i/coverage'
]
 
task jacocoTestReport(type: JacocoReport) {
    group = "Reporting"
    description = "Generate Jacoco coverage reports"
    additionalSourceDirs = files(coverageSourceDirs)
    sourceDirectories = files(coverageSourceDirs)
 
    reports {
        xml.enabled = true
        html.enabled = true
    }
 
    classDirectories = fileTree(
            dir: './build/intermediates/classes/normal/debug',
            excludes: ['**/R.class', '**/R$*.class', '**/*$ViewInjector*.*', '**/BuildConfig.*', '**/Manifest*.*']
    )
 
    executionData = files('./build/outputs/code-coverage/connected/flavors/NORMAL/coverage.ec')
}

(1) 生成测试报告需要添加插件jacoco
(2) 针对build不成功工程其jacoco版本必须和2步骤对应,否则会造成报告不能读
(3) coverageSourceDirs为测试代码路径,executionData为生成测试报告的数据源路径

4. 工程配置以及测试工具apk
(1) 测试工程添加广播接收者

 <receiver android:name="test.JacocoReceiver">
      <intent-filter>
          <action android:name="jacoco.test.action.START"/>
          <action android:name="jacoco.test.action.CLEAR"/>
          <action android:name="jacoco.test.action.END"/>
      </intent-filter>
</receiver>

public class JacocoReceiver extends BroadcastReceiver {
    private static String TAG = "JacocoReceiver:";
    private final static String ACTION_START = "jacoco.test.action.START";
    private final static String ACTION_END = "jacoco.test.action.END";
    private final static String ACTION_CLEAR = "jacoco.test.action.CLEAR";
    private static String mCoverageFilePath;
    private Context mContext;

    @Override
    public void onReceive(Context context, Intent intent) {
        mContext = context;
        if (intent != null) {
            String aciton = intent.getAction();
            if (ACTION_START.equals(aciton)) {
                createFile();
            } else if (ACTION_END.equals(aciton)) {
                generateCoverageReport();
            } else if (ACTION_CLEAR.equals(aciton)) {
                deleteCoverageReport();
            }
        }

    }

    private void createFile() {
        mCoverageFilePath = mContext.getFilesDir().getPath() + File.separator + "coverage.ec";
        File file = new File(mCoverageFilePath);
        if (!file.exists()) {
            try {
                file.createNewFile();
                Log.i(TAG, "create report");
            } catch (IOException e) {
                Log.e(TAG, "IOException", e);
            }
        }
    }

    private void generateCoverageReport() {
        Log.i(TAG, "generateCoverageReport():" + mCoverageFilePath);
        OutputStream out = null;
        try {
            out = new FileOutputStream(mCoverageFilePath, 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.e(TAG, "Exception", e);
        } finally {
            if (out != null) {
                try {
                    out.close();
                } catch (IOException e) {
                    Log.e(TAG, "IOException", e);
                }
            }
            Log.i(TAG, "jacoco end");
        }
    }
    
    private void deleteCoverageReport() {
        if (!TextUtils.isEmpty(mCoverageFilePath)) {
            File file = new File(mCoverageFilePath);
            if (file != null && file.exists() && file.isFile()) {
                file.delete();
                Log.i(TAG, "delete report");
            }
        }
    }
}

(2) 测试工具发送广播,然后手动操作测试

public class MainActivity extends Activity implements View.OnClickListener {
    private final static String ACTION_START = "jacoco.test.action.START";
    private final static String ACTION_END = "jacoco.test.action.END";
    private final static String ACTION_CLEAR = "jacoco.test.action.CLEAR";

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

    private void initView() {
        findViewById(R.id.start).setOnClickListener(this);
        findViewById(R.id.end).setOnClickListener(this);
        findViewById(R.id.clear).setOnClickListener(this);
    }

    @Override
    public void onClick(View v) {
        switch (v.getId()) {
            case R.id.start:
                Intent intent = new Intent(ACTION_START);
                sendBroadcast(intent);
                Toast.makeText(this, "start listener coverage",1).show();
                break;
            case R.id.end:
                Intent intentEnd = new Intent(ACTION_END);
                sendBroadcast(intentEnd);
                Toast.makeText(this, "create coverage report end",1).show();
                break;
            case R.id.clear:
                Intent intentClear = new Intent(ACTION_CLEAR);
                sendBroadcast(intentClear);
                Toast.makeText(this, "clear coverage report success",1).show();
                break;
            default:
                break;
        }

    }
}

(1) 测试工具用于生成测试代码数据源,在测试代码覆盖率时需要使用,生成数据源,路径/data/data/{packagename}/files/coverage.ec;
(2) 生成测试代码覆盖率报告时,需要拷贝该数据源到{projectname}/app/build/outputs/code-coverage/connected/flavors/NORMAL/coverage.ec
(3) 执行步骤3的task jacocoTestReport , 则在{projectname}/app/build/reports/jacoco/jacocoTestReport/html 生成测试报告,可针对性的查看.

三.报告样例
在这里插入图片描述
四.后续
以上是全量代码覆盖率的生成,适用于大改版的情况下去做全面的测试覆盖;
在日常的小版本迭代过程中,适合使用差量代码覆盖率的使用.
//TODO 差量代码覆盖率 https://testerhome.com/topics/5823

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值