“覆盖率检测”的实现原理,就这?

开发 前端
覆盖率就是执行过的代码占总代码的比例,比如执行了多少行(Line),执行了多少个分支(Branch),执行了多少个函数(Function),执行了多少条语句(Statement)。

[[429164]]

覆盖率检测是用来判断单测完整性的,jest 和 karma 都提供了这种功能:

 

覆盖率就是执行过的代码占总代码的比例,比如执行了多少行(Line),执行了多少个分支(Branch),执行了多少个函数(Function),执行了多少条语句(Statement)。

用它比上总的数量就是覆盖率,分为行覆盖率、分支覆盖率、函数覆盖率、语句覆盖率等。

看起来是不是很神奇,执行完一遍就能知道覆盖到了哪些代码,其实实现原理比较简单,相信看完这篇文章,你会有“就这?”的感觉。

原理探究

jest 和 karama 都是基于 istanbul 做的覆盖率检测,我们来探究下 istanbul 的实现原理。

测试代码如下: 

我们执行 istanbul 的 instrument 命令:

  1. npx istanbul instrument ./test.js -o ./out.js 

instrument 是指函数插桩,也就是透明的给函数添加一些代码。

为什么要插桩呢?看完生成的代码你就明白了。

我们来格式化一下,把变量名替换下。

这就是转换后的代码,在每一个 statement,每一个 function、每一个 branch 都做了计数,分别是 s、f、b 属性。

上面还有一段代码:

初始化了全局变量 AAA,记录了这些信息:

  • path:路径
  • s:statement 数
  • b:branch 数
  • f:function 数
  • fnMap:function 的开始结束位置信息
  • statementMap:statement 的开始结束位置信息
  • branchMap:branch 的开始结束位置信息

看到这里我们大概就能搞懂覆盖率的原理了,就是对每个 statement、function、branch 都插入一段计数代码,记录在一个全局对象中。

为了不和别的全局变量冲突,这个对象的名字是随机生成的,比如 __cov_5ZoEXQ_Hbo27uXArxdm2oA,这里为了简化改为了 AAA。

我们搞明白了覆盖率就是靠插入计数代码,那怎么做的插桩呢?

函数插桩

函数插桩是基于 AST,找到 statement、function、branch 的 AST,在前面插入插桩代码的 AST。

istanbul 确实也是这么做的。

下面是 istanbul 的源码(只看红线标出的位置就行):

就是通过 esprima(js parser)来把代码 parse 成 AST,然后对 AST 进行插桩。

插桩代码分为两部分,一部分是初始化全局对象的代码,一部分是每个分支、语句、函数的计数代码。

我们分别来看下:

初始化全局对象的代码插桩

istanbul 初始化了全局的 coverState 对象用于统计:

做插桩的时候会记录信息到这个 coverState 中:

最后把 coverState 变成字符串加入到代码里:

那具体的分支、语句、函数的 AST 是怎么插桩的?

分支、语句、函数的插桩

对不同 AST 的插桩,就是遍历过程中根据类型做不同的处理:

然后,具体的插桩就是在前面插入一段 AST:

statement 插桩:

function 插桩:

看到这里,我们就知道了函数插桩的实现原理,就是遍历 AST,在不同的位置插入计数代码的 AST 就可以了。

但是有的同学可能会说了,平时我也没手动生成插桩后的代码啊?用 jest --coverage 跑测试用例自动就做了计数,然后给出覆盖率数据了。

istanbul 是怎么做到透明的插桩的呢?

require hook 实现透明无感知的函数插桩

看过之前一篇 require hook 的魔术那篇文章的小伙伴知道,nodejs 的模块加载是分为 load、extension['.js']、compile 这几步的。

我们只需要重写 extension['.js'] 这一步,就能做到透明的代码转换。

istanbul 也是这么做的:

它就是通过修改了 extension['.js'] 方法,在这里面做了函数插桩,之后执行的代码就是转换过后的了,开发者根本感知不到。

总结

jest 和 karma 都基于 istanbul 实现了覆盖率检测。覆盖率统计的原理就是函数插桩,基于 AST 在代码的 statement、function、branch 处插入计数代码,同时通过 require hook 实现了透明的转换。这样代码一执行就能拿到统计数据,自然就可以算出覆盖率了。

看完之后,是不是觉得:

覆盖率检测的实现,就这?

 

责任编辑:姜华 来源: 神光的编程秘籍
相关推荐

2022-05-31 09:01:18

SwiftApp 项目

2024-04-01 08:26:30

单测覆盖率字节码

2019-09-25 09:20:41

谷歌代码开发者

2022-03-29 11:32:32

单元测试覆盖率框架

2011-11-01 10:10:48

ScriptCover

2023-10-27 08:49:00

JCovOpenJDK

2021-12-25 22:30:27

Chrome DevTJavaScript调试工具

2022-10-21 15:29:32

5G网络

2022-08-25 06:27:39

vivoJaCoCo代码覆盖率

2012-04-11 11:21:57

ibmdw

2019-09-30 10:27:52

变异测试评估

2018-02-27 14:50:20

大数据公厕城市

2024-06-14 12:04:33

2012-09-21 10:30:56

Linux项目代码覆盖率

2011-04-25 09:49:20

代码测试

2016-01-13 10:14:15

WebPHP函数覆盖

2015-11-09 17:56:57

WebPHP函数覆盖

2022-07-22 07:38:31

监控系统

2022-05-13 09:40:51

代码可行应用性能

2021-04-22 06:13:41

Express 中间件原理中间件函数
点赞
收藏

51CTO技术栈公众号