Revisando la cobertura del código JavaScript

¡Acostúmbrate a escribir juntos! Este es el décimo día de mi participación en el "Nuggets Daily New Plan · April Update Challenge", haz clic para ver los detalles del evento .

Dirección original: Replanteamiento de la cobertura de prueba de JavaScript

Este artículo es de Benjamin Coe , quien es el gerente de producto de npm y el mantenedor central de yargs y estambul.

Hola a todos, soy Xiaoyu, un agricultor de códigos. Recientemente, vitest se ha utilizado para reemplazar a jest en varios proyectos, y la cobertura de la prueba se ha mejorado considerablemente. Entonces, cuando fui a verificar la información para comprender la diferencia entre la cobertura de la prueba vitest y jest, vi algunas publicaciones de blog interesantes. Este artículo describe primero las ventajas y desventajas de dos métodos de compilación de puntos ocultos y cobertura de código basada en V8, y luego describe la búsqueda de cobertura de código de Benjamin Coe.

Ahora es posible configurar una variable de entorno NODE_V8_COVERAGEpara que apunte a para generar cobertura de código. La herramienta c8 puede generar una hermosa información de informes superponiendo datos.

Historia de la cobertura de la prueba

En el código JavaScript, la historia se ha recopilado a través de trucos inteligentes. Herramientas como Istanbul y Blanket recopilan analizando el código JavaScript y enterrando puntos, de modo que la lógica original no se vea afectada. por ejemplo:

function foo (a) {
  if (a) {
  // do something with 'a'.
  } else {
  // do something else.
  }
}
复制代码

Reescribir como:

function foo(a) {
  cov_2mofekog2n.f[0]++;
  cov_2mofekog2n.s[0]++;
  if (a) {
    // do something with 'a'.
    cov_2mofekog2n.b[0][0]++;
  } else {
    // do something else.
    cov_2mofekog2n.b[0][1]++;
  }
}
复制代码

cov_2mofekog2n.f[0]++Indica que se ejecutó la función foo, cov_2mofekog2n.s[0]++que se llamó a la instrucción de esta función e Indica quecov_2mofekog2n.b[0][0]++ se ejecutó la rama. cov_2mofekog2n.b[0][1]++En base a los datos anteriores, se genera un informe.

El enfoque anterior funciona, pero también tiene algunas desventajas:

  • 类似 istanbul 这样的工具需要跟上不断发展的 JavaScript 语言,经常会出现跟不上语言特性不同步的情况,比如这个 issue 就是不同步导致的;
  • 在程序每一行都埋点会显著地影响性能;
  • 不在特意修改代码行为上,部分代码很难被收集,比如 hoist statement counter for class variables

我希望能够有一种更好的方式收集代码覆盖率……

V8 中的代码覆盖率

在 Node.js 支持 ESM 后,istanbul 出现了问题。Bradley 重写了 Node.js 的加载机制去支持 ESM 导致不再支持 require 的钩子,这导致 istanbul 很难检测到 ESM 模块已被加载并对其进行检测。我提出这个问题之后,Bradley 给出了另一个建议:

如果利用 V8 新的内置覆盖功能会怎样?

使用直接内置在 V8 引擎中的覆盖可以解决基于转译的代码覆盖方法所面临的许多缺点。好处是:

  • V8 没有使用计数器来检测源代码,而是将计数器添加到从源代码生成的字节码中。这使得计数器改变程序行为的可能性大大降低;
  • 字节码中引入的计数器不会像在源代码的每一行中注入计数器那样对性能产生负面影响;
  • 一旦新的语言特性被添加到 V8 中,它们就会立即被覆盖。

我开始研究使用 Node.js 的 inspector 模块直接从 V8 收集覆盖率;有一些小问题:

  • inspector 的时间问题使得只能检测函数的覆盖率(无法收集块级语句的覆盖率:if 语句、while 语句、switch 语句);
  • 块覆盖缺少一些功能:|| 表达式,&& 表达式;
  • 让 inspector 启动并运行的步骤过于复杂。您需要启动您的程序,启用 inspector,连接到它,然后转覆盖率报告。

撇开这些挑战不谈,通过 inspector 使用 V8 的覆盖范围感觉很有希望。

证明想法

我联系了 V8 团队的 Jakob Gruber ,就我看到的将 V8 覆盖率与 Node.js 集成的错误联系起来。谷歌的人们也很高兴看到 Node.js 中的覆盖支持,并立即着手解决这个问题。

在与几位 V8 维护人员讨论后,我们确定实际上存在一种启用块级覆盖的机制:

  • 需要使用 --inspect-brk 标志启动程序,以便 inspector 立即终止执行;
  • 需要启用覆盖范围;
  • 需要运行 Runtime.runIfWaitingForDebugger 来启动程序执行;
  • 需要监听事件 Runtime.executionContextDestroyed,此时可以输出覆盖率。

我测试了上述方法,它奏效了!

接下来我问 Jakob 是否可以参与并开始在 V8 中实现一些缺失的覆盖功能。在 V8 团队几个人的耐心帮助下,我实现了对 ||&& 表达式的支持。

此时,我们已经输出了详细的 V8 覆盖率信息,但没有简单的方法来输出人类可读的报告。编写了两个 npm 模块来促进这一点:

  • v8-to-istanbul,它将 V8 格式覆盖输出转换为 istanbul 格式。
  • c8,它将整个 inspector 步骤整合到一个命令中,因此您可以通过简单地运行 c8 node foo.js 来收集覆盖率。

利用这些新库,我们终于能够看到覆盖率报告!

这是一个激动人心的里程碑,但我仍然不满意。原因如下:

  • inspector 步骤继续变得复杂;
  • 根据程序退出的方式,例如,如果 process.exit(0) 被调用,则无法转储覆盖率报告;
  • 我们使用的方法要求我们等待 inspector 启动并通过套接字连接到它;这很慢,感觉不雅。

Node 核心实现

我顿悟了,如果可以将 Node.js 隔离在一个总是丢弃覆盖范围的模式中呢?

  • Esto significa que no es necesario que otro proceso se conecte a la sesión del inspector e inicie el rastreo de cobertura;
  • Esto nos permitirá detectar mejor cuándo se apaga Node.js para que podamos capturar los eventos process.exit(0) y process.kill.

En una conversación con Anna Henningsen , resulta que la implementación del inspector de Node.js coincide con mis pensamientos:

  • Inspector siempre se ejecuta en la mayoría de los entornos, solo que sin la interfaz websocket habilitada;
  • Hay un protocolo de inspector interno disponible para interactuar con el inspector sin crear una conexión de socket.

Emocionado, confirmé la implementación de la cobertura de prueba V8 como una característica de Node.js. Esto es lo que parece:

  • En Node.js >=10.10.0, ahora puede establecer la variable de entorno NODE_V8_COVERAGE en un directorio, lo que hará que el informe de cobertura de V8 se emita en esta ubicación;
  • La herramienta c8 ahora simplemente habilita la variable de entorno NODE_V8_COVERAGE, usa V8 para cubrir los datos y genera buenos informes.

cómo utilizar

Ahora, puede usar el informe de cobertura integrado de Node.js siguiendo estos pasos:

  1. Asegúrese de haber actualizado a Node.js 10.10.0;
  2. Instale la herramienta c8, que se puede utilizar para convertir la salida de cobertura de V8 en un informe legible;
  3. Ejecute su aplicación usando c8, por ejemplo, c8 node foo.js.

Supongo que te gusta

Origin juejin.im/post/7084973940069629965
Recomendado
Clasificación