JavaScript覆盖率统计实现

主要需求

1、 支持browser & nodejs

由于javascript既能够在浏览器环境执行,也能够在nodejs环境执行,因此须要能够统计两种环境下单元測试的覆盖率情况。

2、 透明、无缝

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

原理

javascript覆盖率的相关文章比較少。以下的图是通过阅读开源javascript覆盖率工具istanbul及开源測试框架Karma的覆盖率插件karma-coverage得出的。

javascript覆盖率统计的核心思想是,在源码对应的位置注入统计代码,当代码执行之后,依据统计代码统计的数据确定程序执行的路径,终于生成覆盖率统计报告。

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接口)
时间: 2024-10-09 07:48:46

JavaScript覆盖率统计实现的相关文章

javascript如何统计页面中标签的数量

javascript如何统计页面中标签的数量:本章节介绍一下如何统计页面中标签的数量,当然标签是可以重复的,虽然不常用,不过寄希望能够给大家带来或多或少的帮助.代码如下: <!DOCTYPE html> <html> <head> <meta charset=" utf-8"> <meta name="author" content="http://www.softwhy.com/" />

bcov进行覆盖率统计

kcov是在bcov基础上进行的,bcov已经很久没有维护了: 首先需要下载依赖库libdwraft,然后在configure时候进行指定: ./configure --with-libdwarf=/usr/local/lib 然后make;make install (1)悲催的是编译过程中出现了错误 ‘PTRACE_O_TRACECLONE’ was not declared in this scope 增加头文件<sys/ptrace> 同时对ptrace调用的地方进行强制类型转换,例如原

.NET Core单元测试之搞死开发的覆盖率统计(coverlet + ReportGenerator )

.NET Core单元测试之搞死开发的覆盖率统计 这两天在给项目补单元测试,dalao们要求要看一下测试覆盖率 翻了一波官方test命令覆盖率倒是有支持了,然而某个更新日志里面写着 ["Support for Linux and Mac will be considered separately in a subsequent effort."] 吐血ing... 8102年都要过去了,微软同学你是不有点过分啊. 然后又翻了一堆资料之后发现,GitHub有dalao自己搞了个cover

多环境多需求并行下的代码测试覆盖率统计工具实现

马蜂窝技术原创内容,更多干货请关注公众号:mfwtech 测试覆盖率常被用来衡量测试的充分性和完整性,也是测试有效性的一个度量.「敏捷开发」的大潮之下,如何在快速迭代的同时保证对被测代码的覆盖度和产品质量,是一个非常有挑战性的话题. 在马蜂窝大交通.酒店等交易相关业务中,项目的开发和测试实践同样遵循敏捷的原则,迭代周期短.速度快.因此,如何依据测试覆盖率数据帮助我们有效判断项目质量.了解测试状态.提升迭代效率,是我们一直很重视的工作. Part.1 测试覆盖率统计中的挑战 对于功能测试而言,通常

单元测试覆盖率统计

参考http://www.cnblogs.com/turtle-fly/archive/2013/01/09/2851474.html 1.下载安装gcov/lcovgcov生成覆盖率数据lcov数据统计 2.编译gcc -g -pipe -m64 -fpic -DLINUX -fprofile-arcs -ftest-coverage 3.链接三条任选一个执行即可$ gcc --coverage$ gcc -lgcov$ gcc -fprofile-arcs 4.运行程序,会生成覆盖数据 5.

Sonar + Jacoco,强悍的UT, IT 双覆盖率统计(转)

以前做统计代码测试覆盖,一般用Cobertura.以前统计测试覆盖率,一般只算Unit Test,或者闭上眼睛把Unit Test和Integration Test一起算. 但是,我们已经过了迷信UT的时代: UT不支持大幅度重构,如果对类和方法进行重构拆分,UT就失去了保障重构后代码仍然正确的作用,还要花时间按新的类和方法重写,其他用例对旧类和方法的mock改起来也是噩梦. UT不支持基于用户故事的测试,即使覆盖率100%了,也不保证就是产品经理想要的东西. UT对输入参数和Mock对象行为的

Javascript字数统计

字数统计功能,原理是给textarea添加onKeyup事件,事件读取textarea内容并获得长度,并赋值给统计字数的那个文本节点,这里有一点要注意的是添加onKeypress和onKeydown事件也能实现效果,但都有些不足,会在某些情况下造成误解,我都试了下,感觉只用一个onKeyup事件是最明智的选择. <!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title&g

EclEmma单元测试覆盖率统计插件

EclEmma是Eclipse里的一个插件,安装简单,覆盖率显示直观.安装EclEmma.打开Eclipse,点击Help → Intall New SofaWare → Work with 输入 http://update.eclemma.org/  回车 点击完成结束配置,并开始下载安装插件,安装结束后Eclipse通常会提醒你重新启动Eclipse选择确定.在重新打开的Eclipse工具栏里你就会看到这个代码覆盖测试工具Eclemma!  

JavaScript中统计Textarea字数并提示还能输入的字符

<span style="font-size:18px;"><script language="javascript"> function countChar(textareaName,spanName) { document.getElementById(spanName).innerHTML = 140 - document.getElementById(textareaName).value.length; } </script&