【问题标题】:Quickcheck: produce arbitrary elements of an arbitrary set快速检查:产生任意集合的任意元素
【发布时间】:2016-02-21 05:22:05
【问题描述】:

假设我正在为Data.Set 编写测试。我想检查从集合中删除元素是否有效,所以我可能会写这样的东西:

prop_deleteA it x = member x it ==> not (member x (delete x it))

假设it 有一个合适的Arbitrary 实例。但是,这依赖于在集合中碰巧存在的 x 的快速检查生成值,这通常不能保证。如果可以使x 依赖于it 以保证x 已经是it 的成员,那就更好了。我该怎么做?

我认为我可以写作

prop_deleteB it f = let x = f it
                   in not (member x (delete x 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 实现(容器包)通过检查(delete x) . (insert x) == id 如果x 还不是给定集合的成员来测试删除。

【问题讨论】:

    标签: haskell quickcheck


    【解决方案1】:

    这取决于您可用的生成器。例如,如果你已经有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
    

    但是,即使您不写setOf1setElementsforAll 对于简单的测试也可以相当简洁:

    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 解决方案。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2019-01-24
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-01-17
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多