14

我有一个要测试的高阶函数,我要测试的属性之一是它对传入的函数的作用。为了说明,这里是一个人为的例子:

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

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

gen init next stop

在哪里

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

这是我要测试的属性:

在任何对 的调用中gen init next stopgen承诺永远不会将空列表传递给next.

我可以使用 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是永远不会提供空列表的属性。测试这个的一个障碍是你想检查一个内部循环不变量 inside 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。虽然这里不是绝对必要的,但一个好习惯是单态属性:我们使用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

让我们尝试使用 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 测试高阶函数。

于 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 回答