こんにちは!開発現場を渡り歩いてきた、君の専属の先輩エンジニアだよ。
今日一緒に極めるのは、C言語の「コードカバー率(テストカバレッジ)」、そしてそれをClangの強力なエコシステムである `llvm-cov` と `llvm-profdata` を使って丸裸にする方法だ。
「テストを書いているのに、なぜかバグが減らない」「昔からある謎の関数、怖くて誰も消せない……」
そんな現場の負の遺産に悩んだことはないかい?
カバレッジ計測というと、「テストが何%通ったか」という数字(KPI)だけに目がいきがちだ。だが、アーキテクトの視点から言わせてもらうと、カバレッジツールの真の価値は「実行されていないコード(デッドコードや、誰も通らない異常系パス)を炙り出すこと」にある。
これをマスターすれば、君の書くコード、そしてチームが管理するコードベースから「死に体」が消え去り、驚くほど見通しが良くなる。さあ、一緒にその扉を開けていこう!
—
1. なぜClangの `llvm-cov` なのか?(アーキテクトの視点)
世の中には様々なカバレッジツールがあるが、C言語においてClang標準のプロファイリング機構(Source-based Code Coverage)を使うべき理由は圧倒的だ。
- コンパイラ直結の精密さ: GCCの `gcov` のような行単位の曖昧な計測ではなく、LLVMのIR(中間表現)レベルでブロックや分岐(Branch)単位の正確な実行回数を追跡できる。
- バイナリの軽量さ: 特別なランタイムをリンクしなくても、コンパイラフラグを数個立てるだけでバイナリ自身に計測機能が埋め込まれる。
内部では、コンパイル時にコードの各ブロックに「カウンター(実行された回数を数える変数)」が埋め込まれ、プログラムが正常終了した際(あるいは明期的なダンプ時)に `.profraw` という生データファイルとして吐き出される。これをマージして、ソースコードと突き合わせるのが今回のワークフローだ。
—
2. 基礎セットアップと検証用プログラムの用意
まずは、手元の環境でClangとLLVMのツール群が揃っているか確認しよう。macOSならXcode Command Line Toolsに標準搭載されているし、UbuntuなどのLinuxなら `clang` と `llvm` パッケージをインストールすれば即座に使える。
今回は、あえて「テストが通らないパス(デッドコード)」を含んだ簡単なCプログラムを用意した。これを題材に、`llvm-cov` の真骨頂を見せよう。
ステップ1: 検証用ソースコードの作成
適当な作業ディレクトリを作り、以下のコードを `main.c` として保存してほしい。
// main.c
include
// 正常にテストされる関数
int add(int a, int b) {
return a + b;
}
// あえて誰も呼び出さない「死に体」の関数
int legacy_unused_function(int x) {
printf(“Warning: This should never be called!\n”);
return x 42;
}
// 分岐網羅が完全ではない関数
int check_number(int n) {
if (n > 10) {
return 1; // ここはテストする
} else {
return 0; // ここはテストから漏らす
}
}
int main(void) {
// add関数のテスト
printf(“2 + 3 = %d\n”, add(2, 3));
// check_numberの片側の分岐だけ実行
printf(“Check 15: %d\n”, check_number(15));
return 0;
}
このコードには、
1. 一切呼ばれない関数 `legacy_unused_function`
2. 条件分岐の片側(`n <= 10` の時)が一度も実行されない `check_number`
が潜んでいる。これをツールでどう検知できるか、ワクワクするよね。
---
3. 魔法のコンパイルフラグ:計装(Instrumentation)の実行
カバレッジを測定するためには、コンパイラに対して「実行カウンタを埋め込んでくれ」と指示する必要がある。Clangでは、以下の専用フラグを使用する。
プロファイル生成機能(-fprofile-instr-generate)と
カバレッジマッピング情報付与(-fcoverage-mapping)を同時に指定してコンパイル
clang -fprofile-instr-generate -fcoverage-mapping main.c -o main_cov
【ここがポイント!】
- `-fprofile-instr-generate`: バイナリ実行時に `.profraw` ファイルを生成するためのカウンターコードを挿入する。
- `-fcoverage-mapping`: ソースコードのどの行・どのブロックがどのカウンターに対応しているかの「地図(メタデータ)」をバイナリ内に埋め込む。
コンパイルが無事に完了すると、通常の `main_cov` バイナリができあがる。
—
4. 実行とプロファイルの生成・マージ
それでは、作成したバイナリを実行してみよう。
環境変数でプロファイルデータの出力先を指定(デフォルトでも動くが明示すると安全)
export LLVM_PROFILE_FILE=”main.profraw”
バイナリを実行
./main_cov
実行すると、ターミナルに計算結果が表示されると同時に、カレントディレクトリに `main.profraw` というバイナリのプロファイルデータが出力される。
しかし、このままでは人間が読めない。LLVMが提供するツール `llvm-profdata` を使って、この生データを解析可能な形式に変換(マージ)する。
.profrawファイルを、llvm-profdataでマージ・インデックス化して .profdataファイルへ変換する
llvm-profdata merge -sparse main.profraw -o main.profdata
- `-sparse` オプションを使うことで、実行されなかった膨大なパスのデータ量を最適化し、軽量なプロファイルデータを生成できる。実務の巨大なプロダクトでは必須のテクニックだ。
—
5. `llvm-cov` で「死に体コード」をあぶり出す
さあ、役者は揃った。いよいよ `llvm-cov` を使って、ソースコードの健康診断を行おう。
① ターミナルでリッチに確認する (`show`)
まずは、ソースコードの各行が何回実行されたかを視覚的に表示するコマンドを叩いてみよう。
バイナリ(-instr-profile)とプロファイルデータ、対象のソースコードを指定して表示
llvm-cov show ./main_cov -instr-profile=main.profdata main.c
実行すると、ターミナルに次のような出力が現れる(一部色付きでハイライトされる)。
1| |#include
2| |
3| |// 正常にテストされる関数
4| |int add(int a, int b) {
5| 1| return a + b;
6| |}
7| |
8| |// あえて誰も呼び出さない「死に体」の関数
9| 0|int legacy_unused_function(int x) {
10| 0| printf(“Warning: This should never be called!\n”);
11| 0| return x 42;
12| 0|}
13| |
14| |// 分岐網羅が完全ではない関数
15| 1|int check_number(int n) {
16| 1| if (n > 10) {
17| 1| return 1; // ここはテストする
18| 0| } else {
19| 0| return 0; // ここはテストから漏らす
20| 0| }
21| 1|}
22| |
23| |int main(void) {
24| 1| …
お気づきだろうか?
行番号の左側にある数字が「実行回数(カウンターの数値)」だ。
- `legacy_unused_function` の行はすべて `0` になっている。これが、コードベースに巣食う「死に体(デッドコード)」だ。
- `check_number` 内の `else` 節(19行目)も `0` になっており、テストケースが不足している(分岐網羅が漏れている)ことが一目瞭然でわかる。
② 全体像のサマリーを確認する (`report`)
CI/CDパイプラインに組み込む際は、関数ごとのパーセンテージをサマリーで取得するのが有効だ。
llvm-cov report ./main_cov -instr-profile=main.profdata
実行結果:
Filename Regions Misused Regions Missed Regions Cover Functions Executed
———————————————————————————-
main.c 9 4 4 55.56% 3 66.67%
———————————————————————————-
TOTAL 9 4 4 55.56% 3 66.67%
このように、ファイル単位・関数単位でのカバレッジ率が瞬時に弾き出される。これを閾値として「80%未満ならビルドを失敗させる」といった品質ゲートをCIに組むことが容易になる。
—
6. さらに実戦的へ:HTMLレポートでチーム共有する
CUIだけでなく、リッチなHTMLレポートを出力してチームのメンバーやレビュアーに共有できるのも `llvm-cov` の強みだ。
htmlディレクトリ以下に、ソースコード対応の美しいカバレッジレポートを出力
llvm-cov show ./main_cov -instr-profile=main.profdata -format=html -output-dir=coverage_html
生成された `coverage_html/index.html` をブラウザで開けば、実行されたコードは爽やかなグリーン、一度も通らなかった「死に体コード」は鮮やかなレッドでハイライトされた、極上のコードリーディング環境が手に入る。
—
先輩からのメッセージ
お疲れ様!ここまで手を動かしてくれた君なら、Clangの持つカバレッジ解析能力の深さと、それがもたらす圧倒的な安心感に気づけたはずだ。
「テストを書くため」だけでなく、「自分たちの書いたコードの肥満化を防ぎ、使われていないレガシーコードを勇気を持って削除する」ために `llvm-cov` と `llvm-profdata` を使いこなす。このアプローチを取り入れるだけで、君の書くC言語のコードベースは、驚くほど美しく、メンテナンス性の高いものに生まれ変わる。
毎日のコーディングが劇的に楽に、そしてエキサイティングになる感覚を、ぜひ実際のプロジェクトでも味わってみてほしい。
わからないところがあれば、いつでも僕のところへ聞きに来てくれよな!