JavaScript覆盖率统计实现

简介: 主要需求 1、 支持browser & nodejs 因为javascript既可以在浏览器环境运行,也可以在nodejs环境运行,因此需要能够统计两种环境下单元测试的覆盖率情况。 2、 透明、无缝 用户写单元测试用例的时候,不需要为了支持覆盖率统计多写代码,之前写的用例无需修改就可以直接统

主要需求

1、 支持browser & nodejs

因为javascript既可以在浏览器环境运行,也可以在nodejs环境运行,因此需要能够统计两种环境下单元测试的覆盖率情况。

2、 透明、无缝

用户写单元测试用例的时候,不需要为了支持覆盖率统计多写代码,之前写的用例无需修改就可以直接统计覆盖率情况。

原理

javascript覆盖率的相关文章比较少,下面的图是通过阅读开源javascript覆盖率工具istanbul及开源测试框架Karma的覆盖率插件karma-coverage得出的。javascript覆盖率统计的核心思想是,在源代码相应的位置注入统计代码,当代码运行之后,根据统计代码统计的数据确定程序运行的路径,最终生成覆盖率统计报告。
screenshot

1. 转换(instrument)

  • 使用开源工具Esprima对源代码进行语法分析生成语法树
  • 在语法树相应的位置注入统计代码,在程序执行到这个位置的时候对相应的全局变量赋值,确保执行之后能够根据全局变量知道代码的执行流程
  • 使用开源工具Escodegen根据注入之后的语法树生成对应的javascript代码,即转换之后的代码(instrumented code)

注:这里进行语法分析的好处是,针对书写不规范的代码(比如一行多个语句),依然能够很好统计出分支覆盖和组合覆盖等信息。

2. 执行(run)

这一步需要先载入转换后的代码:

  • nodejs:直接通过对require语句进行hook来无缝实现,后面会详细介绍
  • 浏览器环境:需要将转换后的代码传给浏览器。如果是karma之类的带server的测试框架,需要通过socket传输至浏览量器,执行完之后再将包含覆盖率信息的执行结果传回server,生成测试报告

然后执行单元测试,产生的统计信息会挂在全局变量this下面。对于浏览器环境,this就是window,而对于nodejs环境this就是global

3. 生成报告(report)

这一步会根据全局标量中的覆盖率信息生成特定格式的报告,如html、lcov、cobertura、teamcity等。

一个例子

//source code
function abs(num){
    if(abs > 0)
        return num;
    else
        return -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可以直接无缝的加载转换后的代码,可以对下面两种语句进行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提供了API:
1. 转换代码(调用istanbul的Instrumenter接口)
2. 将instrumented code发送到浏览器(*自己实现*)
3. 将包含覆盖率信息的执行结果发回server(*自己实现*)
4. 根据返回的覆盖率信息生成覆盖率报告(调用istanbul的Reporter接口)

目录
相关文章
|
17天前
|
JavaScript 前端开发
如何通过js实现对句子个数的统计
使用JavaScript统计文本中句子个数,可通过识别语言类型(中文/日文:句号、问号、感叹号,西方语言:.?!)来分割文本。示例代码提供。同样,也有字数统计函数,对中文和西方语言处理方式不同。
|
4月前
|
JavaScript
JS判断一个字符串中出现次数最多的字符 统计这个次数
JS判断一个字符串中出现次数最多的字符 统计这个次数
|
6月前
|
机器学习/深度学习 存储 前端开发
手撕前端面试题【javascript~ 总成绩排名、子字符串频次统计、继承、判断斐波那契数组等】
在刷题之前先介绍一下牛客。Leetcode有的刷题牛客都有,除此之外牛客里面还有招聘(社招和校招)、一些上岸大厂的大佬的面试经验。 牛客是可以伴随一生的编程软件(完全免费),从学校到社会工作,时时刻刻你都可以用到,感兴趣的可以去注册试试可以伴随一生的刷题app
35 0
|
8月前
|
JavaScript
JS判断一个字符串中出现次数最多的字符 统计这个次数
JS判断一个字符串中出现次数最多的字符 统计这个次数
72 0
|
11月前
|
XML Web App开发 JavaScript
不用 JavaScript,纯静态网站如何统计 PV?
不用 JavaScript,纯静态网站如何统计 PV?
82 0
|
11月前
|
JavaScript 前端开发
JavaScript中统计字符的个数
JavaScript中统计字符的个数
88 0
|
11月前
|
JavaScript
js 统计字符出现的次数
js 统计字符出现的次数
71 0
|
JavaScript 前端开发
利用JavaScript实现二级联动
利用JavaScript实现二级联动 要实现JavaScript二级联动效果,首先要确定需要哪些技术: 二维数组 for in循环 new Option(text,value,true,true) add(option,null) onchange() 表单事件 HTML代码: <!-- <input type="text" id="text"> --> 请选择省份: <select name="" id="provinces"> <!-- <option value="江苏省">江苏省</option>
|
JavaScript 前端开发
JavaScript函数柯里化的实现原理,进来教你完成一个自己的自动实现柯里化方法
JavaScript函数柯里化的实现原理,进来教你完成一个自己的自动实现柯里化方法
167 0
|
移动开发 JavaScript weex
weex-自定义module,实现weex在iOS的本地化,js之间互相跳转,交互,传值(iOS接入weex的最佳方式)
weex-自定义module,实现weex在iOS的本地化,js之间互相跳转,交互,传值(iOS接入weex的最佳方式)
219 0