javascript
JavaScript覆盖率统计实现
主要需求
1、 支持browser & nodejs
由于javascript既能夠在瀏覽器環(huán)境執(zhí)行,也能夠在nodejs環(huán)境執(zhí)行,因此須要能夠統(tǒng)計兩種環(huán)境下單元測試的覆蓋率情況。
2、 透明、無縫
用戶寫單元測試用例的時候,不須要為了支持覆蓋率統(tǒng)計多寫代碼,之前寫的用例無需改動就能夠直接統(tǒng)計覆蓋率情況。
原理
javascript覆蓋率的相關(guān)文章比較少。以下的圖是通過閱讀開源javascript覆蓋率工具istanbul及開源測試框架Karma的覆蓋率插件karma-coverage得出的。
javascript覆蓋率統(tǒng)計的核心思想是,在源碼對應(yīng)的位置注入統(tǒng)計代碼,當(dāng)代碼執(zhí)行之后,依據(jù)統(tǒng)計代碼統(tǒng)計的數(shù)據(jù)確定程序執(zhí)行的路徑,終于生成覆蓋率統(tǒng)計報告。
1. 轉(zhuǎn)換(instrument)
- 使用開源工具Esprima對源碼進行語法分析生成語法樹
- 在語法樹對應(yīng)的位置注入統(tǒng)計代碼。在程序運行到這個位置的時候?qū)?yīng)的全局變量賦值,確保運行之后可以依據(jù)全局變量知道代碼的運行流程
- 使用開源工具Escodegen依據(jù)注入之后的語法樹生成對應(yīng)的javascript代碼,即轉(zhuǎn)換之后的代碼(instrumented code)
注:這里進行語法分析的優(yōu)點是,針對書寫不規(guī)范的代碼(比方一行多個語句),依舊可以非常好統(tǒng)計出分支覆蓋和組合覆蓋等信息。
2. 運行(run)
這一步須要先加載轉(zhuǎn)換后的代碼:
- nodejs:直接通過對require語句進行hook來無縫實現(xiàn),后面會具體介紹
- 瀏覽器環(huán)境:須要將轉(zhuǎn)換后的代碼傳給瀏覽器。假設(shè)是karma之類的帶server的測試框架,須要通過socket傳輸至瀏覽量器,運行完之后再將包括覆蓋率信息的運行結(jié)果傳回server。生成測試報告
然后運行單元測試。產(chǎn)生的統(tǒng)計信息會掛在全局變量this以下。
對于瀏覽器環(huán)境,this就是window,而對于nodejs環(huán)境this就是global。
3. 生成報告(report)
這一步會依據(jù)全局標(biāo)量中的覆蓋率信息生成特定格式的報告,如html、lcov、cobertura、teamcity等。
一個樣例
//source code function abs(num){if(abs > 0)return num;elsereturn -num; } //instrumented code var __cov_iypKC$dWI6uJFmvxThycaA = (Function('return this'))(); if (!__cov_iypKC$dWI6uJFmvxThycaA.__coverage__) { __cov_iypKC$dWI6uJFmvxThycaA.__coverage__ = {}; } __cov_iypKC$dWI6uJFmvxThycaA = __cov_iypKC$dWI6uJFmvxThycaA.__coverage__; if (!(__cov_iypKC$dWI6uJFmvxThycaA['/Users/lonfee88/Codes/testframe/coverage-jasmine-istanbul-karma/abs.js'])) {__cov_iypKC$dWI6uJFmvxThycaA['/Users/lonfee88/Codes/testframe/coverage-jasmine-istanbul-karma/abs.js'] = {"path":"/Users/lonfee88/Codes/testframe/coverage-jasmine-istanbul-karma/abs.js","s":{"1":1,"2":0,"3":0,"4":0},"b":{"1":[0,0]},"f":{"1":0},"fnMap":{"1":{"name":"abs","line":1,"loc":{"start":{"line":1,"column":-15},"end":{"line":1,"column":17}}}},"statementMap":{"1":{"start":{"line":1,"column":-15},"end":{"line":6,"column":1}},"2":{"start":{"line":2,"column":1},"end":{"line":5,"column":14}},"3":{"start":{"line":3,"column":2},"end":{"line":3,"column":13}},"4":{"start":{"line":5,"column":2},"end":{"line":5,"column":14}}},"branchMap":{"1":{"line":2,"type":"if","locations":[{"start":{"line":2,"column":1},"end":{"line":2,"column":1}},{"start":{"line":2,"column":1},"end":{"line":2,"column":1}}]}}}; } __cov_iypKC$dWI6uJFmvxThycaA = __cov_iypKC$dWI6uJFmvxThycaA['/Users/lonfee88/Codes/testframe/coverage-jasmine-istanbul-karma/abs.js']; function abs(num){__cov_iypKC$dWI6uJFmvxThycaA.f['1']++;__cov_iypKC$dWI6uJFmvxThycaA.s['2']++;if(abs>0){__cov_iypKC$dWI6uJFmvxThycaA.b['1'][0]++;__cov_iypKC$dWI6uJFmvxThycaA.s['3']++;return num;}else{__cov_iypKC$dWI6uJFmvxThycaA.b['1'][1]++;__cov_iypKC$dWI6uJFmvxThycaA.s['4']++;return-num;}}node.js集成覆蓋率
通過hook能夠直接無縫的載入轉(zhuǎn)換后的代碼。能夠?qū)σ韵聝煞N語句進行hook:
- require
- vm.createScript
對require進行hook的代碼是通過對Module._extensions['.js']進行賦值實現(xiàn)的:
function hookRequire(matcher, transformer, options) {options = options || {};var fn = transformFn(matcher, transformer, options.verbose),postLoadHook = options.postLoadHook &&typeof options.postLoadHook === 'function' ? options.postLoadHook : null;Module._extensions['.js'] = function (module, filename) {var ret = fn(fs.readFileSync(filename, 'utf8'), filename);if (ret.changed) {//加載instrument之后的代碼并執(zhí)行module._compile(ret.code, filename);} else {//加載原來的代碼并執(zhí)行originalLoader(module, filename);}if (postLoadHook) {postLoadHook(filename);}}; }hook使覆蓋率的集成變得簡單。甚至不須要寫代碼,比方Mocha的覆蓋率集成,僅僅須要改用例如以下的調(diào)用方式就可以:
istanbul cover _mocha -- -R spec test/spec瀏覽器集成覆蓋率
瀏覽器集成覆蓋率就略微麻煩一點。好在istanbul提供了API:
轉(zhuǎn)載于:https://www.cnblogs.com/jzdwajue/p/6900462.html
總結(jié)
以上是生活随笔為你收集整理的JavaScript覆盖率统计实现的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: java 生成二维码
- 下一篇: javascript 自定义Map