2012-04-02 39 views
17

我编写了一个算法来找到Haskell中子集和问题的解决方案。签名是控制在QuickCheck中如何生成测试数据

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。在Haskell Wiki上有A Tutorial,但没有太多细节,只是在实例化Arbitrary时有几个字。

所以我有两个问题:

  • 什么快速检查2改变使其成为比快速检查这么多慢?
  • 什么是控制数据集创建以使其对给定测试有用的最佳方法?

编辑:更具体地讲,我想测试我的解决方案,从0到100的列表大小,-10000和10000

+1

您的问题似乎是快速检查的背景下,有点含糊;也许你会更好地问一个普通的测试问题。尽管目前的方法存在一些问题:它不会检查解决方案是否是一个子集,或者如果函数返回Nothing,那么实际上没有解决方案。 – gatoatigrado 2012-04-02 19:37:07

+0

@gatoatigrado该属性只是一个例子。我相信检查解决方案是一个子集属于另一个属性。我错了吗?我没有看到一个简单的方法来检查什么时候没有返回,实际上没有解决方案,除了用另一种算法再次解决问题。这似乎有点多余。 – Antoine 2012-04-03 04:56:18

+0

http://stackoverflow.com/questions/10712373/cookbook-for-converting-from-quickcheck1-to-quickcheck2 – gliptak 2016-08-28 12:54:13

回答

25

一件事,快速检查2引入了包含之间的整数可能是 效率低下的来源是shrink函数。如果属性失败,则调用收缩函数,该函数为候选人提供较小的测试值,并缩小它们直到它们不能给出属性失败的较小值 。但是只有当属性失败时才会发生这种情况。

为列表任意实例中的变化并没有改变太多 version 1version 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)或使 为独立发生器,并使用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个元素较长的列表。 元素不使用大小参数,所以它们总是在 的范围[-10000,10000],所以有一些改进的余地。 shrink函数只是使用可用的shrink进行列表并且 Int s。

当您使用属性这种情况下,只需使用一个SmallIntList作为 参数:

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