TotT: テストカバレッジを理解する
こんにちは、kubopです。
Googleにはトイレテスト(TotT)という文化があるようで、テストに関するTipsをトイレに貼り出し、テストに関する知識を全社で共有しているらしいです。
昨今はリモート勤務が広まり、TotTの実施は難しく、SlackやBotを用いてもなかなか浸透するかどうか…
そこで、noteに書きつつ自分が勉強するために、少しずつ読んで内容や、所感を書いてみようと思います。
※ 翻訳・解釈の間違いなどあるかもしれません。
その場合はこっそり教えてください。
TotT: Understanding Your Coverage Data
コードカバレッジ(テストカバレッジ)は、ソースコードのどの行がテストによって実行されたかを測定するものです。
「コードカバレッジの割合が高いので、私のコードはよくテストされている。」という考えは間違っています。
高いカバレッジは必要条件ではありますが、十分条件ではありません。
よくテストされたコード =========> 高いカバレッジ
よくテストされたコード <===X=== 高いカバレッジ
よくテストされたコードであることと、高いカバレッジであることは別ベクトルの問題という認識です。
最も一般的なカバレッジデータは、ステートメントカバレッジです。(命令網羅)
これは、収集するコストが最も低く、直感的に理解しやすいコードです。
プロダクトコードの特定の行にテストが到達しているかを測定します。
※ ステートメントカバレッジは、実行されたユニークな実行パスの割合を測定するものではありません。
以下のコードは、すべての可能なデータ入力を考慮していません。
このコードを考えてみましょう。
int a = b / c;
これは、b=18、b=6ではカバー出来るのですが、c=0のテストは出来ません。(0で割る事はできない…)
raiseしたエラーをassertする事はできるが…。
[0] pry(main)> 15/0
ZeroDivisionError: divided by 0
ツールによっては、分数カバレッジを提供しないものもあります。
例えば、以下のコードでは、条件aが真のとき、コードはすでに100%のカバレッジを持っています。条件bは評価されなくとも、命令は全て実行したことになります。
if (a || b) {
// do something
}
カバレッジ解析は、存在するコードがどのように実行されたかを知ることができるだけです。
"本来あるべきコード"がどのように実行されたかを知ることはできません。
次の例を見てみましょう。
error_code = FunctionCall();
// returns kFatalError, kRecoverableError, or kSuccess
if (error_code == kFatalError) {
// handle fatal error, exit
} else {
// assume call succeeded
}
このコードでは、3つの返り値が期待されます。
kFatalError
kRecoverableError
kSuccess
このコードは明確な不具合が潜んでいて、
kRecoverableError が返されたときにエラーリカバリーを行うためのコードが欠落しています。
kFatalError と kSuccess という値だけを生成するテストでは、カバレッジは 100%になります。
kRecoverableError のテストケースはカバレッジを上げず、カバレッジの観点からは「冗長」に見えますが、不具合ということがテストを通じてわかります。
カバレッジの正しい解析方法は
カバレッジを意識せず、できる限り網羅的にテストを行う。
カバレッジを最大にするための、最小のテストケースではなく、必要に足るだけのテストケースを書く。
テストから得られたカバレッジ結果を確認し、テストで見逃しているコードを見つける。
予期しないカバレッジパターンを探す。
予期しないカバレッジパターンを見つけたら、テストケースを追加する、というのを繰り返し行う。
もし追加するのが難しいコードであれば、適宜リファクタを検討するのが良い。
🤔
kRecoverableErrorというのは、仮にユーザーに影響がない場合でも明確に不具合として扱うのだろうか…?
あと、テストカバレッジには以下のような種類があるらしい。
ステートメント・カバレッジ(C0)
デシジョンカバレッジ(C1)
コンディションカバレッジ
コンディション/デシジョン・カバレッジ
MC/DC
複合条件カバレッジ(C2)
パスカバレッジ
一方でRubyのgemであるSimpleCovのカバレッジは、以上のどれにも該当していないらしい。
SimpleCov gemはRubyの標準ライブラリであるcoverageを利用していて、
このライブラリでは、コンパイル済みの命令シーケンスが持っているline番号を使っているからだそうな。
Licensed under a Creative Commons
Attribution–ShareAlike 4.0 License
この記事が気に入ったらサポートをしてみませんか?