为什么要做覆盖率?

经常有人问这样的问题:“我们在做单元测试,那测试覆盖率要到多少才行?”。答案其实很简答,“作为指标的测试覆盖率都是没有用处的。”

Martin Fowler(重构那本书的作者)曾经写过一篇博客来讨论这个问题,他指出:把测试覆盖作为质量目标没有任何意义,而我们应该把它作为一种发现未被测试覆盖的代码的手段。

代码覆盖率的意义

分析未覆盖部分的代码,从而反推在前期测试设计是否充分,没有覆盖到的代码是否是测试设计的盲点,为什么没有考虑到?需求/设计不够清晰,测试设计的理解有误,工程方法应用后的造成的策略性放弃等等,之后进行补充测试用例设计。

检测出程序中的废代码,可以逆向反推在代码设计中思维混乱点,提醒设计/开发人员理清代码逻辑关系,提升代码质量。

代码覆盖率高不能说明代码质量高,但是反过来看,代码覆盖率低,代码质量不会高到哪里去,可以作为测试自我审视的重要工具之一。

以上都是我抄的,用于装逼

今天主要分享我如何把覆盖率落地到实际测试过程中。以下是我学习过的帖子

Jacoco 统计 Android 代码覆盖率 [instrument 方式]
浅谈代码覆盖率
Android 增量代码测试覆盖率工具
jacoco jenkins 插件

处理流程

看了很多的帖子,学习了很多内容,在看到 jenkins 插件的时候才想如何串起来整个流程。 我的整个流程分为 4 小块

为什么要嵌入 sdk

目前公司测试情况,还是多数手工测试为主,这时候其实可以很好的收集数据的时机,sdk 只要分为 debug 和 release 版本即可做到无缝的构建切换,无任何副作用。
数据收集的时机可考虑以下几种情况:

dump 数据的代码,社区里面很多了,重要是把文件上传了,且需要记录对应的代码分支,例如这个:

 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();

            }
        }
    }

服务端

服务端主要的作用是保持覆盖率数据,同时进行相同的 exec 文件进行替换操作,分析对应的文件可以得知每次 app 初始化的时候会随机产生一个 seesion id,解析方式如下:

BufferedInputStream inputStream = null;
        final SessionInfoStore sessionInfoStore = new SessionInfoStore();
        final ExecutionDataStore executionDataStore = new ExecutionDataStore();
        try {
            inputStream = new BufferedInputStream(file.getInputStream());
            final ExecutionDataReader reader = new ExecutionDataReader(inputStream);
            reader.setSessionInfoVisitor(sessionInfoStore);
            reader.setExecutionDataVisitor(executionDataStore);
            reader.read();
        }catch (Exception e) {
            log.error("parse exec file fail", e);
            return Result.Failure("parse exec file fail, please check it .");
        }finally {
            if (inputStream != null) {
                try {
                    inputStream.close();
                } catch (IOException e) {
                }
            }
        }

        if (sessionInfoStore.isEmpty()) {
            return Result.Failure("exec file not valid, can't found sessionid");
        }
        final String sessionId = sessionInfoStore.getInfos().get(0).getId();

jenkins

使用多参数构建方式,管理后台选择对应的 jacoco 覆盖率数据,发给 jenkins。
jenkins 主要事物:

下载代码分享

#!/bin/bash
echo "downloading ExecFiles"

echo "$ExecFiles"

fileUrl="$ExecFiles"

fileUrl=${fileUrl//,/ };
fileUrls=($fileUrl);

jacocoDir="$WORKSPACE""/jacoco"
if test -e $jacocoDir
then
    echo '文件夹已存在,删掉'
    rm -rf "$jacocoDir"
    mkdir $jacocoDir
else
    mkdir $jacocoDir
fi

echo "jacocoDir: $jacocoDir"

cd $jacocoDir

for url in ${fileUrls[*]}
do
    curl -O $url

done

echo "Done !!!!!!"

这时候你会发现为毛一点增量也没有说?

其实到这里你应该发现,在数据收集的时候已经有对应的覆盖率文件的分支了,这时候只需要借助我上面发的插件,下载你想要进行增加覆概率检查的分支代码即可。

支持这个的主要理论还有就是 jacoco 本身支持多份 exec 文件合并生成覆盖率报告。

好了,我的装逼到此结束。


↙↙↙阅读原文可查看相关链接,并与作者交流