代码覆盖率命令行工具

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. primes.js 文件的末尾添加console.assert(getPrime(30) == 113); 并运行js primes.js --coverage。由于新的断言添加了对 30 的getPrime 调用(我们的缓存只有 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 的代码覆盖率插件。代码覆盖率高亮显示 用于本示例,但其他插件应该也能类似地工作。

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

请注意,代码覆盖率高亮显示插件默认情况下会在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 文件。

与我们联系