前端代碼覆蓋率增量計算

關(guān)于后臺的代碼增量的邏輯已經(jīng)有比較成熟的方案了。 根據(jù)javaparser解析前后的文件的方法列表,判斷是否有新增或者修改的方法。

前端代碼覆蓋率增量覆蓋的困難

針對前端代碼覆蓋率并不能像java那塊那么簡單,有專門的javascript的解析器,能夠獲取到這個js文件中所有的方法。所以套用原有的java那套邏輯基本是不太可行的。所以我們需要另辟蹊徑來解決這個問題。

java的增量代碼diff 我們是從解析源碼的文件入手的,那針對js既然這套不行,有沒有方式能夠從覆蓋率結(jié)果數(shù)據(jù)入手,去解決這個事情呢?
如果了解前端代碼覆蓋率的同學(xué)可能都清楚,前端的覆蓋率收集是根據(jù)瀏覽器提交的coverage數(shù)據(jù)來的。而coverage的數(shù)據(jù)其實是大有來頭的。

我們看一個數(shù)據(jù)


/**
 *
 * * `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: {}
}

以上的內(nèi)容中statementMap 中的5代表標(biāo)記的對應(yīng)的代碼塊為第10行,列則是從26到30, 同時映射到s中 這個代碼塊被執(zhí)行了10290次。
這塊的數(shù)據(jù)其實也完整的說明了對應(yīng)的文件中,代碼/分支/方法是否覆蓋的情況。

思路

那么是不是可以這么去考慮呢?從git diff中對比得到對應(yīng)文件的改動行數(shù),然后再對應(yīng)到這塊的數(shù)據(jù)上,如果修改的代碼行 是在statemanMap/ fnMap/ branchMap 的覆蓋范圍的話就保留這塊的數(shù)據(jù),如果說改動行中,不存在有這塊的內(nèi)容則從對象中將這塊的內(nèi)容剔除掉。這樣子就可以得到增量的數(shù)據(jù)了。

但是我們是不是直接針對用戶提交的coverage數(shù)據(jù)做處理呢?

答案是不行的。 我們需要了解一個問題,之前我們在 聊聊前端代碼覆蓋率 (長文慎入) 中提到過用戶提交的coverage數(shù)據(jù)并不是完整的反應(yīng)到原本的代碼行上,主要是針對typescript這塊,因為如果你的編譯是經(jīng)過ts-loader -> babel-loader 處理的話。得到的coverage中的數(shù)據(jù)中的line的值,其實跟源碼中的line會出現(xiàn)不一致的情況。而istanbul這塊是會根據(jù)sourceMap 重新映射回去的。

那哪里的數(shù)據(jù)才是正在正確的呢? 答案其實在通過nyc api生成的報告目錄下, 當(dāng)你的api指定了reporter包含有 json的情況下,就會在覆蓋率報告的目錄下生成有 coverage-final.json。 這里的數(shù)據(jù)其實跟coverage數(shù)據(jù)基本是一致的,并且這里的數(shù)據(jù)已經(jīng)經(jīng)過istanbul校正過。所以我們可以信任這塊的數(shù)據(jù)。

解決

從git api中獲取到改動行,判斷statemanMap/ fnMap/ branchMap 的開始行及結(jié)束行的范圍是否包含了改動行。如果在對應(yīng)的范圍那么則保留對象數(shù)據(jù),如果不在,則移除掉對應(yīng)的對象。如此剩下的就是改動范圍的覆蓋情況

所以我們可以這么處理

/**
 * 根據(jù)代碼codeDiff,過濾掉未改動的語句
 * @param fileFinal 對應(yīng)文件的覆蓋率數(shù)據(jù)情況,格式就是我們上述提到的數(shù)據(jù)
 * @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);
}

這里關(guān)于statement/line/fun的統(tǒng)計計算這里就不詳細(xì)描述了,主要可以參考 file-coverage 中的各個值的計算邏輯。

所以我們舉一個簡單的例子來說明下:假設(shè) client/src/utils/location.js 的coverage數(shù)據(jù)如下:

{
    "/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"
    }
}

以上的數(shù)據(jù)我們省掉了 fnMap以及branchMap的數(shù)據(jù)。

而我們的的codeDiff的數(shù)據(jù):

image

說明改動的行數(shù)只是49-50行

所以處理過的coverage的結(jié)果數(shù)據(jù)為

{
    "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"
}

相應(yīng)的fn, branch也是相應(yīng)的處理。

不足:

上述的方式其實存在一個問題,前端的增量覆蓋計算的邏輯并不是跟java的增量的邏輯一致的,java的最小增量的計算單位是方法,而前端的最小增量單位是語句。所以并不能很好的得到結(jié)果是前端某個代碼改動后,需要覆蓋這個代碼所在的方法的內(nèi)容,而只是需要覆蓋到改動的語句就可以了。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容