コードカバレッジコマンドラインツール

GraalVMは、ユーザーがコードの特定の実行のソースコードカバレッジを記録および分析できるコードカバレッジコマンドラインツールを提供します。

ソースコードの行、関数、またはステートメントのカバレッジの割合としてのコードカバレッジは、特定のソースコードの実行を理解するための重要な指標であり、一般的にテスト品質(テストカバレッジ)に関連付けられます。コードの個々の行の視覚的なカバレッジの概要を提供することで、開発者はどのコードパスがカバーされているか、カバーされていないかを把握でき、たとえば、さらなるテストの取り組みに役立つ実行の特性に関する洞察が得られます。

以下のサンプルアプリケーションは、GraalVMのコードカバレッジ機能を示すために使用されます。このアプリケーションでは、エラトステネスのふるいアルゴリズムに基づく基本的な素数計算機を使用して、n番目の素数を計算するgetPrime関数を定義しています。また、最初の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ステートメントがカバーされていても、1つの分岐のみが実行される場合、他の分岐は偶然カバーされていると見なされます)。

出力を見ると、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を指定して実行すると、カバレッジデータを表示するために複数のツール(たとえば、genhtml)で使用される一般的なlcov形式で出力が生成されます。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ファイルが出力されます。

お問い合わせ