前端代码覆盖率增量计算

作者: sw_saii | 来源:发表于2021-03-01 16:20 被阅读0次

    关于后台的代码增量的逻辑已经有比较成熟的方案了。 根据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的最小增量的计算单位是方法,而前端的最小增量单位是语句。所以并不能很好的得到结果是前端某个代码改动后,需要覆盖这个代码所在的方法的内容,而只是需要覆盖到改动的语句就可以了。

    相关文章

      网友评论

        本文标题:前端代码覆盖率增量计算

        本文链接:https://www.haomeiwen.com/subject/aguafltx.html