跳至主要内容

Flow 代码覆盖率

coverage 命令提供了一个指标,用于衡量 Flow 对代码各个部分执行的检查量。代码覆盖率高的程序应该能提高你对 Flow 已经检测到所有潜在运行时错误的信心。

决定因素是每个表达式的推断类型中是否存在 any。推断类型为 any 的表达式被认为是未覆盖的,否则被认为是已覆盖的。

为了了解为什么选择这个指标来确定 Flow 的有效性,请考虑以下示例

1const one: any = 1;2one();

这段代码会导致运行时类型错误,因为我们试图对一个数字进行调用。然而,Flow 并没有在这里标记错误,因为我们已经将变量 one 注解为 any。只要涉及 any,Flow 的检查就会被有效地关闭,因此它会静默地允许调用。使用这种不安全的类型使得类型检查器失效,而代码覆盖率指标就是为了解决这个问题,通过报告所有 one 的实例为未覆盖的实例。

设计空间

哪些类型应该被“覆盖”?

上面描述的是一种相当粗粒度的确定覆盖率的方法。可以想象一个标准,如果表达式的任何部分包含 any,例如 Array<any>,就会将表达式标记为未覆盖。虽然这样的指标有一定的价值,但类型的“未覆盖”部分通常会通过对这种类型的值进行各种操作而被覆盖。例如,在以下代码中

1declare const arr: Array<any>;2arr.forEach(x => {});

参数 x 将被标记为未覆盖。此外,在实践中,这样严格的标准会过于嘈杂,而且在运行时计算起来相当昂贵。

联合类型

联合类型是一个例外:类型 number | any 被认为是未覆盖的,即使从技术上讲 any 不是顶层构造函数。联合只是对一组其他类型中的选项进行编码。从这个意义上讲,我们保守地将表达式视为未覆盖的,当至少该表达式的其中一种可能类型会导致有限的检查时。例如,在以下代码中

1let x: number | any = 1;2x = "a";

Flow 允许你将任何东西赋值给 x,这会降低对 x 作为数字使用的信心。因此 x 被认为是未覆盖的。

空类型

从覆盖率的角度来看,一个有趣的类型是 empty 类型。这种类型大致对应于死代码。因此,对类型为 empty 的表达式进行检查会更加宽松,但这是有充分理由的:这段代码在运行时不会被执行。由于清理此类代码是一种常见的做法,因此 Flow 代码覆盖率也会报告类型被推断为 empty 的代码,但会将其与 any 的情况区分开来。

命令行使用

要找出以下内容的文件 foo.js 的覆盖率

1function add(one: any, two: any): number {2  return one + two;3}4
5add(1, 2);

你可以发出以下命令

$ flow coverage file.js
Covered: 50.00% (5 of 10 expressions)

此输出表示该程序的 10 个节点中有 5 个被推断为类型 any。要查看哪些部分未被覆盖,你还可以传递以下标志之一

  • --color:这将在终端上打印 foo.js,并将未覆盖的位置以红色显示。例如 flow coverage --color file.js
  • --json:这将列出所有在标签 "uncovered_locs" 下未覆盖的位置跨度。例如 flow coverage --json file.js

最后,作为死代码的示例,请考虑以下代码

1function f(x: 'a' | 'b') {2  if (x === 'a') {3    // ...4  } else if (x === 'b') {5    // ...6  } else {7    x;8  }9}

最终的 else 子句永远不会被执行,因为我们已经检查了联合的两个成员。因此,x 在该分支中被推断为类型 empty

在该命令的彩色版本中,这些部分将以蓝色显示,在 JSON 版本中,它们将位于 "empty_locs" 字段下。

在多个文件上使用

如果你想一次检查多个文件的覆盖率,Flow 提供了 batch-coverage 命令

$ flow batch-coverage dir/

将报告 dir/ 下每个文件的覆盖率统计信息,以及汇总结果。

请注意,batch-coverage 需要一个非延迟的 Flow 服务器。