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 服务器。