14

テストしたい高階関数があり、テストしたいプロパティの 1 つは、渡された関数で何をするかです。説明のために、以下に不自然な例を示します。

gen :: a -> ([a] -> [a]) -> ([a] -> Bool) -> a

大まかに言うと、これはジェネレーターの例です。単一の から始めて、aのシングルトン リストを作成し、述語で停止するように指示されるまで、 の[a]新しいリストを作成します。[a]呼び出しは次のようになります。

gen init next stop

どこ

init :: a
next :: [a] -> [a]
stop :: [a] -> Bool

テストしたいプロパティは次のとおりです。

への呼び出しでは、空のリストを に渡さないことを約束しgen init next stopます。gennext

QuickCheck を使用してこのプロパティをテストできますか?

4

2 に答える 2

10

の実装を提供していただけると助かりますが、次のgenようになると思います。

gen :: a -> ([a] -> [a]) -> ([a] -> Bool) -> a
gen init next stop = loop [init]
  where
    loop xs | stop xs   = head xs
            | otherwise = loop (next xs)

テストしたいプロパティはnext、空のリストが提供されないことです。これをテストする際の障害は、内部ループの不変条件をチェックしたいことですgen。そのため、これは外部から利用できる必要があります。genこの情報を返すように変更しましょう。

genWitness :: a -> ([a] -> [a]) -> ([a] -> Bool) -> (a,[[a]])
genWitness init next stop = loop [init]
  where
    loop xs | stop xs   = (head xs,[xs])
            | otherwise = second (xs:) (loop (next xs))

Control.Arrowsecondから 使用します。オリジナルは次のように簡単に定義できます。gengenWitness:

gen' :: a -> ([a] -> [a]) -> ([a] -> Bool) -> a
gen' init next stop = fst (genWitness init next stop)

遅延評価のおかげで、オーバーヘッドはあまり発生しません。物件に戻ります!QuickCheck から生成された関数を表示できるようにするには、モジュール Test.QuickCheck.Functionを使用します。ここでは厳密に必要というわけではありませんが、良い習慣として、プロパティをモノモーフィズします: Ints をユニット リストにするモノモーフィズム制限を許可する代わりに、s のリストを使用します。プロパティを記述しましょう。

prop_gen :: Int -> (Fun [Int] [Int]) -> (Fun [Int] Bool) -> Bool
prop_gen init (Fun _ next) (Fun _ stop) =
    let trace = snd (genWitness init next stop)
    in  all (not . null) trace

QuickCheck で実行してみましょう。

ghci> quickCheck prop_gen

何かがループしているようです... はい、もちろん:リストにある fromが neverのgen場合はループします! 代わりに、代わりに入力トレースの有限プレフィックスを見てみましょう。stopnextTrue

prop_gen_prefix :: Int -> (Fun [Int] [Int]) -> (Fun [Int] Bool) -> Int -> Bool
prop_gen_prefix init (Fun _ next) (Fun _ stop) prefix_length =
    let trace = snd (genWitness init next stop)
    in  all (not . null) (take prefix_length trace)

ここですぐに反例を取得します。

385
{_->[]}
{_->False}
2

2 番目の関数は引数nextで、空のリストを返す場合、ループ インは空のリストを返しgenますnext

これがこの質問の答えであり、QuickCheck を使用して高階関数をテストする方法についての洞察が得られることを願っています。

于 2012-03-13T16:13:39.593 に答える
4

これを悪用するのはおそらく悪趣味ですが、QuickCheckは例外をスローした場合に関数を失敗させます。したがって、テストするには、空の場合に例外をスローする関数を与えるだけです。danrの答えを適応させる:

import Test.QuickCheck
import Test.QuickCheck.Function
import Control.DeepSeq

prop_gen :: Int -> (Fun [Int] [Int]) -> (Fun [Int] Bool) -> Bool
prop_gen x (Fun _ next) (Fun _ stop) = gen x next' stop `deepseq` True
  where next' [] = undefined
        next' xs = next xs

この手法では、変更する必要はありませんgen

于 2012-03-13T22:30:05.967 に答える