前端代码覆盖率增量计算

关于后台的代码增量的逻辑已经有比较成熟的方案了。 根据javaparser解析前后的文件的方法列表,判断是否有新增或者修改的方法。

前端代码覆盖率增量覆盖的困难

针对前端代码覆盖率并不能像java那块那么简单,有专门的javascript的解析器,能够获取到这个js文件中所有的方法。所以套用原有的java那套逻辑基本是不太可行的。所以我们需要另辟蹊径来解决这个问题。

java的增量代码diff 我们是从解析源码的文件入手的,那针对js既然这套不行,有没有方式能够从覆盖率结果数据入手,去解决这个事情呢?
如果了解前端代码覆盖率的同学可能都清楚,前端的覆盖率收集是根据浏览器提交的coverage数据来的。而coverage的数据其实是大有来头的。

我们看一个数据


/**
 *
 * * `path` - the file path for which coverage is being tracked
 * * `statementMap` - map of statement locations keyed by statement index
 * * `fnMap` - map of function metadata keyed by function index
 * * `branchMap` - map of branch metadata keyed by branch index
 * * `s` - hit counts for statements
 * * `f` - hit count for functions
 * * `b` - hit count for branches
 */
{
    path: filePath,
    statementMap: {
        "5": {
			"end": {
				"line": 10,
				"column": 30
			},
			"start": {
				"line": 10,
				"column": 26
			}
		},
    },
    fnMap: {},
    branchMap: {},
    s: {
        "5": 10290,
    },
    f: {},
    b: {}
}

以上的内容中statementMap 中的5代表标记的对应的代码块为第10行,列则是从26到30, 同时映射到s中 这个代码块被执行了10290次。
这块的数据其实也完整的说明了对应的文件中,代码/分支/方法是否覆盖的情况。

思路

那么是不是可以这么去考虑呢?从git diff中对比得到对应文件的改动行数,然后再对应到这块的数据上,如果修改的代码行 是在statemanMap/ fnMap/ branchMap 的覆盖范围的话就保留这块的数据,如果说改动行中,不存在有这块的内容则从对象中将这块的内容剔除掉。这样子就可以得到增量的数据了。

但是我们是不是直接针对用户提交的coverage数据做处理呢?

答案是不行的。 我们需要了解一个问题,之前我们在 聊聊前端代码覆盖率 (长文慎入) 中提到过用户提交的coverage数据并不是完整的反应到原本的代码行上,主要是针对typescript这块,因为如果你的编译是经过ts-loader -> babel-loader 处理的话。得到的coverage中的数据中的line的值,其实跟源码中的line会出现不一致的情况。而istanbul这块是会根据sourceMap 重新映射回去的。

那哪里的数据才是正在正确的呢? 答案其实在通过nyc api生成的报告目录下, 当你的api指定了reporter包含有 json的情况下,就会在覆盖率报告的目录下生成有 coverage-final.json。 这里的数据其实跟coverage数据基本是一致的,并且这里的数据已经经过istanbul校正过。所以我们可以信任这块的数据。

解决

从git api中获取到改动行,判断statemanMap/ fnMap/ branchMap 的开始行及结束行的范围是否包含了改动行。如果在对应的范围那么则保留对象数据,如果不在,则移除掉对应的对象。如此剩下的就是改动范围的覆盖情况

所以我们可以这么处理

/**
 * 根据代码codeDiff,过滤掉未改动的语句
 * @param fileFinal 对应文件的覆盖率数据情况,格式就是我们上述提到的数据
 * @param statements 
 * @return
 */
private void handleStatements(List<Integer> codeDiff, Coverage fileFinal, CoverageSummary statements, CoverageSummary lines) {
    List<String> keys = new ArrayList<>();
    Map<Integer, Integer> line = new HashMap<>();
    // 这里的key 是0, 1, 2, 3  "statementMap":{"0":{"start":{"line":9,"column":22},"end":{"line":39,"column":1}} ;; "s":{"0":10150,"1":10150,"2":0,"3":0},
    for (String key : fileFinal.getStatementMap().keySet()) {
        if (!isDiff(codeDiff, fileFinal.getStatementMap().get(key).getStart().getLine(), fileFinal.getStatementMap().get(key).getEnd().getLine())) {
            keys.add(key);
        }
    }

    // 将不包含改动行的桩删除
    for (String key : keys) {
        fileFinal.getStatementMap().remove(key);
        fileFinal.getS().remove(key);
    }

    computeCoverageSummary(getLineCoverage(fileFinal), lines);
    computeCoverageSummary(fileFinal.getS(), statements);
}

这里关于statement/line/fun的统计计算这里就不详细描述了,主要可以参考 file-coverage 中的各个值的计算逻辑。

所以我们举一个简单的例子来说明下:假设 client/src/utils/location.js 的coverage数据如下:

{
    "/app/thirdCode/c94933a0-7250-11eb-8c93-c9620716ef34_1/xxx/client/src/utils/location.js": {
        "path": "/app/thirdCode/c94933a0-7250-11eb-8c93-c9620716ef34_1/xxx/client/src/utils/location.js",
        "statementMap": {
            "0": {
                "start": {
                    "line": 9,
                    "column": 39
                },
                "end": {
                    "line": 9,
                    "column": 54
                }
            },
            "1": {
                "start": {
                    "line": 10,
                    "column": 25
                },
                "end": {
                    "line": 10,
                    "column": 52
                }
            },
            "2": {
                "start": {
                    "line": 11,
                    "column": 26
                },
                "end": {
                    "line": 11,
                    "column": 48
                }
            },
            "3": {
                "start": {
                    "line": 13,
                    "column": 4
                },
                "end": {
                    "line": 19,
                    "column": 5
                }
            },
            "4": {
                "start": {
                    "line": 14,
                    "column": 8
                },
                "end": {
                    "line": 17,
                    "column": 9
                }
            },
            "5": {
                "start": {
                    "line": 15,
                    "column": 12
                },
                "end": {
                    "line": 15,
                    "column": 37
                }
            },
            "6": {
                "start": {
                    "line": 16,
                    "column": 12
                },
                "end": {
                    "line": 16,
                    "column": 21
                }
            },
            "7": {
                "start": {
                    "line": 18,
                    "column": 8
                },
                "end": {
                    "line": 18,
                    "column": 37
                }
            },
            "8": {
                "start": {
                    "line": 20,
                    "column": 22
                },
                "end": {
                    "line": 20,
                    "column": 45
                }
            },
            "9": {
                "start": {
                    "line": 21,
                    "column": 20
                },
                "end": {
                    "line": 21,
                    "column": 72
                }
            },
            "10": {
                "start": {
                    "line": 23,
                    "column": 4
                },
                "end": {
                    "line": 25,
                    "column": 5
                }
            },
            "11": {
                "start": {
                    "line": 24,
                    "column": 8
                },
                "end": {
                    "line": 24,
                    "column": 50
                }
            },
            "12": {
                "start": {
                    "line": 27,
                    "column": 4
                },
                "end": {
                    "line": 27,
                    "column": 19
                }
            },
            "13": {
                "start": {
                    "line": 36,
                    "column": 25
                },
                "end": {
                    "line": 36,
                    "column": 78
                }
            },
            "14": {
                "start": {
                    "line": 37,
                    "column": 19
                },
                "end": {
                    "line": 40,
                    "column": 10
                }
            },
            "15": {
                "start": {
                    "line": 38,
                    "column": 8
                },
                "end": {
                    "line": 38,
                    "column": 31
                }
            },
            "16": {
                "start": {
                    "line": 39,
                    "column": 8
                },
                "end": {
                    "line": 39,
                    "column": 19
                }
            },
            "17": {
                "start": {
                    "line": 42,
                    "column": 4
                },
                "end": {
                    "line": 44,
                    "column": 5
                }
            },
            "18": {
                "start": {
                    "line": 43,
                    "column": 8
                },
                "end": {
                    "line": 43,
                    "column": 29
                }
            },
            "19": {
                "start": {
                    "line": 46,
                    "column": 4
                },
                "end": {
                    "line": 48,
                    "column": 5
                }
            },
            "20": {
                "start": {
                    "line": 47,
                    "column": 8
                },
                "end": {
                    "line": 47,
                    "column": 35
                }
            },
            "21": {
                "start": {
                    "line": 50,
                    "column": 4
                },
                "end": {
                    "line": 50,
                    "column": 18
                }
            }
        },
        "s": {
            "0": 43,
            "1": 43,
            "2": 43,
            "3": 43,
            "4": 43,
            "5": 0,
            "6": 0,
            "7": 43,
            "8": 43,
            "9": 43,
            "10": 43,
            "11": 43,
            "12": 43,
            "13": 2184,
            "14": 2184,
            "15": 6552,
            "16": 6552,
            "17": 2184,
            "18": 2184,
            "19": 0,
            "20": 0,
            "21": 0
        },
        "_coverageSchema": "1a1c01bbd47fc00a2c39e90264f33305004495a9",
        "hash": "26596d813cde8cfd5d6449001d4e49f4283c164a"
    }
}

以上的数据我们省掉了 fnMap以及branchMap的数据。

而我们的的codeDiff的数据:

image

说明改动的行数只是49-50行

所以处理过的coverage的结果数据为

{
    "s": {
        "21": 0
    },
    "hash": "26596d813cde8cfd5d6449001d4e49f4283c164a",
    "path": "/app/thirdCode/c94933a0-7250-11eb-8c93-c9620716ef34_1/xxx/client/src/utils/location.js",
    "statementMap": {
        "21": {
            "end": {
                "line": 50,
                "column": 18
            },
            "start": {
                "line": 50,
                "column": 4
            }
        }
    },
    "_coverageSchema": "1a1c01bbd47fc00a2c39e90264f33305004495a9"
}

相应的fn, branch也是相应的处理。

不足:

上述的方式其实存在一个问题,前端的增量覆盖计算的逻辑并不是跟java的增量的逻辑一致的,java的最小增量的计算单位是方法,而前端的最小增量单位是语句。所以并不能很好的得到结果是前端某个代码改动后,需要覆盖这个代码所在的方法的内容,而只是需要覆盖到改动的语句就可以了。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值