ユニットテストを手作業で書くのは例による証明ですか?


9

JUnitテストの記述は、コードを介した1つの特定のパスを示すことを知っています。

私の仲間の一人がコメントしました:

単体テストを手動で作成することは、例による証明です。

彼は、Quickcheckのようなツールと型を使用したプログラムの動作を推論する機能を備えたHaskellのバックグラウンドから来ていました。

彼の含意は、あなたのコードがテストされていないこの方法で試されない他の入力の組み合わせがたくさんあるということでした。

私の質問は次のとおりです。手動でユニットテストを記述しているかどうかは、例によって証明されますか?


3
いいえ、テストを作成/使用していません。単体テストがプログラムに何も問題がないことの証明であると主張することは、例による証明(不適切な一般化)です。テストは、コードの正確さを数学的に証明することではありません。テストは、本質的に実験的なチェックです。これは、コードについて何かを伝えることで信頼を築くのに役立つセーフティネットです。ただし、コードを調査するための適切な戦略を選択する必要があるのはあなたであり、そのデータの意味を解釈する必要があるのはあなたです。
FilipMilovanović17年

回答:


10

テスト用の入力をランダムに選択している場合、Proof By Exampleの論理的誤りを実行している可能性があると思います。

しかし、優れた単体テストはそれを行うことはありません。代わりに、範囲エッジケースを扱います。

たとえば、整数を入力として受け入れる絶対値関数の単体テストを作成する場合、コードが機能することを証明するために入力のすべての可能な値をテストする必要はありません。包括的なテストを取得するには、5つの値(-1、0、1、および入力整数の最大値と最小値)のみが必要です。

これらの5つの値は、関数のすべての可能な範囲とエッジケースをテストします。関数がすべての入力値に対して機能する高い信頼水準を得るために、他のすべての可能な入力値(つまり、整数型が表すことができるすべての数値)をテストする必要はありません。


11
コードテスターがバーに入り、ビールを注文します。5ビール。-1ビール、MAX_VALUEビール、鶏肉。null。
ニール、

2
「5つの値」は純粋なナンセンスです。のような些細な関数を考えてみましょうint foo(int x) { return 1234/(x - 100); }。また、(テストする内容によっては)無効な(「範囲外」)入力が正しい結果を返すようにする必要がある場合があることにも注意してください(たとえば、「find_thing(thing)」は、ある種の「見つかりません」ステータスを正しく返します。物が見つからなかった場合)。
ブレンダン

3
@ブレンダン:5つの値であることは重要ではありません。私の例では、たまたま5つの値です。別の関数をテストしているため、例のテストの数は異なります。すべての関数に正確に5つのテストが必要だと言っているのではありません。あなたは私の答えを読んでそれを推測しました。
ロバートハーヴェイ

1
ジェネレーティブテストライブラリは、通常、エッジケースのテストに優れています。たとえば、あなたが代わりに整数の浮動小数点数を使用していた、場合、あなたのライブラリーもチェックだろう-InfInfNaN1e-100-1e-100-02e200...私はむしろ、すべて手動でそれらを行う必要はありませんと思います。
Hovercouch 2017

@Hovercouch:良いものをご存知なら、ぜひ聞いてみたいです。私が見た中で最高のものはPexでした。しかし、それは信じられないほど不安定でした。ここでは、比較的単純な関数について話しています。現実のビジネスロジックのようなものを扱うとき、物事はより難しくなります。
ロバートハーヴェイ

8

任意のソフトウェアテストは、「例による証明」、JUnitのようなツールを使用してテストするだけでなく、ユニットのようなものです。そして、それは新しい知恵ではありません、1960年のダイクストラからの引用があります、それは本質的に同じことを言います:

「テストは、バグがないことではなく、存在を示します」

(「shows」という単語を「proofs」に置き換えてください)。ただし、これはランダムなテストデータを生成するツールにも当てはまります。現実世界の関数の可能な入力の数は、通常、ケースの生成方法とは無関係に、生成され、宇宙の時代内の期待される結果に対して検証できるテストケースの数よりも桁違いに大きいため、大量のテストデータを生成するためにジェネレーターツールを使用しても、特定のバグを検出した可能性がある1つのテストケースを見逃さないという保証はありません。

ランダムなテストがあり、時々手動で作成したテストケースで見落とされたバグを明らかにしました。ただし、一般的には、テストする関数のテストを慎重に作成し、できるだけ少ないテストケースで完全なコードとブランチカバレッジを取得できるようにする方が効率的です。手動で生成されたテストとランダムに生成されたテストを組み合わせることが実現可能な戦略になる場合があります。さらに、ランダムテストを使用する場合、再現可能な方法で結果を取得するように注意する必要があります。

したがって、手動で作成されたテストは、ランダムに生成されたテストよりも悪くはありません。


1
ランダムチェックを使用する実用的なテストスイートには、単体テストもあります。(厳密には、単体テストはランダムテストの退化したケースにすぎません。)言葉遣いは、ランダム化テストの実現が難しい、またはランダム化テストとユニットテストの組み合わせが難しいことを示唆しています。通常、これは当てはまりません。私の意見では、ランダム化テストの最大の利点の1つは、常に保持することを目的としたコードに関するプロパティとしてテストを記述することを強く推奨することです。これらのプロパティは、いくつかのポイントテストを推測する必要があるよりも、明示的に記述(およびチェック)したほうがよいでしょう。
Derek ElkinsがSE

@DerekElkins:「難しい」は私見の誤解です。ランダムテストにはかなりの労力が必要です。これは、手作りのテストに利用できる時間を短縮するための労力です(そして、質問で述べられているようなスローガンをフォローしているだけの人は、おそらく手作りをまったく行いません)。1つのコードに大量のランダムテストデータを投げるだけで作業は半分になり、これらのテスト入力ごとに期待される結果を生成する必要があります。一部のシナリオでは、これは自動的に行うことができます。他の人のために。
Doc Brown

良いディストリビューションを選択するために何らかの考慮が必要になることは間違いありませんが、これは通常、大きな問題ではありません。あなたのコメントは、これを間違った方法で考えていることを示唆しています。ランダムチェック用に作成するプロパティは、モデルチェック用または正式な証明用に作成するプロパティと同じです。確かに、それらは同時にこれらすべてのものに使用でき、使用されてきました。同様に作成する必要がある「期待される結果」はありません。代わりに、常に保持する必要があるプロパティを指定するだけです。いくつかの例:1)何かをスタックにプッシュして...
Derek ElkinsがSEを去った

...その後、ポップは何もしないことと同じでなければなりません。2)10,000ドルを超える残高のある顧客は、高金利の金利を獲得する必要があります。3)スプライトの位置は常に画面の境界ボックス内にあります。一部のプロパティはポイントテストによく対応している可能性があります。たとえば、「残高が0ドルのとき、残高ゼロの警告を出します」プロパティは、完全な仕様を取得するという理想を備えた部分仕様です。これらのプロパティを考えるのが難しいことは、仕様が何であるかが不明確であることを意味し、多くの場合、優れたユニットテストを考えるのが難しいことを意味します。
Derek ElkinsがSEを去る

0

手動でテストを書くことは「例による証明」です。しかし、QuickCheckや、限られた範囲のタイプのシステムも同様です。正式な検証ではないものはすべて、コードについての説明が制限されます。代わりに、アプローチの相対的なメリットの観点から考える必要があります。

QuickCheckのような生成テストは、広い範囲の入力をスイープするのに非常に適しています。また、手動テストよりもエッジケースに取り組む場合の方がはるかに優れています。生成テストライブラリは、これよりも経験豊富です。一方、特定の出力ではなく、不変量についてのみ説明します。そのため、プログラムが正しい結果を得ていることを検証するには、実際にそれを検証するための手動テストが必要foo(bar) = bazです。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.