コードカバレッジの目標値

単体テストのコードカバレッジの目標値についての記事をいくつか。
一般的なビジネスアプリケーションで100%のカバレッジを目指すことが困難なのは確かだが、だとすればどのくらいを目指すのが妥当なのか?


まずは、「高いカバレッジ」≠「高い品質」ということについてのibm Developerworksの記事
「コード品質を追求する: カバレッジ・レポートに騙されないために」
http://www.ibm.com/developerworks/jp/java/library/j-cq01316/


カバレッジ率が高いということは、単に大量のコードが実行された、ということにすぎません。コードが『充分に』実行されたことにはならないのです。皆さんがコード品質に注目するのであれば、テスト・カバレッジ・ツールがどのように動作するのか、逆にうまく動作しないのはどういう場合かを、よく理解する必要があります。そうすれば、単にカバレッジ率が高いことに満足することなく、こうしたツールを利用して貴重な情報を入手できるはずです。

テスト・カバレッジ・ツールは、ユニット・テストの仕組みの中にぜひ加えるべきものです。テスト・カバレッジを測ることによって、テストという既に有利なプロセスに、深みと精度が加わります。ただし、カバレッジ・レポートを見る場合には、よく注意する必要があります。単にカバレッジ率が高いからと言って、コード品質が保証されることにはなりません。カバレッジ率が高いことは、欠陥を含む可能性が「より低い」というだけであって、必ずしも欠陥が無いことを意味するわけではありません。


つづいて、カバレッジの目標値を70%に設定しているというVisual Studio C++ライブラリのテストチームリードの方のブログ
Using Code Coverage to Improve Orcas Quality
http://blogs.msdn.com/vcblog/archive/2007/01/29/using-code-coverage-to-improve-orcas-quality.aspx


Our code coverage goal was set to 70% block coverage. You may ask, why not higher, or even 100%? This goal was set after carefully considering the variety of ways in which there might be dead or extremely difficult to hit code in binary. Here are just a few of the situations that would make getting higher code coverage hard:

・Internal helper functions or functions from another library may not be exercised fully in the binary. The rest of the blocks in those functions are dead code.

・The virtual inheritance hierarchy could bring lots of dead code from parent classes when its child class is being used in the binary.

・Some error conditions are extremely difficult to hit and hitting those code does not bring too much value to our testing.

・Some compiler generated code is extremely difficult to hit.

・Currently our code coverage tool only supports x86. So 64-bit specific code will be reported as uncovered even if they do.

・There are some legacy code in the source base that may be difficult to factor out.

われわれは、コードカバレッジの目標値を70%に設定しました。みなさんは、「なぜもっと高くないのか?」「なぜ100%ではないのか?」と思うかもしれません。この目標値は、到達不可能なコード(dead code)や、バイナリ状態では実行するのがとても難しいコードが存在する可能性を注意深く分析した結果です。高いコードカバレッジを実現することを阻むいくつかの要因はつぎのとおりです。

・内部的なヘルパー関数や、別のライブリ内の関数は、バイナリ状態では完全に実行することができないことがあります。これらの関数の残りの部分は、到達不可能なコードです。

・継承関係にあるクラスの子クラスがバイナリで使用されている場合、親クラスのコードの多くの部分は到達不可能となってしまいます。

・いくつかのエラーコンディションは、実行するのが大変困難であるにも関わらず、テストの品質に大きな価値をもたらしません。

・いくつかのコンパイラによって自動生成されたコードは、実行するのが大変困難です。

・現在のわれわれのコードカバレッジツールは、x86バイナリのみを対象としているので、64bit固有のコードは、たとえそれがテストされていたとしても、未実行として報告されてしまいます。

ソースコード中に削除することの困難な過去のコードが残っています。


At the beginning of the code coverage effort, some of our binaries had really low code coverage percentages. After almost a year of hard work, we are proud to say that we have reached the 70% block coverage goal for most of the binaries we ship. Some of them even reached 80% or above. Here are what we’ve done in order to achieve this.

・Get rid of dead code in the binaries by examining build options. E.g. we noticed that some of our debug binaries were not built with the /Gy compiler option, which caused the linker not able to get rid of unused functions even when the linker option /opt:ref is used.

・Get rid of dead code in the binaries by using dynamic linking of external libraries whenever feasible.

・Get rid of dead code in the source code. Due to the long history of our source code, there are code that were written many years ago but are no longer used. We did some clean-up of our source code to get rid of those dead code. E.g. Future VC releases will not support Win9x platforms and we’ve got rid of those code that are Win9x specific.

・ For difficult-to-hit error conditions, we used some mechanism called “error injection” to trigger the error conditions so that we can test the behavior of our product under those error conditions. e.g. for memory allocation failure, we intercept the memory allocation routine to call another function that returns an error code indicating that the machine is out of memory.

・ For functions that throw assertions under certain error conditions, we don’t want the test to terminate whenever an assertion occurs because otherwise there would be no way to validate the test result. So we redirect the assertion pop-ups to the debug windows and use a combination of assertion flags/counters to validate that the proper assertions occur under the error conditions.

・Of course, we wrote thousands of new tests to cover those code that were previously ignored. When doing this, we made sure that the new tests not only exercise the code, but also do the proper validation that the product is doing the right thing. If we don’t take care of the validation part, a high code coverage number would be meaningless, or even dangerous because it would give a false sense of testing quality.

コードカバレッジに取り組み始めた当初、いくつかのバイナリはとても低いカバレッジ率でした。1年近くにわたる取り組みの結果、われわれの出荷するほぼすべてのバイナリで70%のカバレッジという目標を達成することができました。80%をこえているものもいくつかあります。これを達成するために実行したのは、いかのようなことです。

・ビルドオプションの設定によって、到達不可能コードをバイナリから取り除く。たとえば、/Gyオプションを使用せずにビルドされたデバッグ用のバイナリは、リンカに/opt:refオプションを設定していても不要な関数を取り除くことができませんでした。

・可能な場合は、外部リンクへの動的リンクを使用して、到達不可能なコードをバイナリから取り除く。

・到達不可能なコードをソースコードから取り除く。ソースコードの長い歴史のせいで、かなり昔に書かれた現在は使われていないコードが存在しています。われわれはこれらのソースコードを整理し、到達不能なコードを取り除きました。たとえば、将来のVCはWin9Xプラットフォームをサポートしなくなるので、Win9X固有のコードを取り除きました。

・実行するのが困難なエラーコンディションについては、「エラーインジェクション」という仕組みを導入し、エラーを再現させ、エラー発生時のプロダクトの挙動をテスト可能としました。たとえば、メモリ割り当てエラーをテストするために、メモリ割り当ての呼び出しを横取りし、メモリ不足を表すエラーコードを返す別の関数を実行するようにしています。

・いくつかの関数は、特定のエラーコンディションでアサーションが発生することがあります。しかし、テスト結果が確認できなくなってしまうので、アサーションが発生してもテストを中止したくない場合があります。そこで、アサーションポップアップをデバッグウィンドウにリダイレクトし、アサーションフラグ/カウンターを使用することで、エラー発生時に適切なアサーションが実行されていることを確認しています。

・もちろん、以前は無視していた部分をカバーするためのテストを大量に作成しています。この作業の過程で新しいテストは、単にコードを実行するだけでなく、製品が正しいことを実行していることを検証するためにあるのだ、ということを確信しました。この「検証」の部分をおろそかにすると高いコードカバレッジは無意味になるだけはなく、テスト品質に関して誤った感覚を与えるという点でむしろ危険であるとさせ言えます。