2

Haskell には、コード片の型シグネチャによって、そのコードが何をするかがわかるという優れた特性があります。そして、私は考えました...型シグネチャに基づいてテストを構築できますか?

たとえば、 などのモジュールを考えてみましょうData.Map。データ型と、その型で動作するいくつかの関数を定義します。型シグネチャだけを見ると、[原則として]値を構築するすべての可能な方法を把握できるはずMapです。つまり、アルゴリズム的に可能ということです。モジュールへのインターフェイスを取り、モジュールに対して実行できるすべての可能な関数呼び出しを計算するプログラムを作成できるはずです。

さて、よく書かれたテスト スイートは、ライブラリが何をすべきかの「仕様」と見なすことができると感じる人がいます。明らかに、コードを書いた人間は何をしようとしているのかを知っていますが、機械は知りません。しかし、あなたが提供した型シグネチャがあればマシンは何を要求できるかを理解できるはずです。

より具体的なケースとして、すべての可能な値に適用されるはずの一連の不変条件があるとしますMap。(たとえば、null m == (size m == 0)、またはおそらく内部の健全性チェック関数は常に true を返す必要があります。) テスト フレームワークを作成し、それをモジュールに渡し、「型の値はMap X Y常にこれらの不変条件を満たさなければならない」と言って、テスト フレームワークをマップを作成し、それらがすべての条件を満たしていることを確認するために提供した関数のすべての可能な組み合わせを実行してみてください。そうでない場合は、違反している条件と、この無効な値を作成するために必要な式が示されます。

私の質問は、この種のアプローチは扱いやすいと思いますか? 望ましい?面白い?このような問題を解決するには、どのようにアプローチしますか? Djinn は、既存の関数と定数を指定して、指定された型の値を構築する方法を既に知っているようです。そのため、特定の型を持つ複数の式を考え出すことはそれほど難しいことではありません。適切なコード カバレッジを取得するために、どのような種類のヒューリスティックを適用できますか? (明らかに、型だけでなくコードを分析することは非常に困難です...ライスの定理が待ち構えています。)

(この種のマシン チェックが手書きのテスト コードに取って代わるべきだと言っているわけではないことに注意してください。この考えは、それを補強することを目的としています。おそらく、より困難なケースでは、マシンが考えられる表現をせき止めて、人間にそのコードの意味を尋ねます。これは新しいテスト ケースとして記録され、テスト スイートが実行されるたびに実行されます。)

4

3 に答える 3

1

「aavogt」からのコメントを拡張します。

Irulan は一連の識別子を受け取り、それらの識別子を含む適切に型指定された式をすべて構築し、実行時に例外をスローする式を検索します。

もちろん、関数が例外をスローする場合、これは必ずしもバグではありません。例外をスローすることを期待している式があるかもしれません。しかし、Irulan はそれらをすべて見つけて表示するので、どれが合法でどれがバグであるかを判断できます。

Irulan は、例外にならないバグを見つけられないようです。(特に、例外をスローする必要があるが、例外をスローしない式は検出されません。) 非終了または不正な結果はキャッチされず、過度のリソース使用もありません。(もう一度言いますが、機械はどのようにして「正しい」結果がどうあるべきかを自動的に判断しますか? テレパシー?)

私が魅力的だと思うのは、テストデータ生成へのアプローチです。Irulan は、型の値コンストラクターを探すか、それらがスコープ内にない場合は、適切な型の値を生成する関数を見つけようとします。インスタンスをまったく使用せずEq、値を検査するためにケースブロック (またはコンストラクターにアクセスできない場合は射影関数) を使用することを好みます。

面白いのは、Irulan が怠惰なトリックを使用してテスト データを遅延生成する方法です。たとえば、関数をテストしようとしている場合、リストに実際にlengthどのデータが含まれているかは問題ではなく、リストの大きさだけが重要です。Irulan は自動的にそれを判断できるので、いくつかの異なるサイズのリストを生成しますが、リストにデータを入れることは気にしません。QuickCheck のようなフレームワークは、リスト サイズは同じでも内容が異なる何百ものテスト ケースを無駄に生成します。

たとえば、Irulan は 3 つの「穴」を含むリストを生成できます。これらの穴の 1 つに触れると、例外がスローされます。ただし、穴には番号が付けられており、例外には穴番号が含まれています。Irulan は例外をキャッチするため、どの穴に触れたかを「認識」します。次に、その穴を、そこに入る可能性のある適切に型指定されたすべての値に体系的に置き換えることができます (それ自体が再帰的に再び穴で埋められます)。このアプローチでは、検索ツリーは、実際にテスト中のコードにとって重要なものだけに刈り込まれます。

例外と遅延がこのように相互作用して、そうでなければ不透明なコードの内部操作を「観察」できるようになることを私は知りませんでした。私はそれが本当に面白いと思います...

また、Irulan の博士論文には、この質問に関連する他の研究のかなり網羅的な調査が含まれていることも指摘しておく必要があります。

于 2013-11-26T13:35:50.847 に答える
0

Thomas DuBuissonからのコメントを拡張します。

QuickSpec に一連の値 (通常は関数値) を与えると、[概念的に] これらの値を使用して、適切に型指定されたすべての可能な式を構築し、常に成立する等式を見つけようとします。(これには の正しい実装が必要であることに注意してくださいEq。)

たとえば、emptyinsertdeletefromData.Mapなどを指定すると、 などのルールが返されると期待できますinsert x (insert y empty) == insert y (insert x empty)。繰り返しますが、これはインスタンスがあり、実際に正しく機能する場合にのみ正しくData.Map機能Eqします。

ルールのポップアップが明らかに偽物である場合、または期待していたルールが表示されない場合は、コードにバグがある可能性が高いと考えられます。(繰り返しになりますが、想定しているルールがわかっている場合は、最初に QuickCheck を使用してください!) もう 1 つの可能性は、QuickSpec でルールのシステムを生成し、それらを QuickCheck プロパティに変換することです。これで、コードを徹底的にリファクタリングして、観察可能な動作が変わらないことを確認できます。

最初は少し奇妙ですが、これは主題にアプローチする興味深い方法です。余談ですが、QuickSpec が実際にこのようなルールのシステムを何もないところから思いつくことができるのは興味深いことです。それはほとんど魔法のようです。とてもクールなもの...

于 2013-11-26T13:23:37.497 に答える