【问题标题】:Controlling how test data is generated in QuickCheck控制如何在 QuickCheck 中生成测试数据
【发布时间】:2012-04-02 13:51:36
【问题描述】:

我编写了一个算法来解决 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 之间的整数。

【问题讨论】:

  • 您的问题对于 QuickCheck 的上下文来说似乎有点模糊;也许你最好问一个一般的测试问题。但是,您当前的方法存在一些问题:它不会检查解决方案实际上是一个子集,或者如果函数返回 Nothing 那么实际上没有解决方案。
  • @gatoatigrado 该属性只是一个示例。我相信检查解决方案是否属于另一个属性的子集。我错了吗?我没有看到一种简单的方法来检查返回 Nothing 时实际上没有解决方案,除非用另一种算法再次解决问题。这似乎有点多余。

标签: haskell quickcheck


【解决方案1】:

QuickCheck 2 引入的一件事可能是 效率低下是shrink 函数。如果一个属性失败,那么 调用收缩函数,它为候选人提供更小的 测试值,并且它们被缩小,直到它们不能给出 属性失败的较小值。但这只 当属性失败时发生。

列表的任意实例的更改未更改 version 1version 2。 区别在于措辞,版本1使用vector,版本2使用 一个列表理解,但是 vector 正是用这样一个列表理解来实现的 对任意顺序的调用。

两种实现都使用了 size 参数。在 QuickCheck 1 中,大小 生成的值是 默认div n 2 + 3,其中n 是测试编号。 QuickCheck 2 使用另一种方法,其中最大尺寸 并配置测试次数。测试大小将被分发 在该范围内,测试数量呈线性增长(参见quickCheckWithResult 中的computeSize')。 这意味着,在默认设置为 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 个元素。 元素不使用 size 参数,所以它们总是在 范围[-10000,10000],所以还有一些改进的余地。 shrink 函数简单地使用可用的shrinks 用于列表和 Ints.

要将此实例与您的属性一起使用,只需使用 SmallIntList 作为 一个论点:

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

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多