前端代码覆盖率增量计算

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

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 205,236评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,867评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,715评论 0 340
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,899评论 1 278
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,895评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,733评论 1 283
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,085评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,722评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,025评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,696评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,816评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,447评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,057评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,009评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,254评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,204评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,561评论 2 343

推荐阅读更多精彩内容