在 Node.js 中收集代码覆盖率

Node.js 通过其测试运行器为代码覆盖率提供了内置支持,可以使用 --experimental-test-coverage 标志启用。

如果使用 run() API,则必须将 coverage 选项设置为 true。有关 run() API 的更多信息,请参阅 node:test 文档

什么是代码覆盖率?

代码覆盖率是测试运行器的一项指标,用于衡量程序源代码在测试期间被执行的程度。它揭示了代码库的哪些部分被测试了,哪些没有,有助于找出测试套件中的空白。这确保了对软件进行更全面的测试,并最大限度地降低了未被发现的错误的风险。通常以百分比表示,代码覆盖率越高表示测试覆盖越彻底。有关代码覆盖率的更详细解释,您可以参阅维基百科“代码覆盖率”条目

基本覆盖率报告

让我们通过一个简单的例子来演示代码覆盖率在 Node.js 中是如何工作的。

注意:本示例以及本文件中的所有其他示例均使用 CommonJS 编写。如果您不熟悉此概念,请阅读 CommonJS 模块文档。

function (, ) {
  return  + ;
}

function () {
  return  % 2 === 0;
}

function (, ) {
  return  * ;
}

. = { , ,  };

在模块中,我们有三个函数:addisEvenmultiply

在测试文件中,我们正在测试 add()isEven() 函数。请注意,multiply() 函数未被任何测试覆盖。

要在运行测试时收集代码覆盖率,请参见以下代码片段

node --experimental-test-coverage --test main.test.js

运行测试后,您将收到一份如下所示的报告

✔ add() should add two numbers (1.505987ms)
✔ isEven() should report whether a number is even (0.175859ms)
ℹ tests 2
ℹ suites 0
ℹ pass 2
ℹ fail 0
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 59.480373
ℹ start of coverage report
ℹ -------------------------------------------------------------
ℹ file         | line % | branch % | funcs % | uncovered lines
ℹ -------------------------------------------------------------
ℹ main.js      |  76.92 |   100.00 |   66.67 | 9-11
ℹ main.test.js | 100.00 |   100.00 |  100.00 |
ℹ -------------------------------------------------------------
ℹ all files    |  86.96 |   100.00 |   80.00 |
ℹ -------------------------------------------------------------
ℹ end of coverage report

覆盖率报告详细说明了您的代码有多少被测试所覆盖

  • 行覆盖率:测试期间执行的代码行百分比。
  • 分支覆盖率:被测试的代码分支(如 if-else 语句)的百分比。
  • 函数覆盖率:在测试期间被调用的函数百分比。

在此示例中

  • main.js 显示 76.92% 的行覆盖率和 66.67% 的函数覆盖率,因为 multiply() 函数未被测试。未覆盖的行 (9-11) 对应于此函数。
  • main.test.js 在所有指标上都显示了 100% 的覆盖率,这表明测试本身已完全执行。

包含和排除

在开发应用程序时,您可能会遇到需要排除某些文件或代码行的情况。

Node.js 提供了处理此问题的机制,包括使用注释来忽略特定的代码部分以及使用命令行排除整个模式。

使用注释

function (, ) {
  return  + ;
}

function () {
  return  % 2 === 0;
}

/* node:coverage ignore next 3 */
function (, ) {
  return  * ;
}

. = { , ,  };

使用修改后的 main.js 文件报告覆盖率时,报告现在将显示所有指标均为 100% 覆盖。这是因为未覆盖的行 (9-11) 已被忽略。

有多种方法可以使用注释来忽略代码部分。

function (, ) {
  return  + ;
}

function () {
  return  % 2 === 0;
}

/* node:coverage ignore next 3 */
function (, ) {
  return  * ;
}

. = { , ,  };

这些不同的方法都将生成相同的报告,所有指标的代码覆盖率均为 100%。

使用命令行

Node.js 提供了两个命令行参数,用于管理在覆盖率报告中包含或排除特定文件。

--test-coverage-include 标志(在 run() API 中为 coverageIncludeGlobs)将覆盖范围限制为与提供的 glob 模式匹配的文件。默认情况下,会排除 /node_modules/ 目录中的文件,但此标志允许您明确地包含它们。

--test-coverage-exclude 标志(在 run() API 中为 coverageExcludeGlobs)会从覆盖率报告中忽略与给定 glob 模式匹配的文件。

这些标志可以多次使用,当两者同时使用时,文件必须遵守包含规则,同时也要避开排除规则。

.
├── main.test.js
├── src
│   ├── age.js
│   └── name.js

在上面的报告中,src/age.js 的覆盖率不甚理想,但使用 --test-coverage-exclude 标志(在 run() API 中为 coverageExcludeGlobs),可以将其完全从报告中排除。

node --experimental-test-coverage --test-coverage-exclude=src/age.js --test main.test.js

我们的测试文件也包含在此覆盖率报告中,但我们只想要 src/ 目录中的 JavaScript 文件。在这种情况下,可以使用 --test-coverage-include 标志(在 run() API 中为 coverageIncludeGlobs)。

node --experimental-test-coverage --test-coverage-include=src/*.js --test main.test.js

阈值

默认情况下,当所有测试都通过时,Node.js 会以代码 0 退出,表示执行成功。但是,可以配置覆盖率报告,在覆盖率不达标时以代码 1 退出。

Node.js 目前支持为所有三种覆盖率设置阈值

如果您想要求前一个示例的行覆盖率 >= 90%,您可以使用 --test-coverage-lines=90 标志(在 run() API 中为 lineCoverage: 90)。

node --experimental-test-coverage --test-coverage-lines=90 --test main.test.js