代码覆盖率统计的实现原理

        前几天面试被问到:流水线中写好的“代码行覆盖率”原子服务怎么实现的,没答出来,因为不懂原理。今天来编写博客边梳理一下:

原理很简单:

1. 插桩

2. 统计

3. 生成报告

原理

代码的原理是:

(以代码行覆盖率为例)

定义一个lineCounters[] 数组,每一行写一个lineCounters[i]++,一共n行代码,若执行了m行,则lineCounters数组中有m个元素>0,用m/n得到行覆盖率

import java.io.FileOutputStream;
import java.io.IOException;
// 方法一:手动插桩
// 在代码中手动插入计数器,记录每行代码的执行次数。
class Solution {
    // 行计数器数组
    private static int[] lineCounters;

    public void quickSort(int[] arr, int low, int high) {
        // 插桩
        lineCounters[0]++;
        if (low < high) {
            // 插桩
            lineCounters[1]++;
            int pivotIndex = partition(arr, low, high);
            // 插桩
            lineCounters[2]++;
            quickSort(arr, low, pivotIndex - 1);
            // 插桩
            lineCounters[3]++;
            quickSort(arr, pivotIndex + 1, high);
            // 插桩
            lineCounters[4]++;
        }
    }

    private int partition(int[] arr, int low, int high) {
        // 插桩
        lineCounters[5]++;
        int pivot = arr[high];
        // 插桩
        lineCounters[6]++;
        int i = low - 1;
        // 插桩
        lineCounters[7]++;
        for (int j = low; j < high; j++) {
            // 插桩
            lineCounters[8]++;
            if (arr[j] <= pivot) {
                // 插桩
                lineCounters[9]++;
                i++;
                // 插桩
                lineCounters[10]++;
                swap(arr, i, j);
                // 插桩
                lineCounters[11]++;
            }
        }
        // 插桩
        lineCounters[12]++;
        swap(arr, i + 1, high);
        // 插桩
        lineCounters[13]++;
        return i + 1;
    }

    private void swap(int[] arr, int i, int j) {
        // 插桩
        lineCounters[14]++;
        int temp = arr[i];
        // 插桩
        lineCounters[15]++;
        arr[i] = arr[j];
        // 插桩
        lineCounters[16]++;
        arr[j] = temp;
        // 插桩
        lineCounters[17]++;
    }

    public static void main(String[] args) {
        // 初始化行计数器
        lineCounters = new int[18];
        Solution solution = new Solution();
        int[] arr = {3, 6, 8, 10, 1, 2, 1};
        solution.quickSort(arr, 0, arr.length - 1);

        // 计算行覆盖率
        int coveredLines = 0;
        for (int counter : lineCounters) {
            if (counter > 0) {
                coveredLines++;
            }
        }
        double lineCoverage = (double) coveredLines / lineCounters.length;
        System.out.println("行覆盖率: " + lineCoverage * 100 + "%");
    }
}

那为什么不用boolean[]呢,它每一个元素只占1bit,而int[]要占4byte(32bit)

因为:
int[] 每一个元素表示32bit,更省空间。具体统计方法如下:

可以看出,成熟的代码覆盖率框架中,int[]的每个元素并不是代表一行,而是代表32行!我们上边手写的统计demo只是最简陋的版本

封装的包

JaCoCo

JaCoCo的插桩就是每个int元素代表32行,且它的插桩是在字节码层面的,即不修改源码(编译后、类加载时插桩),有两大好处:

1. 兼容所有 JVM 语言(Java/Kotlin/Groovy)

2. 更准确,因为字节码才是最终执行的指令

使用方法更是简单,不用在源代码上做任何修改,配好依赖,直接运行命令就可以生成覆盖率报告了:

# 1. 运行测试收集数据
java -javaagent:jacocoagent.jar=destfile=jacoco.exec -jar app.jar 

# 2. 生成报告
java -jar jacococli.jar report jacoco.exec \
    --classfiles target/classes \         字节码加载路径
    --sourcefiles src/main/java \       源代码路径(为了在报告中高亮代码)
    --html report/ \                            生成html报告
    --line --branch                            统计行覆盖率(line)、分支覆盖率(branch)

Coverage

与JaCoCo类似,区别在于:

1. 插桩基于Python 源码行号+解释器执行追踪

 2. 代码不规范时失真:因为只能统计源代码的行,而不是编译后的逻辑层面的一行

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值