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

时间:2012-03-13 15:04:55

标签: haskell quickcheck

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

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的任何来电中,gen承诺永远不会将空列表传递给next

我可以使用QuickCheck 测试此属性吗?若然,怎么做?

2 个答案:

答案 0 :(得分: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))

我们使用second Control.Arrow。 原始gen可以根据genWitness:

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

感谢懒惰评估,这不会给我们带来太多开销。回到 财产!要启用从QuickCheck显示生成的函数, 我们使用该模块 Test.QuickCheck.Function。 虽然这里并不是绝对必要,但一个好习惯就是 monomorphise属性:我们使用Int 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

似乎有些东西循环......是的当然: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)

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

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

第二个函数是参数next,如果它返回空列表, 那么gen中的循环将为next提供一个空列表。

我希望这可以回答这个问题并且它会给你一些见解 如何使用QuickCheck测试高阶函数。

答案 1 :(得分: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