代码覆盖率命令行工具

GraalVM 提供了一个**代码覆盖率命令行工具**,允许用户记录和分析特定代码执行的源代码覆盖率。

代码覆盖率作为源代码行、函数或语句的覆盖百分比,是理解特定源代码执行的重要指标,并且通常与测试质量(测试覆盖率)相关联。为每行代码提供可视化覆盖率概览,可以向开发者展示哪些代码路径被覆盖,哪些没有被覆盖,从而深入了解执行的特性,例如,为进一步的测试工作提供参考。

以下示例应用程序将用于演示 GraalVM 的代码覆盖率功能。此应用程序定义了一个 getPrime 函数,该函数使用基于埃拉托斯特尼筛法算法的基本素数计算器来计算第 n 个素数。它还有一个稍微有些简单的、包含前 20 个素数的缓存。

  1. 将以下代码复制到一个名为 primes.js 的新文件中。
class AcceptFilter {
    accept(n) {
        return true
    }
}
class DivisibleByFilter {
    constructor(number, next) {
        this.number = number;
        this.next = next;
    }
    accept(n) {
        var filter = this;
        while (filter != null) {
            if (n % filter.number === 0) {
                    return false;
            }
            filter = filter.next;
        }
        return true;
    }
}
class Primes {
    constructor() {
        this.number = 2;
        this.filter = new AcceptFilter();
    }
    next() {
        while (!this.filter.accept(this.number)) {
            this.number++;
        }
        this.filter = new DivisibleByFilter(this.number, this.filter);
        return this.number;
    }
}
function calculatePrime(n) {
    var primes = new Primes();
    var primesArray = [];
    for (let i = 0; i < n; i++) {
        primesArray.push(primes.next());
    }
    return primesArray[n-1];
}
function getPrime(n) {
    var cache = [2,3,5,7,11,13,17,19,23,29,31,37,41,43,47,53,59,61,67,71];
    var n = arguments[0];
    if (n > cache.length) { return calculatePrime(n); }
    return cache[n-1];
}
// TESTS
console.assert(getPrime(1) == 2);
console.assert(getPrime(10) == 29);

请注意,最后几行是应被视为单元测试的断言。

  1. 运行 js primes.js。示例应用程序应该不会打印任何输出,因为所有断言都通过了。但是,这些断言对实现的测试效果如何呢?

  2. 运行 js primes.js --coverage 以启用代码覆盖率。代码覆盖率工具应该为示例应用程序打印如下输出:
    js primes.js --coverage
    --------------------------------------------------------
    Code coverage histogram.
    Shows what percent of each element was covered during execution
    --------------------------------------------------------
     Path               |  Statements |    Lines |    Roots
    --------------------------------------------------------
     /path/to/primes.js |      20.69% |   26.67% |   22.22%
    --------------------------------------------------------
    

    跟踪器会为每个源文件打印一个覆盖率直方图。您可以看到语句覆盖率大约为 20%,行覆盖率大约为 26%,而根覆盖率(“根”一词涵盖函数、方法等)为 22.22%。这表明我们简单的测试在覆盖源代码方面表现不佳。接下来您将找出代码中哪些部分没有被覆盖。

  3. 运行 js primes.js --coverage --coverage.Output=detailed。请准备好接收一个相对冗长的输出。将输出指定为 detailed 将打印所有带有覆盖率注释的源代码行。由于输出可能非常大,建议将此输出模式与 --coverage.OutputFile 选项结合使用,该选项将输出直接打印到文件。我们示例应用程序的输出如下:
js primes.js --coverage --coverage.Output=detailed
--------------------------------------------------------
Code coverage per line of code and what percent of each element was covered during execution (per source)
  + indicates the line is covered during execution
  - indicates the line is not covered during execution
  p indicates the line is part of a statement that was incidentally covered during execution
    for example, a not-taken branch of a covered if statement
--------------------------------------------------------
 Path               |  Statements |    Lines |    Roots
 /path/to/primes.js |      20.69% |   26.67% |   22.22%

  class AcceptFilter {
      accept(n) {
-         return true
      }
  }
  class DivisibleByFilter {
      constructor(number, next) {
-         this.number = number;
-         this.next = next;
      }
      accept(n) {
-         var filter = this;
-         while (filter != null) {
-             if (n % filter.number === 0) {
-                     return false;
-             }
-             filter = filter.next;
          }
-         return true;
      }
  }
  class Primes {
      constructor() {
-         this.number = 2;
-         this.filter = new AcceptFilter();
      }
      next() {
-         while (!this.filter.accept(this.number)) {
-             this.number++;
          }
-         this.filter = new DivisibleByFilter(this.number, this.filter);
-         return this.number;
      }
  }
  function calculatePrime(n) {
-     var primes = new Primes();
-     var primesArray = [];
-     for (let i = 0; i < n; i++) {
-         primesArray.push(primes.next());
      }
-     return primesArray[n-1];
  }
  function getPrime(n) {
+     var cache = [2,3,5,7,11,13,17,19,23,29,31,37,41,43,47,53,59,61,67,71];
+     var n = arguments[0];
p     if (n > cache.length) { return calculatePrime(n); }
+     return cache[n-1];
  }
  // TESTS
+ console.assert(getPrime(1) == 2);
+ console.assert(getPrime(10) == 29);
--------------------------------------------------------

正如输出开头的图例所解释的,被执行覆盖的行前面带有 +。未被执行覆盖的行前面带有 -。部分覆盖的行前面带有 p(例如,当一个 if 语句被覆盖但只执行了一个分支时,则认为另一个分支是偶然覆盖的)。

查看输出,您可以看到 calculatePrime 函数及其所有调用从未被执行。再次查看断言和 getPrime 函数,很明显我们的测试总是命中缓存。因此,大部分代码从未被执行。您可以对此进行改进。

  1. console.assert(getPrime(30) == 113); 添加到 primes.js 文件的末尾,并运行 js primes.js --coverage。由于新添加的断言调用 getPrime 时传入了 30(我们的缓存只有 20 个条目),因此覆盖率将如下所示:
js primes.js --coverage
-------------------------------------------------------
Code coverage histogram.
  Shows what percent of each element was covered during execution
-------------------------------------------------------
 Path               |  Statements |    Lines |    Roots
-------------------------------------------------------
 /path/to/primes.js |     100.00% |  100.00% |  100.00%
-------------------------------------------------------

与其他工具集成 #

代码覆盖率工具提供了与其他工具集成的方式。运行 --coverage.Output=lcov 将以常用的 lcov 格式生成输出,该格式被多种工具(例如 genhtml)用于显示覆盖率数据。查看下一个示例,它展示了如何使用 Visual Studio Code 可视化 Node.js 应用程序的覆盖率。

  1. 将以下代码复制到一个名为 nodeapp.js 的新文件中。
const express = require('express')
const app = express()
const port = 3000

app.get('/', (req, res) => {
  res.send('Hello World!')
})

app.get('/neverCalled', (req, res) => {
  res.send('You should not be here')
})

app.get('/shutdown', (req, res) => {
  process.exit();
})
app.listen(port, () => console.log(`Example app listening on port ${port}!`))
  1. 安装 express 模块依赖项
    $JAVA_HOME/bin/npm install express
    
  2. 启动 Visual Studio Code 并安装一个支持 lcov 的代码覆盖率插件。本示例使用 Code Coverage Highlighter,但其他插件也应该类似工作。

  3. 运行 nodeapp.js 文件,并启用和配置覆盖率
    $JAVA_HOME/bin/node --coverage --coverage.Output=lcov \
    --coverage.OutputFile=coverage/lcov.info \
    nodeapp.js
    

请注意,Code Coverage Highlighter 插件默认在 coverage 目录中查找 lcov.info 文件,因此请将代码覆盖率工具的输出定向到该目录。

  1. 在浏览器中访问 localhost:3000/,然后访问 localhost:3000/shutdown 以关闭应用程序。

  2. 打开 Visual Studio Code,然后打开包含 nodeapp.js 文件和 coverage 目录的文件夹,您应该会看到类似以下的图像:

Visual Studio Code Coverage

如果您希望将 GraalVM 代码覆盖率工具收集的数据与您自己的可视化工具集成,--coverage.Output=json 选项将使输出成为一个包含跟踪器收集的原始数据的 JSON 文件。

联系我们