PR

C++のコードカバレッジをgtestとlcovで可視化する

プログラミング
記事内に広告が含まれています。
スポンサーリンク
この記事はこんな方におすすめ
  • 単体テストを書き始めた
  • コードの品質を上げたい
  • C, C++言語を使っている

 私は普段、gtestとlcovを使って単体テストを書いて、lcovというツールを使ってコードカバレッジを把握しています。

コードカバレッジとはテストによってどれだけ対象のソフトのコードがテスト時に実行されているかを示すソフトウェア品質指標の1つです。

 私の場合、すでにプロジェクトのCI環境で構築されていたものを利用しているので、一度自分で1から作ってより深く理解したいと思います。

 日常的に業務で使う場合は、Github Actionsに組み込んで動かす方が楽かと思いますが、状況によっては手元で高速に動かしたいという場合がありますので、そのような場面で役立つと思います。

実行環境

毎度のことながらWindows11上のWSL2環境のUbunutu 22.04のローカルマシンで実行します。

WSL環境で実施すると、
後々htmlファイルをWebブラウザで表示する際に困るかもしれなので注意です。

環境構築

 どなたでも再現しやすいように、そしてローカル環境を汚さないようにする目的でDockerfileを使ってコンテナ環境で実行するようにします。

Dockerfile作成

Dockerfileは次のようにします。

ubuntu22.04のOSイメージを使って、Cmakeをインストールした後にgoogle testをダウンロードしてビルドしています。

最後にlcovをインストールしています。

apt install lcovでもインストールできるのですが、Docekr build時に

WARNING: apt does not have a stable CLI interface. Use with caution in scripts.

という警告が出るのでapt-getにしました。
上記の警告は無視しても問題ないようですが、念のため。

Docker imageのbuild

上記のDockerfileが存在するディレクトリで

を実行して、Docker imageをlcovという名前でビルドします。

しばらく待つと、

と表示され、lcovという名前とlatestというタグでDocker imageのビルドが完了しました。

docker image lsで作成済みのimageを確認してみると次のように表示されました。

Docker Container起動

 先ほどdocker image lsで確認した際に表示されたIMAGE ID 4e26230c8e7c を指定してdocker runします。

無事、Docker Containerのbashが起動されました。

また、-vオプションを使用して、カレントディレクトリをDocker container内の/appディレクトリにマウントしています。

マウントすることでカレントディレクトリにあるテスト対象のコード等をコンテナ内で扱えることを想定しています。

テスト対象のコード

 今回はC++で計算機クラスを定義してみました。

calculator.hppを次のようにして、

calculator.cppを次のようにしました。

テストコードの実装

test_calculator.cppというファイルを作成して次のように実装しました。

とりあえず、add関数のテストコードを書いてカバレッジが変わる様子を見たいと思います。

ビルドと実行(カバレッジ情報取得)

gtestの実行

先ほどマウントした/appディレクトリに移動して次のようにg++を使ってビルドします。

各オプションの意味は次の通り。

各オプションの意味

-o : 出力される実行ファイルの名前を指定。今回はtestを指定。

-lgtest_main :Google Testフレームワークのmain関数をリンクします。これは、Google Testのエントリーポイントを提供します。

-lgtest :Google Testフレームワークライブラリをリンクします。

ひとまず、これらのオプションでgtestが実行できます。

次のように出力されたtestを実行するとgtestのmain関数が実行されてテスト結果が出力されます。

プロファイリングデータの取得

 さて、今回はlcovを使って単体テストのコードカバレッジを取得することが目的なので、さらにビルド時のオプションを追加します。

 追加するオプションは次の通り。

カバレッジ取得のために追加するオプション

-fprofile-arcs: コードカバレッジを測定するために使用されます。

-ftest-coverage: どの部分のコードが実行されたかを追跡することができます。

因みに上記2つのオプションは–coverageというオプション1つで代用できるようです。
参考:https://gcc.gnu.org/onlinedocs/gcc/Instrumentation-Options.html#Instrumentation-Options

これらのオプションを追加して次のようにビルドしてみます。

するとカレントディレクトリに、次の2つのファイルが生成されました。

test-calculator.gcno
test-test_calculator.gcno

その後、testを実行すると次の2つのファイルが生成されました。

test-calculator.gcda
test-test_calculator.gcda

gcdaとgcnoファイルのそれぞれの内容は次の通りです。

gcdaとgcno

gcda: [概要] 実行時に収集されたカバレッジ情報を含むファイル。
   [内容] プログラムの実行中に、どのブロックや行が実行されたかの情報が記録される。
   [生成タイミング] プログラム実行中。

gcno: [概要] コンパイル時に生成されるカバレッジ情報を含むファイル。
   [内容] コードの各行やブロックがどのように計測されるかの情報が含まれる。
   [生成タイミング] コードをコンパイルするタイミング。

確かに、内容の通り、それぞれのファイルがコンパイル時と実行時にそれぞれ生成されました。

ここまでで、カバレッジ情報が取得できたので、あとは可視化すればいいだけです。

lcovによる可視化

可視化データの作成

次のコマンドを実行して、カバレッジデータを.infoファイルに出力します。

lcovのオプション意味

-c: カバレッジデータを収集することを指定する。captureの略。

-d  : カバレッジデータを収集するディレクトリを指定する。今回はカレントディレクトリ。

-o: 収集されたカバレッジデータを保存する出力ファイルを指定。今回はcoverage.info。

.infoのファイルはバイナリではなく、中身はテキストとして表示できます。

このデータを使ってhtmlを作成して、Webブラウザで表示します。

このコマンドによって、./lcov-outディレクトリにhtml形式のデータを出力できました。

Webブラウザで表示

ここまで来て、WSL上でやっていることを後悔しました。。 Webブラウザですぐに表示できない。

VS codeを使っているので、何かhtml表示のExtentionを利用すれば便利かもしれません。

ひとまず、zipコマンドをインストールして、lcov-outディレクトリをzip化して
ホスト側のWindowsマシンにダウンロードして、Webブラウザで表示しました。

一応、zipのインストールとzip化のコマンドは次の通りです。

さて、Webブラウザでindex.htmlを表示すると次のようになりました。

カバレッジが低いのは想定通りですが、何かおかしい。。

app配下のcalculatorがテスト対象なのに、色々と他のものが混ざっています。

appの中を見るとtestコードのカバレッジもでています。。

ひとまず、calculator.cppの中は意図通り、add関数がテストされたことになっていることが分かります。

フィルタリングの設定

 上記のようにテストコードまで含めた形でカバレッジが出力されるのは、

デフォルトではテストコードも含めた全体のカバレッジデータを取得するからのようです。

さきほど、 test-test_calculator.gcnoのようなファイルが生成された時から、少し気にはなっていましたが、こういうことだったんですね。。

さきほど実行した次のコマンドだけだとテストコードも含んでカバレッジデータを収集してしまいます。

なので、–removeというオプションを使って次のようにtest_がつくファイルを除外してみました。

出力を見ると、意図通り、test_calculator.cppが除外されたようです。

しかし、さきほどのhtmlの結果からすると
テストコード以外にも標準ライブラリなど自分が実装していないコードもカバレッジデータに含まれているようでした。

なので、カバレッジデータ収集対象のコードを指定する方が良さそうです。

ということで、次のように–extractオプションを使って特定のコードのみのカバレッジデータを収集して出力しました。

出力されたcoverage_target.infoからgenhtmlすると、次のように無事、対象のコードのカバレッジデータを可視化できました。

おまけ

 ここまでで、十分ローカルでgtestを実行してlcovによる可視化ができたので、ひとまずは終わってもいいのですが、一応カバレッジを上げてみます。

カバレッジを上げてみる

 次のようにテストコードを書いてカバレッジを上げます。

ブランチカバレッジを表示する

 また、さきほどの結果だとLine Coverageだけ表示されていたので、Branch Coverageも表示するようにします。

Line Coverage(C0)は命令文の網羅率。どれだけの行数がテストできたかを示します。
Branch Coverage(C1)は条件分岐の網羅率。どれだけ条件分岐のパターンをテストできたかを示します。

lcovでカバレッジデータを収集する際のオプションに–rc lcov_branch_coverage=1を追加します。

そしてextractするときも同様にオプションを追加します。

すると、標準出力からでもbranch coverageが確認できました。

この時点でさきほどよりカバレッジが上がったことが分かりますが、htmlでも確認しておきます。

genhtml実行時は–branch-coverageというオプションを付けます。

すると、無事Branchesという行が追加されました。

コードの中身は次のような感じ。

0で割った場合の例外を投げる部分も分岐として扱われていますね。

まとめ

 今回はgtestで単体テストした際のコードカバレッジをlcovで可視化することをWSL上のDockerコンテナ内で行ってみました。

 普段、開発をしているとこの辺りの作業は自動化されているので意識しないですが、
手動で手順を辿ってみると理解が深まるものですね。

 今回使用したコマンドもシェルスクリプトとして書いたり、コードのbuildはCmakeを使うと自動化できてミスもなくなると思います。

以上、皆さんの参考になれば嬉しいです。

最後までお読み頂き、ありがとうございました。

コメント

タイトルとURLをコピーしました