テストカバレッジ100%を追求しても品質は高くならない理由と推奨されるカバレッジの目標値について

皆さんは **「カバレッジが高ければ、ソースコードの品質が高い」という誤解** をしていませんか？少なくとも私は今までテストカバレッジ100%を追求していました。「C0/C1カバレッジ100%」がユニットテストの完了条件として含まれているプロジェクトも多いかと思います。

本稿では、「カバレッジが高ければ、ソースコードの品質が高い」という命題がなぜ誤っているのかを論理的に証明し、カバレッジを計測する本当の目的、そして推奨されるカバレッジの目標値について紹介したいと思います。

- 「カバレッジが高ければ、ソースコードの品質が高い」はなぜ間違っているのか？
- カバレッジを計測する本当の目的
- バグを潜在させてしまう恐怖のテストケース・アンチパターン
- カバレッジの目標値は100%にするべきではない
- カバレッジの目標値は何%にするべきなのか？

（テストカバレッジの種類については『[ホワイトボックステストにおけるカバレッジ（C0/C1/C2/MCC）について](https://qiita.com/bremen/items/8b6542467d2a0066e5af)』をご覧ください。）

## 「カバレッジが高ければ、ソースコードの品質が高い」はなぜ間違っているのか？

「カバレッジが高ければ、ソースコードの品質が高い」という命題を検証してみましょう。本稿では、それぞれの表現を以下のように定義します。

- 「カバレッジが高い」=「テストケースが十分に網羅されている」
- 「ソースコードの品質が高い」=「バグが潜在している可能性が低い」

ここで見落としがちなのが、 **「テストケースの品質が高い」 = 「テストケースがバグを適切に検出できる」** という観点です。テストケースがバグを適切に検出できなければ、開発者はソースコードにバグが埋め込まれていることに気がつきません。

![test_coverage.png](https://qiita-image-store.s3.amazonaws.com/0/155486/cd50d4b9-dac7-db53-6f2d-4408eb3de032.png)

上記の図から明らかなように、

「テストケースが十分に網羅されている」かつ 「テストケースがバグを適切に検出できる」 => 「バグが潜在している可能性が低い」

は「真」となり、正しい命題となります。しかし、

「テストケースが十分に網羅されている」 => 「バグが潜在している可能性が低い」

は「偽」です。なぜならば、

「テストケースが十分に網羅されている」かつ「テストケースがバグを適切に検出できない」=> 「バグが潜在している可能性が高い」

という反例があるからです。つまり「カバレッジが高ければ、ソースコードの品質が高い」は間違っており、正しくは **「カバレッジが高く、テストケースの品質が高ければ、ソースコードの品質が高い」** となります。

この時、以下の命題も「真」となります。

「テストケースが十分に網羅されていない」 => 「バグが潜在している可能性が高い」

このことから、

**「カバレッジが低ければ、ソースコードの品質が低い」と言えるが、「カバレッジが高ければ、ソースコードの品質が高い」とは言えない**

という少し直感に反する帰結を得ることができました。

## カバレッジを計測する本当の目的

では、カバレッジは何のために計測するのでしょうか？

前章の図から明らかなように、コードは以下の3つの条件のいずれかに該当します。

1. テストケースが十分に網羅されており、テストケースがバグを適切に検出できる
2. テストケースが十分に網羅されているが、テストケースがバグを適切に検出できない
3. テストケースが十分に網羅されていない

カバレッジを計測する目的は、テストが十分に網羅されていないコードを検出することで、 **条件3\. 「テストケースが十分に網羅されていない」コードを限りなく少なくすること** です。

最も厄介なのは、条件2\. 「テストケースが十分に網羅されているが、テストケースがバグを適切に検出できない」に該当するソースコードです。 

いくらカバレッジが高くても、テストケースの品質が悪ければ、バグが潜在している可能性を低くすることはできません。**条件2.のコードを限りなく少なくするためには、バグを適切に検出できるテストケースを作ることが必要です。**

## バグを潜在させてしまう恐怖のテストケース・アンチパターン

では、バグを適切に検出できずに潜在させてしまうテストケースとはどのようなものなのでしょうか？本章では「[Google Testing Blog: TotT: Understanding Your Coverage Data](https://testing.googleblog.com/2008/03/tott-understanding-your-coverage-data.html?m=1)」に掲載されているアンチパターンを紹介します。

### アンチパターンその1

```java
int a = b / c;
```

例えば`b = 18`および`c = 6`の場合のテストケースのみでC1カバレッジは100%となってしまいますが、`c = 0`の場合のテストケースを設定しなければゼロ除算エラーのバグが埋め込まれているかもしれません。

### アンチパターンその2

```java
if (a || b) {
  // do something
}
```

条件bの真偽に関わらず、条件aが真の場合のテストケースのみでC1カバレッジは100%となってしまいます。

### アンチパターンその3

```java
error_code = FunctionCall();
// returns kFatalError, kRecoverableError, or kSuccess
if (error_code == kFatalError) {
  // handle fatal error, exit
} else {
  // assume call succeeded
}
```

このコードは`kRecoverableError`が返されたときにエラーをリカバリするコードがありません。しかし、`kFatalError`と`kSuccess`のテストケースのみでC1カバレッジは100%となってしまいます。

### その他のアンチパターン

その他にも「フリーライド」や「ハッピーパス」など様々なアンチパターンがあります。アンチパターンの学習は以下のウェブサイトと書籍が大変参考になります。

- [How to Misuse Code Coverage](http://www.exampler.com/testing-com/writings/coverage.pdf)
- [TDD Anti-patterns catalogue at Stack Overflow を簡単に訳してみた - joker1007's diary](http://joker1007.hatenablog.com/entry/20130709/1373365053)
- [tdd - Unit testing Anti-patterns catalogue - Stack Overflow](https://stackoverflow.com/questions/333682/unit-testing-anti-patterns-catalogue)
- [Mastering Software Testing with JUnit 5: Comprehensive guide to develop high quality Java applications](http://amzn.asia/2KX8NcC), Chapter 6\. From Requirements To Test Cases, Test anti-patterns（書籍）

「[JUnit Anti-patterns – Renaissance Developer](https://exubero.com/junit/anti-patterns/)」の記事ではPMDの[JUnit Rules](https://pmd.github.io/pmd-5.8.1/pmd-java/rules/java/junit.html)を使うことによって、いくつかのアンチパターンを取り除けることが紹介されています。

## カバレッジの目標値は100%にするべきではない

「カバレッジを100%にしても、テストケースの品質が低ければコードの品質は低い」ということは前述の議論のとおりです。Sam Guckenheimer氏（Product Owner, Visual Studio Team Services, Microsoft）は「[コード カバレッジ 100% はコストに値するか | Visual Studio](https://www.visualstudio.com/ja/learn/100-code-coverage-worth-cost/)」という記事でカバレッジの目標値について以下のように言及しています。

> _私たちは、 **テストの労力がテスト カバレッジに対して指数関数的に増加する** ものの、**現場から報告されるバグの減少はテスト カバレッジに対して線形的にのみ増加する**ことも発見しました。 このことは、大部分のプロジェクトでは、最適なカバレッジのレベルは 100% をはるかに下回る可能性が高いことを示しています。_

**カバレッジを100%に近づければ近づけるほど、バグ検出の費用対効果は低下します** 。カバレッジを100%にすることに一生懸命になるよりは、テストケースが適切に設定されているかをレビューした方がよっぽど賢明です。

## カバレッジの目標値は何%にするべきなのか？

カバレッジの目標値を100%にするべきではないのであれば、カバレッジの目標値は何%にするべきなのでしょうか？カバレッジの目標値について言及しているいくつかの記事を紹介します。

### Googleの場合

Googleの開発チームでは、カバレッジの目標値を以下のように定めているようです。（「[Analyzing statement coverage at Google](http://swisstestingday.ch/pdf_2015/T1.2-Analyzing-statement-coverage-at-Google.pdf))」より）

カバレッジの種類                     | 目標値
---------------------------- | ----
C0（命令網羅, Statement Coverage) | 85%+

ただし、 **これらの値は努力目標であって、テストの完了条件とはしていない** ようです。

### Martin Fowler氏の場合

カバレッジの種類 | 目標値
-------- | ---------
不明       | 85% - 99%

「[15 software development influencers you should follow in 2018（2018年にフォローすべき15のソフトウェア開発のインフルエンサー）](https://apiumhub.com/tech-blog-barcelona/software-development-influencers/)」にも選出されている著名なソフトウェア技術者のMartin Fowler氏（Chief Scientist, ThoughtWorks）は自身のブログ記事である「[テストカバレッジ](http://bliki-ja.github.io/TestCoverage/)」にて以下のように言及しています。

> _プログラミングの多くの側面と同じく、テストには思慮深さが必要だ。よいテストを選るのに、TDD は有用だが、決して十分ではない。思慮深くテストを実施すれば、テストカバレッジはおそらく80%台後半か90%台になるだろう。100%は信用ならない。_

上記の2つの例から、カバレッジは **テストの完了条件とせずに努力目標とし** 、クリティカルなコードではない限り、 **カバレッジ（C0 / C1）の目標値は85%程度に設定すべきでしょう。**

## まとめ

- **『カバレッジが低ければ、ソースコードの品質が低い』と言えるが、『カバレッジが高ければ、ソースコードの品質が高い』とは言えない。**
- **『ソースコードの品質が高い』条件は『「カバレッジが高い」かつ「テストケースの品質が高い」』** である。
- カバレッジを計測する目的は、テストが十分に網羅されていないコードを検出することで、 **「テストケースが十分に網羅されていない」コードを限りなく少なくすること** である。
- いくらカバレッジが高くても、 **テストケースの品質が悪ければ、バグが潜在している可能性を低くすることはできない。**
- **カバレッジを100%に近づければ近づけるほど、バグ検出の費用対効果は低下する。**
- カバレッジはテストの完了条件とせずに **努力目標** とする。
- クリティカルなコードではない限り、カバレッジ（C0 / C1）の目標値は **85%程度** に設定すべきである。

## その他の参考記事

- 「[Code Coverage Analysis](http://www.bullseye.com/coverage.html)」: Bullseye Testing Technology社が公開しているコードカバレッジ分析について大変良くまとめられたウェブサイト。
- 「[コード品質を追求する: カバレッジ・レポートに騙されないために](https://www.ibm.com/developerworks/jp/java/library/j-cq01316/index.html)」: Netflix社のDirector（2018年3月現在）である[Andrew Glover](https://www.linkedin.com/in/ajglover)氏の記事。