如何使用QuickCheck测试高阶函数?

Nor*_*sey 14 haskell quickcheck

我有一个我要测试的高阶函数,我要测试的一个属性就是它传入的函数.为了说明的目的,这是一个人为的例子:

gen :: a -> ([a] -> [a]) -> ([a] -> Bool) -> a
Run Code Online (Sandbox Code Playgroud)

这个想法大致是这是一个示例生成器.我将从一个单一开始a,创建单个列表[a],然后创建新的列表,[a]直到谓词告诉我停止.呼叫可能如下所示:

gen init next stop
Run Code Online (Sandbox Code Playgroud)

哪里

init :: a
next :: [a] -> [a]
stop :: [a] -> Bool
Run Code Online (Sandbox Code Playgroud)

这是我要测试的属性:

在任何调用中gen init next stop,genpromises永远不会传递空列表next.

我可以使用QuickCheck测试此属性吗?如果是,如何测试

dan*_*anr 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)
Run Code Online (Sandbox Code Playgroud)

您要测试的属性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))
Run Code Online (Sandbox Code Playgroud)

我们使用second来自 Control.Arrow.原件gen很容易定义genWitness:

gen' :: a -> ([a] -> [a]) -> ([a] -> Bool) -> a
gen' init next stop = fst (genWitness init next stop)
Run Code Online (Sandbox Code Playgroud)

由于懒惰的评估,这不会给我们带来太多的开销.回到酒店!要启用从QuickCheck显示生成的函数,我们使用模块 Test.QuickCheck.Function.虽然这里并不是绝对必要的,但一个好习惯就是对属性进行单态化:我们使用Ints的列表而不是允许单态限制使它们成为单元列表.现在让我们说明这个属性:

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
Run Code Online (Sandbox Code Playgroud)

让我们尝试使用QuickCheck运行它:

ghci> quickCheck prop_gen
Run Code Online (Sandbox Code Playgroud)

似乎有些东西循环......是的当然:gen循环如果stop在列表next上永远不会True!让我们改为尝试查看输入轨迹的有限前缀:

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)
Run Code Online (Sandbox Code Playgroud)

我们现在很快得到一个反例:

385
{_->[]}
{_->False}
2
Run Code Online (Sandbox Code Playgroud)

第二个函数是参数next,如果它返回空列表,那么循环gen将给出next一个空列表.

我希望这能回答这个问题,并且它为您提供了如何使用QuickCheck测试高阶函数的一些见解.

  • 如果你把`gen`改为`gen :: Monad m => a - >([a] - > m [a]) - >([a] - > m Bool) - > ma`,那么你可以把你的见证`next`(和`stop`)里面的代码,而不用担心这种日志记录会破坏实现. (2认同)