パスカバレッジはすべてのバグの発見を保証しますか?


64

プログラムを通るすべてのパスがテストされる場合、それはすべてのバグを見つけることを保証しますか?

そうでない場合は、なぜですか?プログラムフローのあらゆる可能な組み合わせを通過し、問題が存在する場合はそれを見つけられないのはどうしてですか。

「すべてのバグ」が見つかる可能性があることをお勧めしますが、おそらくパスカバレッジが実用的ではないため(組み合わせであるため)、これまで経験したことがないためでしょうか。

注:この記事では、私が考えているカバレッジタイプの概要を簡単に説明します。


33
これは停止する問題と同等です。

31
そこにあるはずのコードがそうでない場合はどうでしょうか?
-RemcoGerlich

6
@スノーマン:いいえ、そうではありません。すべてのプログラムで停止する問題を解決することはできませんが、多くの特定のプログラムでは解決可能です。これらのプログラムでは、すべてのコードパスを有限の(場合によっては長い)時間で列挙できます。
ヨルゲンフォグ

3
中にバグを見つけるためにしようとしたとき、@JørgenFoghは、しかし、任意のプログラムは、プログラムが停止したか否か先験的には不明ではないでしょうか?これは、「パスカバレッジを介してプログラムのすべてのバグを見つける」一般的な方法に関する質問ではありませんか?その場合、「プログラムが停止するかどうかを調べる」ことに似ていませんか?
アンドレスF.

1
@AndresF。プログラムが記述されている言語サブセットが停止しないプログラムを表現できる場合、プログラムが停止するかどうかは不明です。プログラムが無制限ループ/再帰/ setjmpなどを使用せずにCで、またはCoqまたはESSLで記述されている場合、プログラムを停止し、すべてのパスをトレースできます。(チューリング完全性は深刻に過大評価されています)
ルーシェンコ

回答:


128

プログラムを通るすべてのパスがテストされる場合、それはすべてのバグを見つけることを保証しますか?

番号

そうでない場合は、なぜですか?プログラムフローのあらゆる可能な組み合わせを通過し、問題が存在する場合はそれを見つけられないのはどうしてですか。

可能なすべてのパスをテストしたとしても、すべての可能なまたはすべての可能な値の組み合わせでそれらをテストしていないからです。例(擬似コード):

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つの以上の数十年の今、かなりきています。)


7
@digitgopher:たぶん、プログラムに入力がない場合、どのような便利なことがありますか?
メイソンウィーラー

34
統合テストの欠落、テストのバグ、依存関係のバグ、ビルド/展開システムのバグ、または元の仕様/要件のバグが欠落している可能性もあります。すべてのバグの発見を保証することはできません。
Ixrec

11
@Ixrec:SQLiteはかなり勇敢な努力をしていますが! しかし、それがどんなに大きな努力であるかを見てください!大規模なコードベースにはうまく対応できません。
メイソンウィーラー

13
だけでなく、あなたがそのすべての可能な値またはそれらの組み合わせをテストしていないだろう、あなたは競合状態を公開するか、実際にあなたのテストは、それが報告しないになるだろうデッドロック、入力作ることができ、そのいくつかのすべての相対的なタイミング、テストしていない何かを。それも失敗ではないでしょう!
Iwillnotexist Idonotexist

14
私の回想(このような文章に支えられています)は、Dijkstraが、優れたプログラミングプラクティスでは、プログラムが(すべての条件下で)正しいという証拠は、そもそもプログラムの開発に不可欠な部分であると信じていたということです。その観点から見ると、テスト錬金術のようなものです。誇張ではなく、これは非常に強い言葉で表現された非常に強い意見だと思います。
デビッドK

71

メイソンの答えに加えて、別の問題もあります。カバレッジは、どのコードがテストされたかを示すものではなく実行されたコードを示すものです。

100%のパスカバレッジを持つテストスイートがあるとします。すべてのアサーションを削除して、テストスイートを再度実行してください。Voilà、テストスイートはまだ100%のパスカバレッジを持っていますが、それは絶対に何もテストしません。


2
テストされたコードを(テストのパラメーターを使用して)呼び出すときに、例外がないことを確認できます。これは、何もありません。
パエロエベルマン

7
@PaŭloEbermannAgreeed、わずかに何もない。ただし、「すべてのバグを見つける」よりも大幅に少なくなります;)
Andres F.

1
@PaŭloEbermann:例外はコードパスです。コードがスローできても、特定のテストデータがスローされない場合、テストは100%のパスカバレッジを達成しません。これは、エラー処理メカニズムとしての例外に固有のものではありません。ON ERROR GOTOCのように、Visual Basic もパスif(errno)です。
–MSalters

1
@MSalters私が話しているのは、入力に関係なく、(仕様により)例外をスローしないコードです。それが何かを投げたら、それはバグでしょう。もちろん、例外をスローするように指定されたコードがある場合は、テストする必要があります。(もちろん、Jörgが言ったように、コードが例外をスローしないことを確認するだけでは、通常、スローしないコードであっても正しいことを確認するのに十分ではありません。) -nullポインター逆参照またはゼロ除算のような、可視コードパス。パスカバレッジツールはそれらをキャッチしますか?
パエロエベルマン

2
この答えはそれを否定します。さらに申し立てを行って、これにより、パスカバレッジがバグを1つでも見つけることを保証することは決してないと言います。変化が検出される少なくともすることを保証することができる指標は、しかし、ある-突然変異試験は、実際のコード(一部)の改変があることを保証することができます検出すること。
eis

34

簡単な例を次に示します。次のソートアルゴリズム(Java)を検討してください。

int[] sort(int[] x) { return new int[] { x[0] }; }

それでは、テストしてみましょう。

sort(new int[] { 0xCAFEBABE });

ここで、(A)この特定の呼び出しがsort正しい結果を返すこと、(B)すべてのコードパスがこのテストでカバーされていることを考慮してください。

しかし、明らかに、プログラムは実際にはソートされません。

したがって、すべてのコードパスのカバレッジは、プログラムにバグがないことを保証するには不十分です。


12

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

2
次に、テストに合格する別の実装を示します(非改行Pythonをご容赦ください):パーツを省略して100%のカバレッジを得るdef abs(x): if x == -3: return 3 else: return 0ことができますがelse: return 0、単体テストに合格しても関数は本質的に役に立たないでしょう。
CVn

7

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;
} 

テストスイートがこのためのすべてのパスを生成できる場合、おめでとうございます。あなたは暗号作成者です。


十分に小さい整数の場合は簡単:)
CodesInChaos

について何も知らなくてもcryptohash、「十分に小さい」と言うのは少し難しいです。スーパー計算機で完了するのに2日かかるかもしれません。しかし、ええ、int少しであることが判明するかもしれませんshort
-dureuill

32ビット整数と一般的な暗号化ハッシュ(SHA2、SHA3など)の計算では、これは非常に安価です。数秒かそこら。
CodesInChaos

7

他の回答から、テストでの100%のコードカバレッジは100%のコードの正確性を意味するものではなく、テストで発見できるすべてのバグが見つかることも明らかです(テストで発見できないバグは気にしないでください)。

この質問に答える別の方法は、実践からのものです:

現実の世界では、実際には自分のコンピューター上に、100%のカバレッジを与える一連のテストを使用して開発されたソフトウェアの多くの部分がありますが、より良いテストで特定されるバグを含むバグがまだあります。

したがって、含意される質問は次のとおりです。

コードカバレッジツールのポイントは何ですか?

コードカバレッジツールは、テストを怠った領域を特定するのに役立ちます。それは問題ない場合があります(テストを行わなくてもコードは明らかに正しい)、解決できない場合があります(何らかの理由でパスにヒットできない)、または現在または将来の変更後に大きな悪臭を放つバグの場所になる可能性があります。

いくつかの点で、スペルチェックは同等です:何かがスペルチェックを「パス」し、辞書の単語に一致するような方法でスペルミスをする可能性があります。または、正しい単語が辞書にないため、「失敗」する可能性があります。または、それはパスしてまったくナンセンスであることができます。スペルチェックは、校正で見逃した場所を特定するのに役立つツールですが、完全で正しい校正を保証できないのと同様に、コードカバレッジでは完全で正しいテストを保証することはできません。

そしてもちろん、スペルチェックを使用する間違った方法は、提案されたすべての提案された羊と一緒に行くことで有名です。

コードカバレッジでは、特に98%に近い完璧な場合、残りのパスがヒットするようにケースを埋めることが魅力的です。

これは、すべての単語が天気であるか、すべてが適切な単語であるという結び目である、スペルチェックによる縫い付けと同等です。結果はダッキングの混乱です。

ただし、カバーされていないパスに実際に必要なテストを検討する場合、コードカバレッジツールはその役割を果たします。正確さを約束するのではなく、実行する必要がある作業の一部を指摘しています。


+1この回答は建設的であり、カバレッジの利点のいくつかに言及しているため、この回答が気に入っています。
アンドレスF.

4

パスカバレッジでは、必要な機能がすべて実装されているかどうかはわかりません。機能を除外することはバグですが、パスカバレッジでは検出されません。


1
それはバグの定義に依存すると思います。欠けている機能や機能をバグと見なすべきではないと思います。
eis

@eis-実際にXを実行しないとドキュメントに記載されている製品に問題はありませんか?それは「バグ」のかなり狭い定義です。ボーランドのC ++製品ラインのQAを管理したとき、それほど寛大ではありませんでした。
ピートベッカー

それが実装されなかった場合、ドキュメンテーションがXを行うと言う理由がわかりません
-eis

@eis-元の設計が機能Xを要求した場合、ドキュメントは機能Xを説明することになります。誰もそれを実装しなかった場合、それはバグであり、パスカバレッジ(または他の種類のブラックボックステスト)はそれを見つけられません。
ピートベッカー

パスカバレッジはホワイトボックステストであり、ブラックボックスではありません。ホワイトボックステストでは、欠落している機能を検出できません。
ピートベッカー

4

問題の一部は、100%のカバレッジは、1回の実行後にコードが正しく機能することのみを保証することです。メモリリークなどの一部のバグは、1回の実行後には明らかにならないか、問題を引き起こす可能性がありますが、時間が経つとアプリケーションに問題が発生します。

たとえば、データベースに接続するアプリケーションがあるとします。おそらく、プログラマーは、1つの方法で、クエリの完了時にデータベースへの接続を閉じるのを忘れます。このメソッドに対していくつかのテストを実行し、その機能にエラーは見つかりませんでしたが、データベースサーバーは使用可能な接続が切れるシナリオに陥る可能性があります。この特定のメソッドは完了時に接続を閉じず、開いている接続はタイムアウトになりました。


それは問題の一部であることに同意しましたが、実際の問題はそれよりも根本的なものです。メモリが無限で同時実行性のない理論的なコンピューターであっても、100%のテスト範囲はバグがないことを意味しません。これの簡単な例はここの答えにたくさんありますが、ここに別のものがあります:私のプログラムがの場合times_two(x) = x + 2、これはテストスイートassert(times_two(2) == 4)で完全にカバーされますが、これは明らかに明らかにバグのあるコードです!メモリリークの必要はありません:)
Andres F.

2
それは素晴らしい点であり、バグのないアプリケーションの可能性のffinの中のより大きな/より基本的な釘であると認識していますが、あなたが言うように、それはすでにここに追加されており、既存の回答。データベース接続が不要になったときに接続プールに戻されなかったためにクラッシュしたアプリケーションを聞いたことがあります-メモリリークは、リソースの誤った管理の典型的な例です。私のポイントは、一般的なリソースの適切な管理は完全にはテストできないということです。
デレクW

いい視点ね。同意した。
アンドレスF.

3

プログラムを通るすべてのパスがテストされる場合、それはすべてのバグを見つけることを保証しますか?

すでに述べたように、答えはノーです。

そうでない場合は、なぜですか?

言われていることに加えて、さまざまなレベルで現れるバグがあり、それらは単体テストではテストできません。ほんの数例を挙げると:

  • 統合テストで検出されたバグ(ユニットテストでは実際のリソースを使用すべきではありません)
  • 要件のバグ
  • 設計とアーキテクチャのバグ

2

すべてのパスをテストすることはどういう意味ですか?

他の答えは素晴らしいですが、「プログラムを通るすべてのパスがテストされる」という条件自体が曖昧であることを付け加えたいと思います。

この方法を検討してください:

def add(num1, num2)
  foo = "bar"  # useless statement
  $global += 1 # side effect
  num1 + num2  # actual work
end

をアサートするテストを作成するadd(1, 2) == 3と、コードカバレッジツールにより、すべての行が実行されたことが通知されます。しかし、実際には、グローバルな副作用や役に立たない割り当てについて何も主張していません。これらの行は実行されましたが、実際にはテストされていません。

突然変異テストは、このような問題を見つけるのに役立ちます。ミューテーションテストツールには、コードを「変更」してテストがまだ合格するかどうかを確認するための事前に決められた方法のリストがあります。例えば:

  • 一つの変異は、変更される可能性があります+=-=。その突然変異はテストの失敗を引き起こさないので、テストがグローバルな副作用について意味のあることを主張しないことを証明します。
  • 別の突然変異により、最初の行が削除される場合があります。その突然変異はテストの失敗を引き起こさないので、テストが割り当てに関して意味のあることを主張しないことが証明されます。
  • さらに別の突然変異により、3行目が削除される場合があります。それはテストの失敗を引き起こし、この場合、テストがその行について何かをアサートすることを示します。

本質的に、突然変異テストはテストテストする方法です。ただし、可能なすべての入力セットで実際の関数をテストしないように、すべての可能な突然変異を実行することはないため、これも制限されます。

できるすべてのテストは、バグのないプログラムに移行するためのヒューリスティックです。何も完璧ではありません。


0

まあ... はい、実際、プログラムを「通る」すべてのパスがテストされます。しかし、それは、すべての変数を含む、プログラムが持つことができるすべての可能な状態の空間全体を通るすべての可能なパスを意味します。非常に単純な静的にコンパイルされたプログラム(たとえば、古いFortranの数値計算ツール)であっても、それは少なくとも想像できますが、実行可能ではありません。整数変数が2つしかない場合、基本的に二次元グリッド; 実際、トラベリングセールスマンによく似ています。このような変数がn個あると、n次元の空間を扱うことになります。そのため、実際のプログラムでは、タスクはまったく手に負えません。

さらに悪いことに、深刻な問題について、固定数のプリミティブ変数だけでなく、関数呼び出しでオンザフライで変数を作成するか、可変サイズの変数...またはそのようなものをチューリング完全言語で可能にします。これにより、状態空間は無限次元になり、非常に強力なテスト機器が与えられたとしても、完全なカバレッジのすべての希望が打ち砕かれます。


とは言うものの、実際には物事はそれほど厳しいものではありません。プログラム全体が正しいことを証明すること可能ですが、いくつかのアイデアをあきらめる必要があります。

最初:宣言型のlanguangeに切り替えることを強くお勧めします。命令型言語は、なんらかの理由で常に最も人気がありますが、アルゴリズムと実際の相互作用を組み合わせる方法により、「正しい」という意味を言うことすら非常に難しくなります。

純粋に関数型のプログラミング言語の方がはるかに簡単です。これらは、数学関数の本当の興味深い特性と、実際には何も言えないファジーな実世界の相互作用を明確に区別します。関数の場合、「正しい動作」を指定するのは非常に簡単です。可能なすべての入力(引数の型から)に対して、対応する望ましい結果が得られる場合、関数は正しく動作します。

さて、あなたはそれはまだ手に負えないと言います...結局のところ、すべての可能な引数の空間も一般に無限次元です。確かに、単一の機能については、ナイーブなカバレッジテストでさえ、命令型プログラムで期待することができるよりもはるかに進んでいます!しかし、ゲームを変える信じられないほど強力なツールがあります:普遍的な定量化/ パラメトリック多相性。基本的に、これにより、非常に一般的な種類のデータに関数を書くことができます。データの単純な例で機能する場合、あらゆる入力に対して機能することが保証されます。

少なくとも理論的には。これを完全に証明できるほど一般的な正しい型を見つけるのは簡単ではありません。通常、依存型の言語が必要であり、これらはかなり使いにくい傾向があります。しかし、パラメトリックなポリモーフィズムだけで機能的なスタイルで書くと、すでに「セキュリティレベル」が大幅に向上します。必ずしもすべてのバグを見つける必要はありませんが、コンパイラがそれらを見つけないように十分に隠す必要があります!


私はあなたの最初の文に同意しません。プログラムのすべての状態を通過しても、それ自体でバグは検出されません。クラッシュや明示的なエラーを確認したとしても、実際の機能はまだ確認していないため、エラー領域のほんの一部しかカバーしていません。
マシュー

@MatthewRead:これを結果として適用する場合、「エラースペース」はすべての状態のスペースの適切なサブスペースです。もちろん、「正しい」状態でさえも、徹底的なテストを行うには大きすぎるスペースを構成するため、仮想的なものです。
左辺約
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.