控制在QuickCheck中生成测试数据的方式

时间:2012-04-02 13:51:37

标签: haskell quickcheck

我写了一个算法来找到Haskell中子集求和问题的解决方案。签名是

subsetSum :: (Ord a, Num a) => [a] -> a -> Maybe [a]

QuickCheck似乎非常适合测试。例如,我在这里是我可以检查的属性之一:

prop_sumEqualsS l s = case subsetSum l s of
                        Just solution -> (sum solution) == s
                        Nothing       -> True

问题是该算法计算量很大,运行100次大输入列表测试需要很长时间才能运行。

我尝试使用QuickCheck 1并且确实运行得很快,但用于测试的数据集非常小。转移到QuickCheck 2后,似乎是相反的问题。 QC有a manual,但它似乎很旧(没有日期信息),我不知道QC2还有多少适用。 A Tutorial可以在Haskell Wiki上找到,但没有太多细节,只有几个词就实例化Arbitrary

所以我有两个问题:

  • QuickCheck 2中的哪些变化使它变得比QuickCheck慢得多?
  • 控制数据集创建的最佳方法是什么,使它们对给定的测试有用?

编辑:更具体一点,我想测试我的解决方案,列表大小从0到100,包含-10000到10000之间的整数。

1 个答案:

答案 0 :(得分:25)

QuickCheck 2引入的一件事可能是其中的一个来源 效率低下是shrink函数。如果财产失败,那么 调用缩小函数,使候选人变小 测试值,他们缩小,直到他们不能给 属性失败的较小值。但这只是 属性失败时会发生。

列表的任意实例的更改未更改 在version 1之间 和version 2。 区别在于措辞,版本1使用vector,版本2使用 列表理解,然后vector实现了这样的列表理解 对任意的有序调用。

两种实现都使用了size参数。在QuickCheck 1中,大小 生成的值是 默认div n 2 + 3,其中n是测试的编号。 QuickCheck 2使用另一种方法,即最大尺寸 并配置了测试次数。测试尺寸将分发 在该范围内,测试数量呈线性增长(参见computeSize'中的quickCheckWithResult)。 这意味着,默认设置为100次测试,最大尺寸为最大尺寸 来自QuickCheck 1的将是(div 100 2 + 3) = 53,而使用QuickCheck 2 它只是100

由于子集和是NP完全的,你可能有一个指数算法;) 然后是长度为50和100的列表之间的运行时间差异 当然可以是巨大的。可以理解的是,您希望使用小型列表进行测试。你有两个 选择:创建新数据类型(最好使用newtype)或make 一个独立的生成器并使用forAll。使用newtype即可 还指定了如何缩小值。这是使用newtype方法的示例实现:

newtype SmallIntList = SmallIntList [Int] deriving (Eq,Show)

instance Arbitrary SmallIntList where
  arbitrary = sized $ \s -> do
                 n <- choose (0,s `min` 50)
                 xs <- vectorOf n (choose (-10000,10000))
                 return (SmallIntList xs)
  shrink (SmallIntList xs) = map SmallIntList (shrink xs)

Arbitrary实例不会使列表超过50个元素。 元素不使用size参数,因此它们总是在 范围[-10000,10000],因此还有一些改进空间。 shrink函数只使用可用的shrink列表和 Int秒。

要将此实例与您的媒体资源一起使用,只需使用SmallIntList即可 一个论点:

prop_sumEqualsS (SmallIntList l) s = case subsetSum l s of
                                         ...