为什么 Istanbul / tap 代码覆盖率在每个条件路径都被覆盖时报告 switch 语句未被覆盖?

Why does Istanbul / tap code coverage report a switch statement as not covered when each conditional path is covered?

在一个 node.js 应用程序中,我有一个 class 和一个 getter 包含一个大的 switch 语句,与此相同但更大,具有未发布的产品特定值而不是, b, c, 1, 2, 3 等:

Class SomeClass () {
  constructor (someVar) {
    this.someProp = someVar
  }
  get someGetter () {
    switch (this.someProp) {
      case 'a':
      case 'b':
        return 1

      case 'c':
      case 'd':
      case 'e':
        return 2

      case 'f':
        return 3

      case undefined:
        return null

      default:
        return 0
    }
  }
}

然后我有一个这样结构的测试用例,它到达每条可能的路径:

const expectedResults = new Map(Object.entries({
  a: 1,
  c: 2,
  f: 3,
  null: null,
  z: 0
}))

for (const [testCase, expected] of expectedResults) {
  const someInstance = new SomeClass(testCase)
  t.equal(someInstance.someGetter, expected)
}

t.end()

这让我覆盖了所有可能的路径,并且 node tap 覆盖报告中没有危险信号。

悬停,我得到一个(黄色)未覆盖的行,这是 switch 语句本身的行:示例代码中的第 6 行,行:

switch (this.someProp) {

我试过在每个 return 之后添加冗余的 break,但是这些随后(正确地!)标记为未覆盖(因为它们无法访问)。

我想也许它在抱怨,因为它有一个测试,例如,在 returns 1 路径中的 'a' 但在同一路径中没有 'b' .但如果那是问题所在,我不明白为什么它会标记 switch 语句本身,而不是那些特定的行。

当我让测试覆盖所有可能的 case:,而不仅仅是每条路径时,未覆盖的行通知消失了。

由于在我的真实世界代码中有很多很多情况,并且所有 return 路径都经过测试是正确的,而不是大量扩展 expectedResults 映射,我创建了另一个,更简单的测试条件,它采用每个案例的简单数组,并简单地确保每个案例都被识别(不会回落到默认值)。喜欢:

const allCases = ['a', 'b', 'c', 'd', 'e', 'f']

for (const testCase of allCases) {
  const someInstance = new SomeClass(testCase)
  t.notEqual(someInstance.someGetter, 0)
}

通过与默认回退值进行比较,这个测试至少有一些价值,它不仅仅是为了满足 100% 的覆盖率:它确保 switch 中没有可能的情况在未来被删除等(例如,如果switch 语句被重新排序或修改)。