2025/06/08
目次
- 0 概要
- 1 基礎
- 2 可視化
- 3 カバレッジ計測の範囲無効
はじめに
ターミナルでカバレッジ計測結果を表示するのはお手軽に状態を確認できて便利である。しかし、このままだと、カバレッジへの対処が若干難しい。
具体的には、どこがカバーされているか(あるいはされていないのか)の切り分けや、多くのファイルが並んでいる場合に、どこから手を付けるのかの判断が難しい。
そこで、カバー状態を、よりコードにフレンドリーに表示・閲覧する手法を2つ紹介する。
サンプルコードを複雑化
準備してあるサンプルコードがmain()
のみのものだったので、カバレッジを可視化する意義が薄いので、少しコードを複雑にしてみる。
type SampleResult<T> = Result<T, Box<dyn std::error::Error>>;
fn main() -> SampleResult<()> {
let args = std::env::args().collect::<Vec<String>>();
let message = greet(args)?;
println!("{message}");
Ok(())
}
fn greet(args: Vec<String>) -> SampleResult<String> {
let name = args.get(1).ok_or("")?;
let age = match args.get(2) {
Some(value) => Some(value.parse::<u32>()?),
None => None,
};
Ok(match age {
Some(age) => format!("Hello, {name}! You are {age} years old."),
None => format!("Hello, {name}!"),
})
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_main() {
assert!(main().is_err());
}
}
サンプルとし、このような構成とした。
type SamplResult
: サンプル内で使いまわす汎用エラー型fn main()
: 実行のエントリポイントのみとして働き、標準出力も行うfn greet()
: 処理の本体。これをTest対象として入力のバリエーションを受け入れられ、メッセージを戻り値にしておく
ここでは、エラーハンドリングなどはかなり簡略化している。
fn test_main()
でのmain()
はargsを読み込むが、引数がない場合はエラーになるのが望ましいので、アサーションを入れておく。
サンプルコードのカバレッジと解釈
$ cargo llvm-cov
info: cargo-llvm-cov currently setting cfg(coverage); you can opt-out it by passing --no-cfg-coverage
Compiling coverage v0.1.0 (/<path>/<to>/<project>/coverage)
Finished `test` profile [unoptimized + debuginfo] target(s) in 0.23s
Running unittests src/main.rs (target/llvm-cov-target/debug/deps/coverage-90ad100d3a119901)
running 1 test
test tests::test_main ... ok
test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
Filename Regions Missed Regions Cover Functions Missed Functions Executed Lines Missed Lines Cover Branches Missed Branches Cover
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
/<path>/<to>/<project>/coverage/src/main.rs 20 11 45.00% 3 0 100.00% 18 8 55.56% 0 0 -
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
TOTAL 20 11 45.00% 3 0 100.00% 18 8 55.56% 0 0 -
まず、サンプルコード更新の期待通りにカバレッジは低下し、45%
となった。実際にTestの処理を追っていくと、greet()
に入ってすぐにargs.get(1)
が失敗するので、続く処理は行われず、カバーされないのが原因である。
Executedの数値が100%
となっているのは、main()
から greet()
が呼び出されているためである。
このシンプルなサンプルにおいては、単純に処理を追うだけで十分カバレッジの状態を把握できる。しかし、複数ファイル・modがあり、さらにそれらをまたがってTestされる場合、完全に脳内でカバー範囲を把握するのは難しい。
そのようなシチュエーションでは、カバレッジの可視化を行うと便利である。
htmlに出力
追加の準備なく、最も簡単に可視化する方法は、HTMLであろう。 llvm-cov
に--html
オプションが組み込まれているので、これを使う。
$ cargo llvm-cov --html
とし、 file:///<path>/<to>/<project>/coverage/target/llvm-cov/html/index.html
をブラウザで開くと、カバレッジが可視化される。
$ cargo llvm-cov --open
とすると、htmlを生成しつつブラウザが自動で開く。
index.html
はクレート全体の各ファイルのカバレッジが表示され、ここは主にterminal表示に近い情報を提供する。
ファイル名をクリックすると、ファイルのソースコードとカバーエリアと通過回数が表示され、通っていない行は赤くハイライトされる。
vscodeで表示
Coverage GuttersというVSCodeの拡張機能を使うと、カバレッジを視覚的に表示できた。
そのためには、lcov.info
としてカバレッジ計測結果を出力する必要がある。
拡張機能をインストールしたあと、
$ cargo llvm-cov --lcov --output-path target/lcov.info
と実行し、 Command Palette (Ctrl+Shift+p
, Cmd+Shift+p
) から Coverage Gutters: Display Coverage
を選択すると、カバレッジが表示される。
Coverage Gutters: Watch
を選択すると、再度上記コマンドを実行したときに自動で表示が更新される。
サンプルコードのTestを改善
上のサンプルコードのTestは不足しており、greet()
はアサーションがないため、カバレッジは特に低い。
そこで、カバレッジの計測をしつつ、Testをいくつか追加していった。
Testの最終形としては、以下のようになり、
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_main() {
assert!(main().is_err());
}
#[test]
fn greet_empty() {
let args = vec![];
assert!(greet(args).is_err());
}
#[test]
fn greet_with_name() -> SampleResult<()> {
let args = vec!["program".to_string(), "Alice".to_string()];
let message = greet(args)?;
assert_eq!(message, "Hello, Alice!");
Ok(())
}
#[test]
fn greet_with_name_and_age() -> SampleResult<()> {
let args = vec!["program".to_string(), "Bob".to_string(), "30".to_string()];
let message = greet(args)?;
assert_eq!(message, "Hello, Bob! You are 30 years old.");
Ok(())
}
}
カバレッジはかなり向上した。
$ cargo llvm-cov
info: cargo-llvm-cov currently setting cfg(coverage); you can opt-out it by passing --no-cfg-coverage
Compiling coverage v0.1.0 (/<path>/<to>/<project>/coverage)
Finished `test` profile [unoptimized + debuginfo] target(s) in 0.23s
Running unittests src/main.rs (target/llvm-cov-target/debug/deps/coverage-90ad100d3a119901)
running 4 tests
test tests::greet_empty ... ok
test tests::greet_with_name_and_age ... ok
test tests::greet_with_name ... ok
test tests::test_main ... ok
test result: ok. 4 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
Filename Regions Missed Regions Cover Functions Missed Functions Executed Lines Missed Lines Cover Branches Missed Branches Cover
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
/<path>/<to>/<project>/coverage/src/main.rs 37 5 86.49% 6 0 100.00% 34 2 94.12% 0 0 -
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
TOTAL 37 5 86.49% 6 0 100.00% 34 2 94.12% 0 0 -
line coverageが100%になっていないのは、 main()
の中のgreet()
が成功したパターンを通っていないため。
Testの追加によってカバレッジが改善していく様子は、このtestコードを上から順に追加していき、都度カバレッジを計測すると差がわかりやすい。
おわりに
使ってみた感覚的には、上に上げた可視化手法はだいたい以下の使い分けができそう。
- html出力 : 広域のカバレッジ計測を一望するのに便利
- vscode表示: カバレッジ改善
目次
- 0 概要
- 1 基礎
- 2 可視化
- 3 カバレッジ計測の範囲無効