コードカバレッジが100%の場合でも、コードに含まれる可能性のあるエラーの種類は何ですか?そのようなエラーの具体的な例または具体的な例へのリンクを探しています。
21 に答える
100%のコードカバレッジを持つことは、想像するほど素晴らしいことではありません。些細な例を考えてみましょう。
double Foo(double a, double b)
{
return a / b;
}
単一の単体テストでも、このメソッドのコードカバレッジは100%に上がりますが、この単体テストでは、どのコードが機能していて、どのコードが機能していないかはわかりません。これは完全に有効なコードである可能性がありますが、エッジ条件(いつb
であるかなど0.0
)をテストしないと、単体テストはせいぜい決定的ではありません。
コードカバレッジは、単体テストによって何が実行されたかを示すだけであり、正しく実行されたかどうかは示しません。これは重要な違いです。コードの行が単体テストによって実行されるからといって、必ずしもそのコードの行が意図したとおりに機能していることを意味するわけではありません。
興味深い議論のためにこれを聞いてください。
コードカバレッジは、コードにバグがないことを意味するものではありません。これは、テストケースがソースコードベースをどの程度カバーしているかについての見積もりです。100%のコードカバレッジは、コードのすべての行がテストされることを意味しますが、プログラムのすべての状態は確かにテストされません。この分野で行われている研究があり、それは有限状態モデリングと呼ばれていると思いますが、それは実際にはプログラムのすべての状態を探索しようとするブルートフォースの方法です。
同じことを行うためのよりエレガントな方法は、抽象解釈と呼ばれるものです。MSR(Microsoft Research)は、抽象解釈に基づいたCodeContractsと呼ばれるものをリリースしました。Pexもチェックしてください。彼らは、アプリケーションの実行時の動作をテストするクリーバーメソッドを本当に強調しています。
私は私に良いカバレッジを与える本当に良いテストを書くことができましたが、そのテストが私のプログラムが持つかもしれないすべての状態を調査するという保証はありません。これは本当に良いテストを書くことの問題であり、それは難しいことです。
コードカバレッジは良いテストを意味するものではありません
え?どんな種類の普通の論理バグだと思いますか?メモリの破損、バッファオーバーラン、単純な古い間違ったコード、テストではなく割り当て、リストは続きます。カバレッジはそれだけであり、すべてのコードパスが実行されていることを通知しますが、正しいことは通知しません。
まだ言及されていないので、コード カバレッジではコードのどの部分にバグが ないかがわからないというスレッドを追加したいと思います。
コードのどの部分がテストされていないことが保証されているかを示すだけです。
1.「データスペース」の問題
あなたの(悪い)コード:
void f(int n, int increment)
{
while(n < 500)
{
cout << n;
n += increment;
}
}
あなたのテスト:
f(200,100);
実世界での使用におけるバグ:
f(200,0);
私のポイント:テストはコードの行の100%をカバーするかもしれませんが、(通常)すべての可能な入力データスペース、つまり入力のすべての可能な値のセットをカバーするわけではありません。
2.自分の間違いに対するテスト
もう1つの典型的な例は、設計で悪い決定を下し、自分の悪い決定に対してコードをテストする場合です。
例:仕様書には「nまでのすべての素数を印刷する」と書かれており、 nを除く nまでのすべての素数を印刷します。そして、あなたのユニットテストはあなたの間違った考えをテストします。
3.未定義の動作
初期化されていない変数の値を使用したり、無効なメモリアクセスを引き起こしたりすると、コードの動作が未定義になります(C ++または「未定義の動作」を想定するその他の言語)。テストに合格することもありますが、現実の世界ではクラッシュします。
..。
実行時の例外が常に発生する可能性があります:メモリがいっぱいになる、データベースまたは他の接続が閉じられないなど...
次のコードを検討してください。
int add(int a, int b)
{
return a + b;
}
このコードは、いくつかの必要な機能を実装できない可能性があります(つまり、エンドユーザーの要件を満たしていない):「100%カバレッジ」は、実装する必要があるが実装されていない機能を必ずしもテスト/検出するわけではありません。
このコードは、すべてではありませんが一部の入力データ範囲で機能する可能性があります(たとえば、aとbの両方が非常に大きい場合)。
最近のIEEEソフトウェアの論文「2つの間違いとエラーのないソフトウェア:告白」で、Robert Glassは、「現実の世界」では、欠落しているロジックまたは組み合わせ論(防御できない)によって引き起こされるバグが多いと主張しました。論理エラー(可能性があります)よりもコードカバレッジツールを使用します。
つまり、コードカバレッジが100%であっても、この種のエラーが発生するリスクがあります。そして、あなたができる最善のことは、あなたが推測したように、より多くのコードレビューを行うことです。
テストにバグが含まれていたり、間違ったことをテストしている場合、コード カバレッジは何の意味もありません。
関連接線として; 次の疑似コード テストを満たす O(1) メソッドを簡単に作成できることを思い出してください。
sorted = sort(2,1,6,4,3,1,6,2);
for element in sorted {
if (is_defined(previousElement)) {
assert(element >= previousElement);
}
previousElement = element;
}
私が考えていた抜け穴を指摘してくれたJon Skeetへのボーナスカルマ
通常、コード カバレッジは、関数内の分岐がいくつカバーされているかを示すだけです。通常、関数呼び出し間で取られる可能性のあるさまざまなパスは報告されません。プログラムのエラーの多くは、メソッド自体にエラーが含まれているためではなく、あるメソッドから別のメソッドへの引き継ぎが間違っているために発生します。この形式のすべてのバグは、100% のコード カバレッジでも存在する可能性があります。
テストのエラー:)
さて、あなたのテストがカバーされたコードで起こることをテストしないなら。たとえば、プロパティに数値を追加するこのメソッドがある場合:
public void AddTo(int i)
{
NumberA += i;
NumberB -= i;
}
テストでNumberAプロパティのみがチェックされ、NumberBはチェックされない場合、100%のカバレッジがあり、テストは合格ですが、NumberBにはエラーが含まれます。
結論:100%の単体テストでは、コードにバグがないことは保証されません。
あなたの製品は技術的には正しいかもしれませんが、顧客のニーズを満たしていません。
他の誰かについては知りませんが、100% のカバレッジにはほど遠いです。「これは決して起こらない」CATCH は、テストでは実行されません (まあ、実行されることもありますが、その後コードが修正されるため、実行されなくなります!)。残念ながら、発生しない CATCH に構文/論理エラーが発生する可能性があることを心配する必要はありません。
引数の検証、別名。ヌル チェック。外部入力を取得して関数に渡すが、有効/null であるかどうかを確認しない場合、100% のカバレッジを達成できますが、何らかの方法で関数に null を渡すと、NullReferenceException が発生します。これは、データベースが提供するものだからです。あなた。
また、算術オーバーフローなど
int result = int.MAXVALUE + int.MAXVALUE;
コード カバレッジは既存のコードのみを対象とするため、コードを追加する必要がある場所を指摘することはできません。
参考までに、Microsoft Pex は、コードを調査し、ゼロ除算、オーバーフローなどの「エッジ」ケースを見つけることで支援を試みます。
このツールは VS2010 の一部ですが、VS2008 にテクニカル プレビュー バージョンをインストールできます。ツールが見つけたものを見つけることは非常に注目に値しますが、IME、それでも「防弾」に至るまでには至りません.
コード カバレッジはあまり意味がありません。重要なのは、動作に影響を与える引数値のすべて (またはほとんど) がカバーされているかどうかです。
たとえば、典型的なcompareToメソッドを考えてみましょう(Javaでは、ほとんどの言語に適用されます):
//Return Negative, 0 or positive depending on x is <, = or > y
int compareTo(int x, int y) {
return x-y;
}
のテストがある限り、compareTo(0,0)
コード カバレッジが得られます。ただし、ここでは少なくとも 3 つのテストケースが必要です (3 つの結果のため)。それでもバグがないわけではありません。また、例外/エラー条件をカバーするテストを追加することも有効です。上記の場合、 をしようcompareTo(10, Integer.MAX_INT)
とすると失敗します。
結論: 動作に基づいて入力を互いに素なセットに分割し、各セットから少なくとも 1 つの入力をテストします。これにより、本当の意味でより多くのカバレッジが追加されます。
また、 QuickCheckなどのツールも確認してください (お使いの言語で利用できる場合)。
ここの回答の多くで述べたように、100% のコード カバレッジがあり、まだバグがある可能性があります。
その上、バグが 0 であっても、コードのロジックが正しくない (要件に一致しない) 可能性があります。コード カバレッジ、または 100% バグがないことは、まったく役に立ちません。
一般的な企業のソフトウェア開発手法は次のようになります。
- 明確に書かれた機能仕様を持っている
- (1) に対して書かれたテスト計画を作成し、ピア レビューを受けさせる
- (2) に対してテスト ケースを作成し、ピア レビューしてもらう
- 機能仕様に照らしてコードを記述し、ピア レビューを受ける
- テスト ケースに対してコードをテストする
- コード カバレッジ分析を行い、より多くのテスト ケースを記述して、適切なカバレッジを実現します。
「100%」ではなく「良い」と言ったことに注意してください。100% のカバレッジを常に達成できるとは限りません。その場合、あいまいなブランチのカバレッジよりも、コードの正確さを達成することにエネルギーを費やすのが最善です。上記のステップ 1 から 5 のいずれかで、さまざまな種類の問題が発生する可能性があります: 間違ったアイデア、間違った仕様、間違ったテスト、間違ったコード、間違ったテスト実行... 要するに、ステップ 6 だけが最も重要なステップではないということです。プロセス。
バグがなく、100% のカバレッジを持つ間違ったコードの具体例:
/**
* Returns the duration in milliseconds
*/
int getDuration() {
return end - start;
}
// test:
start = 0;
end = 1;
assertEquals(1, getDuration()); // yay!
// but the requirement was:
// The interface should have a method for returning the duration in *seconds*.
ほとんど何でも。
コードコンプリートを読みましたか? (StackOverflow は、本当にすべきだと言っているからです。) 第 22 章では、「100% のステートメント カバレッジは良い出発点ですが、十分ではありません」と述べています。この章の残りの部分では、追加する追加テストを決定する方法について説明します。ここに簡単なテイスターがあります。
構造化ベース テストとデータ フロー テストは、プログラムの各ロジック パスをテストすることを意味します。
f=1:g=1/f
A と B の値に応じて、以下の人為的なコードを通る 4 つのパスがあります。100% のステートメント カバレッジは、4 つのパスのうちの 2 つだけをテストすることで達成できますf=0:g=f+1
。しかしf=0:g=1/f
、ゼロ除算エラーが発生します。ifステートメント、whileおよびforループ (ループ本体は実行されない可能性があります)、およびselectまたはswitchステートメントのすべての分岐を考慮する必要があります。If A Then
f = 1
Else
f = 0
End If
If B Then
g = f + 1
Else
g = f / 0
End If
エラーの推測- エラーの原因となることが多い入力の種類に関する情報に基づいた推測。たとえば、境界条件 (1 つのエラーによるオフ)、無効なデータ、非常に大きな値、非常に小さな値、ゼロ、null、空のコレクションなどです。
それでも、他の人が述べたように、要件にエラーがあったり、テストにエラーがあったりする可能性があります。