見出し画像

TotT: テストカバレッジを理解する

くぼぴー / note inc.

こんにちは、kubopです。
Googleにはトイレテスト(TotT)という文化があるようで、テストに関するTipsをトイレに貼り出し、テストに関する知識を全社で共有しているらしいです。

昨今はリモート勤務が広まり、TotTの実施は難しく、SlackやBotを用いてもなかなか浸透するかどうか…
そこで、noteに書きつつ自分が勉強するために、少しずつ読んで内容や、所感を書いてみようと思います。

※ 翻訳・解釈の間違いなどあるかもしれません。
その場合はこっそり教えてください。

もっとテストを書いてほしいのです。そう、あなたです。テストは、コードをリファクタリングするときや他の開発者が機能を追加するときに、 あなたを守るセーフティネットであることはもうおわかりでしょう。また、テストがコードの設計に役立つこともご存知でしょう。

私たちは、この秘密兵器(this secret weapon)を世界中の人々と共有し、私たちのテストへの情熱を広めるとともに、あなた自身やあなたの会社の他の人たちに、この重要なトリックやテクニックを楽しく簡単に学んでもらうことにしました。
このブログで定期的にエピソードを紹介し、PDFを提供しますので、プリントアウトしてご自分のバスルーム、廊下、キッチン、月面基地、秘密の地下要塞、億万長者の創業者のプリウスなど、どこにでも貼り付けてください。

Introducing "Testing on the Toilet"

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

この記事が気に入ったら、サポートをしてみませんか?
気軽にクリエイターの支援と、記事のオススメができます!
くぼぴー / note inc.
note/サーバサイドエンジニアです。 ここ最近はQA活動をしております。 哲学とかそういうのが好きです。 何か新しい考えが浮かんだら記事を書いていきたいです。