QuickCheck

quickCheck

Quickchekオンラインマニュアル部分訳

http://www.cs.chalmers.se/~rjmh/QuickCheck/manual.html

QuickCheckって何?

QuickCheckHaskellのプログラムを自動的にテストするツールです。

プログラマーはプログラム内で関数が満たすべき特性を与えることで仕様を定義します。

そうするとQuickCheckは、ランダムに生成された様々なテストケースについて、その特性が保たれているかテストします。

仕様はHaskellで表現されます。

そのためにQuickcheck libraryで宣言されたコンビネーターを使います。

QuickCheckコンビネータを使って性質を宣言し、テストデータの分布を観察し、テストデータの生成機を定義します。

簡単な例

簡単な例性質の宣言の単純な例は次のようなものになります。

prop_RevRev xs = reverse (reverse xs) == xs
  where types = xs::[Int]

この特性をチェックするために、この定義をHugsにロードし実行します。

Main> quickCheck prop_RevRev
OK, passed 100 tests.

特性が満たされなかったとき、QuickCheckは反例を表示します。

例えば、次のように定義してみます。


prop_RevId xs = reverse xs == xs
  where types = xs::[Int]

さて、どうなるか見てみましょう

Main> quickCheck prop_RevId
Falsifiable, after 1 tests:
[-3,15]

Quickcheckを使おう

Quickcheckを使うにはモジュール( QuickCheck.hs),をダウンロードしなければなりません。

なるべく スクリプト( quickCheck)もダウンロードした方がよいでしょう。

(訳注:GHCには標準付属している事を確認.import Test.quickCheck)

定義やテストデータ生成機を含んだ全てモジュールにたいしてquickCheckモジュールをインポートしましょう。

特性をテストするには定義されたモジュールをHugsに読み込んだのちに、次のようにquickCheckを呼びます。

quickCheck <特性名>

あるいはスクリプトを実行させてもいいでしょう。

 > quickCheck <オプション> <ファイル名>

これで与えられたモジュールの中で定義された全ての特性がチェックされます。

コマンドラインのオプションはHugsのオプションと同じ物を与えることが出来ます。

特性をチェックするためにHugsを仕様する必要はありません。

どんなHaskell98の実装であっても十分です。

しかしながら, quickCheck スクリプトはシステムにHugsがインストールされていることを前提に動きます

だからrunhugsの場所を指定するためにスクリプトを編集する必要はないと思います

何がテストされているかどうすれば分かるの?

バージョンによってはHugsは、式を評価する前に、その式を表示します。

これなら、どの特性がテストされていてどんな特性が満たされてないのかわかりますよね。

もし、Hugsがそのようなことをしないバージョンをお使いなら、quickCheckに+namesフラグを与えましょう

 > quickCheck +names <オプション> <ファイル名>

こうすれば全ての特性の名前をテスト前に表示します

テストがループしたりエラーに遭遇したらどうすればいいの

こんなケースでは、特性が満たされないことを私たちは知っていますが

Quickcheckは反例を表示することはありません。

こんな場合のための他のテストの関数が用意されています。

これを使ってテストをやりなおしましょう。

verboseCheck <property-name>

これで、テストを実行する前に毎回テストしようとしているケースが何なのか表示します。

エラーが発生したりループに陥る前に最後に表示されたケースこそが、反例になります。

特性

特性はHaskellの関数の定義として記述されます。関数の名前はprop_から始まっている必要があります。

特性は、そのパラメータがなんであっても定量的でなければありません。だから

prop_RevRev xs = reverse (reverse xs) == xs
  where types = xs::[Int]

この場合は、等式は全てのリストxsについて成り立たなければなりません。 . Technical note.

特性は単相型でなければいけません。

多層型の特性は上記の例のように、特定の型に制約されなければ、テストには使えません。

次のようにwhere節の中で引数の型を明言するのが気軽でしょう。

  where types = (x1 :: t1, x2 :: t2, ...)

注:typesは何か意味を持ったキーワードではありません。

x1やx2の型を制約するための便利な場所を提供するための単なるローカルな意味の無い関数宣言に過ぎません。

特性が返す値の型は普通Boolになるでしょう。あるいは以下に述べる他のコンビネータを使って定義すれば話は別です。

条件付き特性

特性は次のような形を取れます。

	<条件式> ==> <特性>

例えば

ordered xs = and (zipWith (<=) xs (drop 1 xs))
insert x xs = takeWhile (<x) xs++[x]++dropWhile (<x) xs

prop_Insert x xs = ordered xs ==> ordered (insert x xs)
  where types = x::Int

「条件式が満たされるときは必ず、==>の後の特性も満たされる」という特性を意味します。

条件式を満たさないテストケースは予め捨てられます。

テストケースの生成は、条件式を満たすケースが100個見つかるか、

テストケースの生成の試行回数が全体の上限に達したときに終了します。

(これは、条件式が満たされないときにループに陥ることを避けるためです。).

この場合、次のようなメッセージが表示されます。

Arguments exhausted after 97 tests.

これは「97のテストケースが 見つかり、特性はその97個のケースにおいて保たれていた」ことを示しています。

定量的特性

特性は次のような形をとれます。

	forAll <生成機> $ \<パターン> -> <特性>

例えば

prop_Insert2 x = forAll orderedList $ \xs -> ordered (insert x xs)
  where types = x::Int 

forAllの最初の引数はテストデータ生成機です。型に応じたデフォルトのテストデータ生成機を用いる代わりに

自作のテストデータ生成機を与えることでテストデータの分布をコントロールすることが出来ます。

例えば、先ほどの例では、整列されたリストの自作生成機を与えることで、

あらかじめ整列されていないテストケースを省略することが出来るだけでなく

テストケース生成試行回数の全体上限に達してしまうことなく、100個のテストケースの生成が成功することを保証できます。

生成機を宣言するためのコンビネーターについては後述します。