手机版

了解更多关于JavaScript代码覆盖率的信息

时间:2021-08-22 来源:互联网 编辑:宝哥软件园 浏览:

为什么有用?

作为一名JavaScript开发人员,您可能经常会发现自己处于代码覆盖可能有用的情况。例如:

对测试套件的质量感兴趣?重建大型遗产项目?代码覆盖率可以准确地显示代码库的哪些部分已经被覆盖。想快速了解代码库的特定部分是否被覆盖?代码覆盖率可以显示关于应用程序哪些部分已经执行的实时信息,而不是使用console.log进行printf风格的调试或手动代码执行。或者你正在优化速度,想知道应该关注什么?执行时间可以指示关键功能和循环。V8中JavaScript的代码覆盖

今年早些时候,我们在V8中增加了对JavaScript代码覆盖的本地支持。5.9版本的初始版本提供了函数粒度的覆盖(显示已执行的函数),后来扩展到支持v6.2中的块粒度覆盖(同样,它只对单个表达式有效)。

功能粒度(左)和块粒度(右)

对于JavaScript开发人员来说

目前,获取覆盖信息的方式主要有两种。对于JavaScript开发人员来说,Chrome DevTools的Coverage选项卡提供了JS(和CSS)覆盖,并在源代码面板中指出无用的代码。

块覆盖在开发工具覆盖面板中块覆盖。覆盖的行用绿色标记,而未覆盖的行用红色标记。

基于V8覆盖数据的伊斯坦布尔. js报告

给嵌入式

嵌入式和框架作者可以通过直接挂钩到Inspector API获得更大的灵活性。V8提供两种不同的覆盖模式:

1.以尽力覆盖模式收集覆盖信息,以确保在运行时对性能的影响最小,但可能会丢失GC函数的数据。

2.准确的覆盖保证了不会因为GC而丢失数据,用户可以选择接收执行计数而不是二进制覆盖信息;但是,性能可能会受到这种额外开销的影响(有关详细信息,请参见下一节)。精确覆盖可以在功能或块粒度上收集信息。

用于精确覆盖的检查器应用编程接口如下:

侧写。startprecircoverage(call count,detailed)支持覆盖信息收集、可选调用次数(相对于二进制覆盖)和块粒度(相对于函数粒度);Profiler.takePreciseCoverage()返回收集到的覆盖率信息,包括源代码范围列表和相关的执行时间;Profiler.stopPreciseCoverage()禁用收集并释放相关的数据结构。检查员协议之间的通信可能如下:

//嵌入器指示V8开始收集精确的覆盖范围。{“id”: 26,“方法”:“profiler . startprecisecoverage”,“params”: {“call count”: false,“detailed”: true } }//Embeder请求覆盖率数据(自上次请求以来的增量)。{ 'id': 32,'方法' : ' profiler . takecureaverage ' }//回复包含嵌套源范围的集合。{ 'id': 32,' result ' : { ' result ' :[{ ' functions ' :[{ ' function name ' : ' fib ',' isBlockCoverage ' : true,//Block粒度。范围' : [ //嵌套范围的数组。{'startOffset': 50,//Byte offset,含。endOffset ' : 224,//字节偏移量,独占。count': 1}、{'startOffset': 97、' endOffset': 107、' count': 0}、{'startOffset': 134、' endOffset': 144、' count ' : 0 0 }、{'startOffset': 192、' endOffset': 223、' count ' 3: }、]}、' scriptId{'id':37,Method ' : ' profiler . stopplicisecoverage ' }同样,Profiler.getBestEffortCoverage()也可以用于尽力而为的覆盖。

幕后细节

如前一节所述,V8支持两种主要的代码覆盖模式:尽力而为和精确覆盖。有关其实施的概述,请继续阅读。

设法掩盖

尽力而为和精确覆盖模式都重用了其他V8机制,其中第一个称为调用计数器的机制。每次我们通过V8的Ignition解释器调用一个函数,我们都会把它的调用计数器加到函数的反馈向量上。随着函数变得更加频繁并通过优化编译器得到改进,此计数器用于帮助内联函数的内联决策;现在,我们也依靠它来报告代码覆盖率。

第二种重用机制建立了函数的源代码范围。当报告代码覆盖率时,调用计数需要与源文件中的相关范围相关联。例如,在下面的例子中,我们不仅需要报告函数f已经执行了一次,还需要包括从第1行到第3行的f的源代码范围。

函数f(){ console . log(' Hello World ');} f();我们再次感到幸运的是,我们可以在V8中重用现有的信息。因为Function.prototype.toString需要知道函数在原始文件中的位置才能提取适当的子字符串,所以函数已经知道它们在源代码中的开始和结束位置。

在收集最佳覆盖率时,这两种机制简单地结合在一起:首先,我们通过遍历整个堆来找到所有幸存的函数。对于每个可见函数,我们报告调用次数(存储在反馈向量中,我们可以从函数中访问)和源范围(方便地存储在函数本身中)。

请注意,因为无论是否启用覆盖,调用计数都会得到维护,所以尽力而为的覆盖不会引入任何运行时开销。它也不使用专用的数据结构,因此不需要显式启用或显式禁用。

那么为什么这种模式被称为尽力而为,它的局限性是什么?垃圾收集器可能会释放超出范围的函数。这意味着相关的调用计数会丢失,其实我们完全忘记了这些功能曾经存在过。因此,“最佳服务”:即使我们尽了最大努力,收集到的覆盖信息也可能不完整。

精确覆盖(功能粒度)

与尽力而为模式相比,精准覆盖可以确保提供的覆盖信息完整。为了实现这个目标,我们将在启用精确覆盖后,将所有反馈向量添加到V8的根引用集中,从而防止GC回收它。虽然这确保了信息不会丢失,但它通过人为地保持对象的活动增加了内存开销。

精确覆盖模式还可以提供执行计数。这为精确覆盖的实现增加了另一个技巧。回想一下,每次通过V的解释器调用函数时,调用计数器都会递增,一旦函数访问频率过高,这些函数就可以升级优化。但是,优化后的函数不再增加其调用计数器,因此必须禁用优化后的编译器,以保持其报告的执行时间准确。

精确覆盖(块粒度)

块粒度覆盖必须报告精确到独立表达式级别的覆盖。例如,在下面的代码中,块覆盖可以检测到条件表达式的else分支: c从未被执行,而函数粒度覆盖只知道函数f(作为一个整体)被覆盖。

函数f(a){返回a?b : c;}f(真);您可能记得,在前面的部分中,我们已经提供了V8中的函数调用数量和源代码范围。不幸的是,这不适合块覆盖的场景。我们必须实现一种新的机制来收集执行时间和它们对应的源代码范围。

第一个方面是源代码范围:假设我们有一个特定块的执行计数,我们如何将它们映射到源代码的一部分?因此,我们在解析源文件时需要收集相关的位置信息。V8在块覆盖之前已经在某种程度上做到了这一点。一个例子是如上所述的由Function.prototype.toString触发的函数范围的集合。

另一个例子是用于构造错误对象回溯的源代码位置。但这些不足以支持区块覆盖。前者只适用于函数,而后者只保存位置信息(如if-else语句的if标记的位置),而不保存源代码范围。

因此,我们必须扩展解析器来收集源代码范围。为了演示,假设我们使用了一个if-else语句:

If (cond) {/*然后分支。*/} else {/* else分支。*/}当块覆盖被启用时,我们收集then和else分支的源代码范围,并将它们与解析的IfStatement AST节点相关联。其他相关的语言结构也是如此。

在解析期间收集源代码范围集之后,第二个方面是在运行时跟踪执行计数。这是通过在生成的字节码数组的关键位置插入一个新的特殊IncBlockCounter字节码来实现的。在运行时,IncBlockCounter字节码处理程序只添加相应的计数器接口(可通过函数对象访问)。

在上面的if-else语句的例子中,这样的字节码将被插入三个位置:紧接在then分支的主体之前,紧接在else分支的主体之前,以及紧接在if-else语句之后(因为分支中可能有非本地控制,所以需要连续的计数器)。

最后,报告块粒度覆盖类似于报告功能粒度。但是除了调用计数(来自反馈向量),我们现在报告感兴趣的源范围的集合及其块计数(存储在函数被挂起的辅助数据结构中)。

如果想了解更多V8中代码覆盖后的技术细节,请参考覆盖和块覆盖设计文档。

摘要

我们希望您喜欢本文中对V8本机代码覆盖支持的简要介绍。

以上就是本文的全部内容。希望对大家的学习有帮助,支持我们。

版权声明:了解更多关于JavaScript代码覆盖率的信息是由宝哥软件园云端程序自动收集整理而来。如果本文侵犯了你的权益,请联系本站底部QQ或者邮箱删除。