我有一个我要测试的高阶函数,我要测试的一个属性就是传入函数的功能。为了说明的目的,这是一个人为的例子:
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 测试此属性吗?若然,怎么做?
答案 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
。