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

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

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

什么是代码覆盖率?

代码覆盖率是测试运行器的一项指标,用于衡量在测试期间执行的程序源代码量。它揭示了代码库中哪些部分经过了测试,哪些部分没有经过测试,从而有助于查明测试套件中的差距。这确保了对软件进行更全面的测试,并最大限度地降低了未检测到错误的风险。通常以百分比表示,较高的代码覆盖率百分比表示更彻底的测试覆盖率。有关代码覆盖率的更详细说明,您可以参考 “代码覆盖率”维基百科文章

基本覆盖率报告

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

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

function add(a, b) {
  return a + b;
}

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

function multiply(a, b) {
  return a * b;
}

module.exports = { add, isEven, multiply };

在该模块中,我们有三个函数: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 提供了处理此问题的机制,包括使用注释来忽略特定代码段以及使用 CLI 来排除整个模式。

使用注释

function add(a, b) {
  return a + b;
}

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

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

module.exports = { add, isEven, multiply };

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

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

function add(a, b) {
  return a + b;
}

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

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

module.exports = { add, isEven, multiply };

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

使用 CLI

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

--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