简体   繁体   English

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

[英]How can I test a higher-order function using QuickCheck?

I have a higher-order function that I want to test, and one of the properties I want to test is what it does with the functions that are passed in. For purposes of illustration, here is a contrived example: 我有一个我想要测试的高阶函数,我要测试的一个属性就是传入的函数所做的。为了说明的目的,这是一个人为的例子:

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

The idea is roughly that this is an example generator. 这个想法大致是这是一个示例生成器。 I'm going to start with a single a , create a singleton list of [a] , then make new lists of [a] until a predicate tells me to stop. 我会用一个启动a ,创建一个单列表[a]然后作出新的名单[a]直到谓词告诉我停下来。 A call might look like this: 呼叫可能如下所示:

gen init next stop

where 哪里

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

Here's the property I'd like to test: 这是我要测试的属性:

On any call to gen init next stop , gen promises never to pass an empty list to next . 在任何对gen init next stop调用中, gen承诺永远不会将空列表传递给next

Can I test this property using QuickCheck , and if so, how? 我可以使用QuickCheck测试此属性吗?如果是,如何测试

While it would help if you gave the implementation of gen , I am guessing that it goes something like this: 虽然如果你给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)

The property you want to test is that next is never supplied an empty list. 您要测试的属性是next永远不会提供空列表。 An obstacle to test this is that you want to check an internal loop invariant inside gen , so this needs to be available from the outside. 测试这个问题的一个障碍是你要检查gen内部循环不变量,因此需要从外部获取。 Let us modify gen to return this information: 让我们修改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))

We use second from Control.Arrow . 我们使用Control.Arrow中的 second The original gen is easily defined in terms of genWitness: 原始的gen很容易根据genWitness:定义genWitness:

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

Thanks to lazy evaluation this will not give us much overhead. 由于懒惰的评估,这不会给我们带来太多的开销。 Back to the property! 回到酒店! To enable showing generated functions from QuickCheck, we use the module Test.QuickCheck.Function . 要启用从QuickCheck显示生成的函数,我们使用模块Test.QuickCheck.Function While it is not strictly necessary here, a good habit is to monomorphise the property: we use lists of Int s instead of allowing the monomorphism restriction making them to unit lists. 虽然这里并不是绝对必要的,但一个好习惯是单一属性:我们使用Int的列表而不是允许单态限制使它们成为单元列表。 Let us now state the property: 现在让我们说明这个属性:

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

Let us try the running it with QuickCheck: 让我们尝试使用QuickCheck运行它:

ghci> quickCheck prop_gen

Something seems to loop... Yes of course: gen loops if stop on the lists from next is never True ! 有些事情似乎环......是当然的: gen循环,如果stop在列出了从next从来都不是True Let us instead try to look at finite prefixes of the input trace instead: 让我们改为尝试查看输入轨迹的有限前缀:

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)

We now quickly get aa counter-example: 我们现在很快得到一个反例:

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

The second function is the argument next , and if it returns the empty list, then the loop in gen will give next an empty list. 第二个函数是next参数,如果它返回空列表,那么gen的循环将给next一个空列表。

I hope this answers this question and that it gives you some insight in how to test higher-order functions with QuickCheck. 我希望这能回答这个问题,并且它为您提供了如何使用QuickCheck测试高阶函数的一些见解。

It is possibly in bad taste to abuse this, but QuickCheck does fail a function if it throws an exception. 滥用它可能是不好的尝试,但如果QuickCheck抛出异常,它确实会失败。 So, to test, just give it a function that throws an exception for the empty case. 因此,要测试,只需给它一个函数,为空案例抛出异常。 Adapting danr's answer: 适应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

This technique does not require you to modify gen . 此技术不需要您修改gen

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM