プログラムを通るすべてのパスがテストされる場合、それはすべてのバグを見つけることを保証しますか?
そうでない場合は、なぜですか?プログラムフローのあらゆる可能な組み合わせを通過し、問題が存在する場合はそれを見つけられないのはどうしてですか。
「すべてのバグ」が見つかる可能性があることをお勧めしますが、おそらくパスカバレッジが実用的ではないため(組み合わせであるため)、これまで経験したことがないためでしょうか。
プログラムを通るすべてのパスがテストされる場合、それはすべてのバグを見つけることを保証しますか?
そうでない場合は、なぜですか?プログラムフローのあらゆる可能な組み合わせを通過し、問題が存在する場合はそれを見つけられないのはどうしてですか。
「すべてのバグ」が見つかる可能性があることをお勧めしますが、おそらくパスカバレッジが実用的ではないため(組み合わせであるため)、これまで経験したことがないためでしょうか。
回答:
プログラムを通るすべてのパスがテストされる場合、それはすべてのバグを見つけることを保証しますか?
番号
そうでない場合は、なぜですか?プログラムフローのあらゆる可能な組み合わせを通過し、問題が存在する場合はそれを見つけられないのはどうしてですか。
可能なすべてのパスをテストしたとしても、すべての可能な値またはすべての可能な値の組み合わせでそれらをテストしていないからです。例(擬似コード):
def Add(x as Int32, y as Int32) as Int32:
return x + y
Test.Assert(Add(2, 2) == 4) //100% test coverage
Add(MAXINT, 5) //Throws an exception, despite 100% test coverage
プログラムのテストがバグの存在を説得力を持って実証するかもしれないが、バグがないことを実証することはできないと指摘されてから20年になります。このよく知られた発言を熱心に引用した後、ソフトウェアエンジニアはその日の順序に戻り、クリソコスミックな精製を改良し続けた昔の錬金術師のように、テスト戦略を改良し続けます。
- EWダイクストラ(強調は追加1988年に書かれたそれは、2つの以上の数十年の今、かなりきています。)
メイソンの答えに加えて、別の問題もあります。カバレッジは、どのコードがテストされたかを示すものではなく、実行されたコードを示すものです。
100%のパスカバレッジを持つテストスイートがあるとします。すべてのアサーションを削除して、テストスイートを再度実行してください。Voilà、テストスイートはまだ100%のパスカバレッジを持っていますが、それは絶対に何もテストしません。
ON ERROR GOTO
Cのように、Visual Basic もパスif(errno)
です。
簡単な例を次に示します。次のソートアルゴリズム(Java)を検討してください。
int[] sort(int[] x) { return new int[] { x[0] }; }
それでは、テストしてみましょう。
sort(new int[] { 0xCAFEBABE });
ここで、(A)この特定の呼び出しがsort
正しい結果を返すこと、(B)すべてのコードパスがこのテストでカバーされていることを考慮してください。
しかし、明らかに、プログラムは実際にはソートされません。
したがって、すべてのコードパスのカバレッジは、プログラムにバグがないことを保証するには不十分です。
abs
数値の絶対値を返す関数を考えてください。ここにテストがあります(Python、テストフレームワークを想像してください):
def test_abs_of_neg_number_returns_positive():
assert abs(-3) == 3
この実装は正しいですが、コードカバレッジは60%しかありません。
def abs(x):
if x < 0:
return -x
else:
return x
この実装は間違っていますが、100%のコードカバレッジが得られます。
def abs(x):
return -x
def abs(x): if x == -3: return 3 else: return 0
ことができますがelse: return 0
、単体テストに合格しても関数は本質的に役に立たないでしょう。
Masonの答えに加えて、プログラムの動作はランタイム環境に依存する場合があります。
次のコードには、Use-After-Freeが含まれています。
int main(void)
{
int* a = malloc(sizeof(a));
int* b = a;
*a = 0;
free(a);
*b = 12; /* UAF */
return 0;
}
このコードは未定義の動作であり、構成(リリース|デバッグ)、OS、およびコンパイラに応じて、異なる動作になります。パスカバレッジがUAFを見つけることを保証するだけでなく、テストスイートは通常、構成に依存するUAFの考えられるさまざまな動作をカバーしません。
別の注意事項として、たとえパスカバレッジがすべてのバグの発見を保証することであったとしても、どのプログラムでも実際に達成できる可能性は低いです。以下を検討してください。
int main(int a, int b)
{
if (a != b) {
if (cryptohash(a) == cryptohash(b)) {
return ERROR;
}
}
return 0;
}
テストスイートがこのためのすべてのパスを生成できる場合、おめでとうございます。あなたは暗号作成者です。
cryptohash
、「十分に小さい」と言うのは少し難しいです。スーパー計算機で完了するのに2日かかるかもしれません。しかし、ええ、int
少しであることが判明するかもしれませんshort
。
他の回答から、テストでの100%のコードカバレッジは100%のコードの正確性を意味するものではなく、テストで発見できるすべてのバグが見つかることも明らかです(テストで発見できないバグは気にしないでください)。
この質問に答える別の方法は、実践からのものです:
現実の世界では、実際には自分のコンピューター上に、100%のカバレッジを与える一連のテストを使用して開発されたソフトウェアの多くの部分がありますが、より良いテストで特定されるバグを含むバグがまだあります。
したがって、含意される質問は次のとおりです。
コードカバレッジツールのポイントは何ですか?
コードカバレッジツールは、テストを怠った領域を特定するのに役立ちます。それは問題ない場合があります(テストを行わなくてもコードは明らかに正しい)、解決できない場合があります(何らかの理由でパスにヒットできない)、または現在または将来の変更後に大きな悪臭を放つバグの場所になる可能性があります。
いくつかの点で、スペルチェックは同等です:何かがスペルチェックを「パス」し、辞書の単語に一致するような方法でスペルミスをする可能性があります。または、正しい単語が辞書にないため、「失敗」する可能性があります。または、それはパスしてまったくナンセンスであることができます。スペルチェックは、校正で見逃した場所を特定するのに役立つツールですが、完全で正しい校正を保証できないのと同様に、コードカバレッジでは完全で正しいテストを保証することはできません。
そしてもちろん、スペルチェックを使用する間違った方法は、提案されたすべての提案された羊と一緒に行くことで有名です。
コードカバレッジでは、特に98%に近い完璧な場合、残りのパスがヒットするようにケースを埋めることが魅力的です。
これは、すべての単語が天気であるか、すべてが適切な単語であるという結び目である、スペルチェックによる縫い付けと同等です。結果はダッキングの混乱です。
ただし、カバーされていないパスに実際に必要なテストを検討する場合、コードカバレッジツールはその役割を果たします。正確さを約束するのではなく、実行する必要がある作業の一部を指摘しています。
パスカバレッジでは、必要な機能がすべて実装されているかどうかはわかりません。機能を除外することはバグですが、パスカバレッジでは検出されません。
問題の一部は、100%のカバレッジは、1回の実行後にコードが正しく機能することのみを保証することです。メモリリークなどの一部のバグは、1回の実行後には明らかにならないか、問題を引き起こす可能性がありますが、時間が経つとアプリケーションに問題が発生します。
たとえば、データベースに接続するアプリケーションがあるとします。おそらく、プログラマーは、1つの方法で、クエリの完了時にデータベースへの接続を閉じるのを忘れます。このメソッドに対していくつかのテストを実行し、その機能にエラーは見つかりませんでしたが、データベースサーバーは使用可能な接続が切れるシナリオに陥る可能性があります。この特定のメソッドは完了時に接続を閉じず、開いている接続はタイムアウトになりました。
times_two(x) = x + 2
、これはテストスイートassert(times_two(2) == 4)
で完全にカバーされますが、これは明らかに明らかにバグのあるコードです!メモリリークの必要はありません:)
他の答えは素晴らしいですが、「プログラムを通るすべてのパスがテストされる」という条件自体が曖昧であることを付け加えたいと思います。
この方法を検討してください:
def add(num1, num2)
foo = "bar" # useless statement
$global += 1 # side effect
num1 + num2 # actual work
end
をアサートするテストを作成するadd(1, 2) == 3
と、コードカバレッジツールにより、すべての行が実行されたことが通知されます。しかし、実際には、グローバルな副作用や役に立たない割り当てについて何も主張していません。これらの行は実行されましたが、実際にはテストされていません。
突然変異テストは、このような問題を見つけるのに役立ちます。ミューテーションテストツールには、コードを「変更」してテストがまだ合格するかどうかを確認するための事前に決められた方法のリストがあります。例えば:
+=
し-=
。その突然変異はテストの失敗を引き起こさないので、テストがグローバルな副作用について意味のあることを主張しないことを証明します。本質的に、突然変異テストはテストをテストする方法です。ただし、可能なすべての入力セットで実際の関数をテストしないように、すべての可能な突然変異を実行することはないため、これも制限されます。
できるすべてのテストは、バグのないプログラムに移行するためのヒューリスティックです。何も完璧ではありません。
まあ... はい、実際、プログラムを「通る」すべてのパスがテストされます。しかし、それは、すべての変数を含む、プログラムが持つことができるすべての可能な状態の空間全体を通るすべての可能なパスを意味します。非常に単純な静的にコンパイルされたプログラム(たとえば、古いFortranの数値計算ツール)であっても、それは少なくとも想像できますが、実行可能ではありません。整数変数が2つしかない場合、基本的に二次元グリッド; 実際、トラベリングセールスマンによく似ています。このような変数がn個あると、n次元の空間を扱うことになります。そのため、実際のプログラムでは、タスクはまったく手に負えません。
さらに悪いことに、深刻な問題については、固定数のプリミティブ変数だけでなく、関数呼び出しでオンザフライで変数を作成するか、可変サイズの変数...またはそのようなものをチューリング完全言語で可能にします。これにより、状態空間は無限次元になり、非常に強力なテスト機器が与えられたとしても、完全なカバレッジのすべての希望が打ち砕かれます。
とは言うものの、実際には物事はそれほど厳しいものではありません。プログラム全体が正しいことを証明することは可能ですが、いくつかのアイデアをあきらめる必要があります。
最初:宣言型のlanguangeに切り替えることを強くお勧めします。命令型言語は、なんらかの理由で常に最も人気がありますが、アルゴリズムと実際の相互作用を組み合わせる方法により、「正しい」という意味を言うことすら非常に難しくなります。
純粋に関数型のプログラミング言語の方がはるかに簡単です。これらは、数学関数の本当の興味深い特性と、実際には何も言えないファジーな実世界の相互作用を明確に区別します。関数の場合、「正しい動作」を指定するのは非常に簡単です。可能なすべての入力(引数の型から)に対して、対応する望ましい結果が得られる場合、関数は正しく動作します。
さて、あなたはそれはまだ手に負えないと言います...結局のところ、すべての可能な引数の空間も一般に無限次元です。確かに、単一の機能については、ナイーブなカバレッジテストでさえ、命令型プログラムで期待することができるよりもはるかに進んでいます!しかし、ゲームを変える信じられないほど強力なツールがあります:普遍的な定量化/ パラメトリック多相性。基本的に、これにより、非常に一般的な種類のデータに関数を書くことができます。データの単純な例で機能する場合、あらゆる入力に対して機能することが保証されます。
少なくとも理論的には。これを完全に証明できるほど一般的な正しい型を見つけるのは簡単ではありません。通常、依存型の言語が必要であり、これらはかなり使いにくい傾向があります。しかし、パラメトリックなポリモーフィズムだけで機能的なスタイルで書くと、すでに「セキュリティレベル」が大幅に向上します。必ずしもすべてのバグを見つける必要はありませんが、コンパイラがそれらを見つけないように十分に隠す必要があります!