プログラムのすべてのパスがテストされる場合、それはすべてのバグの発見を保証しますか?
そうでない場合、なぜそうではないのですか?プログラムフローのすべての可能な組み合わせを調べ、問題が存在する場合はそれを見つけられないのはなぜですか。
「すべてのバグ」を見つけることはできませんが、パスのカバレッジが実用的ではないため(組み合わせであるため)、これまで経験したことがないためでしょうか。
注: この記事 は、私が考えるカバレッジタイプの簡単な要約を提供します。
プログラムのすべてのパスがテストされる場合、それはすべてのバグの発見を保証しますか?
番号
そうでない場合、なぜそうではないのですか?プログラムフローのすべての可能な組み合わせを調べ、問題が存在する場合はそれを見つけられないのはなぜですか。
すべての可能なpathsをテストしても、すべての可能なvaluesまたはすべての可能な値の組み合わせ。例(疑似コード):
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年になります。これを十分に引用した後熱心に公表された発言で、ソフトウェアエンジニアはその日の秩序に戻り、彼のクリソコスミック浄化を洗練し続けた昔の錬金術師のように、彼のテスト戦略を洗練し続けています。
- E。W. Dijkstra (Emphasisが追加されました。1988年に書かれました。今では20年以上経ちます。)
メイソンの答え に加えて、別の問題もあります。カバレッジはnotがどのコードがテストされたかを通知し、それは通知しますどのコードが実行されたか。
パスカバレッジが100%のテストスイートがあるとします。ここですべてのアサーションを削除し、テストスイートを再度実行します。ちなみに、テストスイートのパスカバレッジは100%ですが、テストはまったく行われません。
丸めの簡単な例を次に示します。次の並べ替えアルゴリズム(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
他の回答から、テストの100%のコードカバレッジは100%のコードの正確さを意味しないこと、またはテストで発見されたすべてのバグが発見されることは明らかです(テストでキャッチされないバグを気にしないでください)。
この質問に答える別の方法は、練習からのものです:
現実の世界には、実際にあなた自身のコンピューター上に、100%のカバレッジを提供する一連のテストを使用して開発され、それでもバグを含む多くのバグがあるソフトウェアが多数ありますより良いテストが特定するであろうこと。
したがって、必要な質問は次のとおりです。
コードカバレッジツールのポイントは何ですか?
コードカバレッジツールは、テストを怠った領域を特定するのに役立ちます。それは問題ないかもしれません(テストがなくてもコードは明らかに正しいです)解決できない場合があります(何らかの理由でパスにアクセスできない場合があります)。または、現在または将来の変更の後に、大きな悪臭のあるバグの場所である可能性があります。
いくつかの点でスペルチェックは同等です。何かがスペルチェックに「合格」し、辞書内の単語と一致するような方法でスペルミスされる可能性があります。または、正しい単語が辞書にないため、「失敗」する可能性があります。または、合格してまったくのナンセンスになることもあります。スペルチェックは、校正で見逃した可能性のある場所を特定するのに役立つツールですが、校正が完全で正しい校正を保証できないため、コードカバレッジは完全で正しいテストを保証できません。
そしてもちろん、スペルチェックの誤った使用方法は、それが提案するすべての提案である有名なことです。
コードカバレッジでは、特に完璧に近い98%の場合、残りのパスがヒットするようにケースを埋めるのは魅力的です。
これは、すべての単語がweatherまたはknotであり、適切な単語であるというスペルチェックの縫い付けによる正義に相当します。結果はダッキング混乱です。
ただし、カバーされていないパスが実際に必要とするテストを検討すると、コードカバレッジツールが機能します。正確さを約束するのではなく、実行する必要がある作業のsomeを指摘します。
メイソンの答え へのさらに別の追加として、プログラムの動作はランタイム環境に依存する可能性があります。
次のコードには、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のさまざまな可能な動作をカバーしません。
もう1つの注意点として、パスカバレッジがすべてのバグの検出を保証するものであったとしても、実際のプログラムでそれを実現できるとは考えられません。次の例を考えてみましょう。
int main(int a, int b)
{
if (a != b) {
if (cryptohash(a) == cryptohash(b)) {
return ERROR;
}
}
return 0;
}
テストスイートがこのためのすべてのパスを生成できる場合は、暗号作成者としておめでとうございます。
問題の一部は、100%のカバレッジは1回の実行の後でコードが正しく機能することのみを保証することです。メモリリークなどの一部のバグは、1回の実行では明らかでないか問題を引き起こす可能性がありますが、時間が経つとアプリケーションに問題が発生します。
たとえば、データベースに接続するアプリケーションがあるとします。おそらく、1つの方法では、プログラマはクエリの処理が完了したときにデータベースへの接続を閉じるのを忘れています。このメソッドに対していくつかのテストを実行し、その機能でエラーを見つけることはできませんが、この特定のメソッドは完了時に接続を閉じず、開いている接続が必要であるため、データベースサーバーが使用可能な接続がないというシナリオに遭遇する可能性がありますタイムアウトしました。
パスカバレッジでは、必要な機能がすべて実装されているかどうかはわかりません。機能を除外することはバグですが、パスカバレッジはそれを検出しません。
プログラムのすべてのパスがテストされる場合、それはすべてのバグの発見を保証しますか?
すでに述べたように、答えはノーです。
そうでない場合、なぜそうではないのですか?
言われていることに加えて、単体テストではテストできないさまざまなレベルで現れるバグがあります。いくつか言及するだけです:
他の答えはすばらしいですが、「プログラムのすべてのパスがテストされる」という条件自体が曖昧であることを付け加えておきます。
この方法を検討してください:
_def add(num1, num2)
foo = "bar" # useless statement
$global += 1 # side effect
num1 + num2 # actual work
end
_
add(1, 2) == 3
をアサートするテストを作成すると、コードカバレッジツールはすべての行が実行されたことを通知します。しかし、実際には、グローバルな副作用や無駄な割り当てについて何も主張していません。これらの行は実行されましたが、実際にはテストされていません。
突然変異テストはこのような問題を見つけるのに役立ちます。ミューテーションテストツールには、コードを「ミューテーション」してテストがまだ成功するかどうかを確認するための事前定義された方法のリストがあります。例えば:
+=
_が_-=
_に変更される可能性があります。その変異はテストの失敗を引き起こさないので、テストがグローバルな副作用について意味のあるものを何も主張しないことを証明します。本質的に、変異テストはテストをテストするの方法です。しかし、可能なすべての入力セットで実際の関数をテストすることはないのと同じように、可能なすべての変更を実行することは決してないので、これも制限されています。
私たちができるすべてのテストは、バグのないプログラムに移行するためのヒューリスティックです。何も完璧ではありません。
ええと...yes実際、everyパスが「を介して」プログラムがテストされます。しかし、これは、すべての変数を含め、プログラムが持つ可能性のあるすべての状態の空間全体を通るすべての可能なパスを意味します。非常に単純な静的にコンパイルされたプログラム(古いFortranの数値計算プログラムなど)でも、それは現実的ではありませんが、少なくとも想像できるかもしれません。 2次元グリッド。それは実際には旅行中のセールスマンによく似ています。 nのような変数の場合、n次元を扱いますスペースなので、実際のプログラムでは、タスクは完全に扱いにくいものです。
さらに悪いことに、深刻なものについては、固定数のプリミティブ変数ではないが、関数呼び出しでその場で変数を作成するか、可変サイズ変数...またはそのようなもの、チューリング完全言語で可能な限り。これにより、状態空間は無限次元になり、非常に強力なテスト機器を使用しても、完全なカバレッジのすべての期待が打ち砕かれます。
とはいえ、実際にはそれほど暗いわけではありません。それはisプログラム全体を正しいprooveすることが可能ですが、あなたはいくつかのアイデアをあきらめる必要があります。
まず、宣言型の言語に切り替えることを強くお勧めします。命令型言語は、何らかの理由で常に最も人気がありますが、アルゴリズムと実際の相互作用を組み合わせる方法では、あなたがの意味を言うことさえも非常に困難です「正しい」。
純粋に機能的 プログラミング言語ではるかに簡単:これらは、数学関数の実際の興味深いプロパティとファジーの間の明確な区別がありますあなたが本当に何も言うことができない現実世界の相互作用。関数の場合、「正しい動作」を指定するのは非常に簡単です。(引数の型からの)可能なすべての入力に対して、対応する必要な結果が出た場合、関数は正しく動作します。
さて、あなたはそれがまだ扱いにくいと言います...結局のところ、すべての可能な引数の空間は一般に無限次元でもあります。確かに-単一の関数の場合でも、ナイーブなカバレッジテストでさえ、命令型プログラムで期待する以上のことが可能になります。しかし、ゲームを変える信じられないほど強力なツールがあります:普遍的な数量化/ parametric polymorphism 。基本的に、これにより、非常に一般的な種類のデータに関数を記述できます。データの簡単な例で機能する場合、あらゆる入力に対して機能します。
少なくとも理論的には。本当に一般的で、これを完全に証明できる適切な型を見つけるのは簡単ではありません。通常、 依存型言語 が必要であり、これらは使いにくい傾向があります。ただし、パラメトリックポリモーフィズムのみを使用して関数スタイルを記述すると、「セキュリティレベル」が大幅に向上します。必ずしもすべてのバグを見つける必要はありませんが、コンパイラーがバグを見つけられないように十分に隠す必要があります。