HPCでコード網羅率を調べる

 Cabalのテスト機能は、コード・カバレッジ・ツールであるHPC(Haskell Program Coverage)と連携できます。configureコマンド実行時に、--enable-testsオプションに加えて--enable-library-coverageオプションを指定します。

$ cabal configure --enable-tests --enable-library-coverage
$ cabal build
$ cabal test

 HPCは、二つの機能から構成されています。一つは、「プログラムの実行時に実際に実行された部分と実行されなかった部分に関する情報」を出力できるような形でHaskellプログラムをビルドするようにするGHCの-fhpcオプションです。もう一つは、-fhpcオプション付きでビルドされたプログラムが出力する情報を、人間が理解できる形に加工するhpcコマンドです。

 --enable-testsオプションと--enable-library-coverageオプションを指定すると、Cabalのテスト機能は「プログラムの実行時に実際に実行された部分と実行されなかった部分に関する情報」を、distディレクトリ配下のhpcディレクトリに出力します。

 では、Cabalのテスト機能からHPCを使ってみましょう。

 HPCを使って調べるテスト・プログラムの例として、「libraryでexposed-modules:に指定されているTypeCheckモジュール」をインポートしているTypeCheckTest.hsを使うことにします。ただし、前節のTypeCheckTest.hsはTypCheckモジュールをインポートしているだけで実際にはTypeCheckモジュールの関数や値を利用していないため、HPCを使っても有用な結果は出力されません。そこでTypeCheckモジュールの関数を利用するようにTypeCheckTest.hsの定義を少し変更します。

import TypeCheck hiding (main)

main = do
    putStr $ render $ link "test" (URI "http://example.org")
    putStr "type checking is success.\n"

 Cabalのテスト機能では「プログラムの実行時に実際に実行された部分と実行されなかった部分に関する情報」は、HTML形式で出力されます。具体的には、「dist/hpc/html/<テスト・プログラム名>/*.html」と「dist/hpc/html/<パッケージ名>-<バージョン番号>/*.html」です。

$ cabal configure --enable-tests --enable-library-coverage
~ 略 ~
$ cabal build
~ 略 ~
$ cabal test
Running 1 test suites...
Test suite TypeCheckTest: RUNNING...
Test suite TypeCheckTest: PASS
Test suite logged to: dist/test/Test-0.0-TypeCheckTest.log
Writing: Test-0.0/TypeCheck.hs.html
Writing: hpc_index.html
Writing: hpc_index_fun.html
Writing: hpc_index_alt.html
Writing: hpc_index_exp.html
Test coverage report written to dist/hpc/html/TypeCheckTest/hpc_index.html
1 of 1 test suites (1 of 1 test cases) passed.
Writing: Test-0.0/TypeCheck.hs.html
Writing: hpc_index.html
Writing: hpc_index_fun.html
Writing: hpc_index_alt.html
Writing: hpc_index_exp.html
Package coverage report written to dist/hpc/html/Test-0.0/hpc_index.html

 「dist/hpc/html/<テスト・プログラム名>/*.html」には、<テスト・プログラム名>で示されたテスト・プログラム単体を実行した時の結果が出力されます。「dist/hpc/html/<パッケージ名>-<バージョン番号>/*.html」には、複数のテスト・プログラムを実行した結果を合わせたものが出力されます。この例では、TypeCheckモジュールを使うテストはTypeCheckTestだけなので、「dist/hpc/html/<テスト・プログラム名>/*.html」と「dist/hpc/html/<パッケージ名>-<バージョン番号>/*.html」の内容は全く同じです。

 出力された結果を見てみましょう。hpc_index.htmlでは、トップレベルで定義された関数や値(Top Level Definitions)、ソースコード内の条件分岐の経路(Alternatives)、ソースコード内の式(Expressions)のそれぞれにおいて、どの部分が実際に実行されたかを割合で示します。この割合を「コード網羅率(code coverage)」といいます(参考リンク)。

 このファイルでは、コード網羅率が「実際に実行された数と全体の数」「その比を100分率で示した値」「棒グラフ」で示されています。棒グラフの緑色の部分が実行された割合、赤色の部分が実行されなかった割合です。

 hpc_index.htmlからリンクされた「<パッケージ名>-<バージョン番号>/<モジュール名>.html」では、ソースコードの中で実行されなかった部分が黄色で示されます。

 低いコード網羅率は、ソースコードがきちんとテストされていないことを示すサインです。一般には、テストを追加して網羅率を上げる必要があります。

 ただし、コード網羅率はあくまで目安に過ぎません。コード網羅率が100%だからといって、完全にバグがないことは保証しません。例えば、TypeCheckモジュールのlinkWithTitle関数などの定義を削ればコード網羅率が100%になりますが、それでは本末転倒です。

 コード網羅率だけでは表されない性質の悪い問題や微妙な問題が隠れていることもあります(参考リンク1参考リンク2)。Haskellには、型検査やQuickCheckといったコードの品質を検証するためのツールが用意されています。HPCの結果だけに頼るのではなく、検証したい問題に適したツールを使うようにしましょう。