Quickcheck:生成任意集合的任意元素

4
假设我正在为 Data.Set 编写测试。我想检查从集合中删除元素是否有效,所以我可能会写出以下代码:
prop_deleteA it x = member x it ==> not (member x (delete x it))

假设 it 具有合适的 Arbitrary 实例。然而,这取决于 quickcheck 生成的 x 值是否存在于集合中,这一点通常不能保证。如果能让 x 依赖于 it,以确保 x 已经是 it 的成员,则效果会更好。我该怎么做呢?
我曾想过写下以下代码:
prop_deleteB it f = let x = f it
                   in not (member x (delete x it))

在it技术相关中,f :: Set a -> a 可以通过coarbitrary进行适当定义。然而,coarbitrary只允许我们定义 f :: Set a -> b,这不是我们想要的。到目前为止,我最好的想法是定义一个新类型。

data SetAndElement a = SetAndElement (Set a) a

这使我们能够编写一个合适的Arbitrary实例。

instance (Ord a, Arbitrary a) => Arbitrary (SetAndElement a) where
    arbitrary = do it <- suchThat arbitrary (not . Set.null)
                   x  <- elements (elems it)
                   return (SetAndElement it x)

允许将prop_delete写为

prop_deleteC (SetAndElement it x) = not (member x (delete x it))

这个方法可行,但有点繁琐;还有更好的选择吗?(如果没有,我会修改问题并将此作为答案。)实际的 Data.Set 实现(containers 包)通过检查 (delete x) . (insert x) == id 来测试删除操作,如果 x 不是给定集合的成员。
1个回答

4

这取决于您可用的生成器。例如,如果您已经有 setOf1(生成至少一个元素的Set)和 setElements(从Set中获取元素),则可以使用 forAll 编写:

-- example implementations of both combinators
setOf1 :: (Arbitrary a, Ord a) => Gen a -> Gen (Set a)
setOf1 = fmap fromList . listOf1

setElements :: Set a -> Gen a
setElements = elements . toList

prop_delete =
  forAll (setOf1 arbitrary)   $ \theSet ->
  forAll (setElements theSet) $ \x ->
    not (member (x :: Int) (delete x theSet))

这与SetAndElement基本相同,但我们使用可重复使用的函数代替固定的data类型,这些函数可以用于进一步的测试:

prop_null =  forAll (setOf1 (arbitrary :: Gen Integer)) $ not . null

然而,即使您不编写setOf1setElements,对于简单的测试,forAll也可以非常简洁。
prop_delete :: (Arbitrary a, Ord a) => (NonEmptyList a) -> Property
prop_delete (NonEmpty xs) =
  let theSet = fromList xs
  in forAll (elements xs) $ \x ->
      not (member x (delete x theSet))

如果您提供了setElementsNonEmptySet,则可以这样写:
newtype NonEmptySet x = NonEmptySet {getNonEmptySet :: Set a}

instance (Ord a, Arbitray a) => Arbitrary (NonEmptySet a) where
  arbitrary = fmap NonEmptySet . setOf1 $ arbitrary

prop_delete :: (Arbitrary a, Ord a) => (NonEmptySet a) -> Property
prop_delete (NonEmptySet theSet) =
  forAll (setElements theSet) $ \x ->
    not (member x (delete x theSet))

这样,您可以在需要非空集合的测试中使用NonEmptySet,而仅在实际需要随机选择元素时才使用setElements


谢谢!这正是我在寻找的。我之前遇到过 forAll,但没有充分理解它。我的努力大多都朝着 coarbitrary 的解决方案错误地引导了。 - sircolinton

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接