04/07
2017

目录

  1. 介绍
  2. instrument
  3. cover
  4. node vs browser

1. 介绍

istanbul是javascript检查代码覆盖率的技术,大概看了一下实现,实现的原理是通过ast分析,向所有的代码路径插入一段用于统计的语句,并且把统计结果防止在window/global变量__cover__, 在所有测试跑完再读取这些统计结果。

github地址见: [https://github.com/gotwarlost/istanbul]

2. instrument

该命令是对原本的js代码进行更改,返回带有统计语句的代码,如原来的js:

function test(a) {
    if (a) {
        console.log(1);
    }
    else {
        console.log(b);
    }
}

test(1);

经过instrument之后:(可以通过执行cli:istanbul instrument /you/js/path.js)

var __cov_nV7KwIqjIcwxJtPnOEx3Lg = (Function('return this'))();
if (!__cov_nV7KwIqjIcwxJtPnOEx3Lg.__coverage__) { __cov_nV7KwIqjIcwxJtPnOEx3Lg.__coverage__ = {}; }
__cov_nV7KwIqjIcwxJtPnOEx3Lg = __cov_nV7KwIqjIcwxJtPnOEx3Lg.__coverage__;
if (!(__cov_nV7KwIqjIcwxJtPnOEx3Lg['/Users/zhanglei55/study/istanbul/test.js'])) {
   __cov_nV7KwIqjIcwxJtPnOEx3Lg['/Users/zhanglei55/study/istanbul/test.js'] = {"path":"/Users/zhanglei55/study/istanbul/test.js","s":{"1":1,"2":0,"3":0,"4":0,"5":0},"b":{"1":[0,0]},"f":{"1":0},"fnMap":{"1":{"name":"test","line":1,"loc":{"start":{"line":1,"column":0},"end":{"line":1,"column":17}}}},"statementMap":{"1":{"start":{"line":1,"column":0},"end":{"line":8,"column":1}},"2":{"start":{"line":2,"column":4},"end":{"line":7,"column":5}},"3":{"start":{"line":3,"column":8},"end":{"line":3,"column":23}},"4":{"start":{"line":6,"column":8},"end":{"line":6,"column":23}},"5":{"start":{"line":10,"column":0},"end":{"line":10,"column":8}}},"branchMap":{"1":{"line":2,"type":"if","locations":[{"start":{"line":2,"column":4},"end":{"line":2,"column":4}},{"start":{"line":2,"column":4},"end":{"line":2,"column":4}}]}}};
}
__cov_nV7KwIqjIcwxJtPnOEx3Lg = __cov_nV7KwIqjIcwxJtPnOEx3Lg['/Users/zhanglei55/study/istanbul/test.js'];
function test(a){__cov_nV7KwIqjIcwxJtPnOEx3Lg.f['1']++;__cov_nV7KwIqjIcwxJtPnOEx3Lg.s['2']++;if(a){__cov_nV7KwIqjIcwxJtPnOEx3Lg.b['1'][0]++;__cov_nV7KwIqjIcwxJtPnOEx3Lg.s['3']++;console.log(1);}else{__cov_nV7KwIqjIcwxJtPnOEx3Lg.b['1'][1]++;__cov_nV7KwIqjIcwxJtPnOEx3Lg.s['4']++;console.log(b);}}__cov_nV7KwIqjIcwxJtPnOEx3Lg.s['5']++;test(1);

可以看到instrument的代码的执行:

  1. 记录所有统计预计的位置以及当前文件的路径,放置于全局变量上global/window上。
  2. 当某一块统计语句执行的时候,会在管理的位置上的统计变量自动加一。
  3. 这样就可以在所有测试模块执行完毕之后,再全局变量上查看统计

3. cover

cover命令是用于node端执行代码覆盖率测试的时候,一个主入口命令。istanbul在node端提供了一个执行js的环境,需要跑覆盖率测试的js都要在istanbul环境下执行,因为istanbul对node的核心代码module模块进行了修改!!! 可以在lib/hook.js下看到:

function hookRequire(matcher, transformer, options) {
    options = options || {};
    var extensions,
        fn = transformFn(matcher, transformer, options.verbose),
        postLoadHook = options.postLoadHook &&
            typeof options.postLoadHook === 'function' ? options.postLoadHook : null;

    extensions = options.extensions || ['.js'];

    extensions.forEach(function(ext){
        if (!(ext in originalLoaders)) {
            originalLoaders[ext] = Module._extensions[ext] || Module._extensions['.js'];
        }
        Module._extensions[ext] = function (module, filename) {
            var ret = fn(fs.readFileSync(filename, 'utf8'), filename);
            if (ret.changed) {
                module._compile(ret.code, filename);
            } else {
                originalLoaders[ext](module, filename);
            }
            if (postLoadHook) {
                postLoadHook(filename);
            }
        };
    });
}

在istanbul提供的环境中,可以看到istanbul对js的加载器重写,不同于node默认的js加载器(readFile + compile), istanbul的js加载器的过程是(readFile + instrument + compile),代码中的fn/transformer就是intrument函数。

node vs browser

istanbul在node端和browser执行的过程是不一样的,

  1. node端:在istanbul提供的环境中执行js,instrument的时机在js文件加载时。
  2. browser端:要事先对js文件进行instrument,然后在浏览器里执行这些js,再从全局变量上获取统计数据。
作者:teazean 文章地址: