本文共 3750 字,大约阅读时间需要 12 分钟。
因为javascript既可以在浏览器环境运行,也可以在nodejs环境运行,因此需要能够统计两种环境下单元测试的覆盖率情况。
用户写单元测试用例的时候,不需要为了支持覆盖率统计多写代码,之前写的用例无需修改就可以直接统计覆盖率情况。
javascript覆盖率的相关文章比较少,下面的图是通过阅读开源javascript覆盖率工具及开源测试框架的覆盖率插件得出的。javascript覆盖率统计的核心思想是,在源代码相应的位置注入统计代码,当代码运行之后,根据统计代码统计的数据确定程序运行的路径,最终生成覆盖率统计报告。
注:这里进行语法分析的好处是,针对书写不规范的代码(比如一行多个语句),依然能够很好统计出分支覆盖和组合覆盖等信息。
这一步需要先载入转换后的代码:
require
语句进行hook来无缝实现,后面会详细介绍然后执行单元测试,产生的统计信息会挂在全局变量this
下面。对于浏览器环境,this
就是window
,而对于nodejs环境this
就是global
。
这一步会根据全局标量中的覆盖率信息生成特定格式的报告,如html、lcov、cobertura、teamcity等。
//source codefunction abs(num){ if(abs > 0) return num; else return -num;}
//instrumented codevar __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;}}
通过hook可以直接无缝的加载转换后的代码,可以对下面两种语句进行hook:
require
vm.createScript
对require进行hook的代码是通过对Module._extensions['.js']
进行赋值实现的:
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之后的代码并运行 module._compile(ret.code, filename); } else { //载入原来的代码并运行 originalLoader(module, filename); } if (postLoadHook) { postLoadHook(filename); } };}
hook使覆盖率的集成变得简单,甚至不需要写代码,比如Mocha的覆盖率集成,只需要改用如下的调用方式即可:
istanbul cover _mocha -- -R spec test/spec
浏览器集成覆盖率就稍微麻烦一点,好在istanbul提供了:
1. 转换代码(调用istanbul的Instrumenter接口) 2. 将instrumented code发送到浏览器(*自己实现*) 3. 将包含覆盖率信息的执行结果发回server(*自己实现*) 4. 根据返回的覆盖率信息生成覆盖率报告(调用istanbul的Reporter接口)转载地址:http://gvenx.baihongyu.com/