關(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ù):
說明改動的行數(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)容,而只是需要覆蓋到改動的語句就可以了。