Quickcheck:生成任意集合的任意元素
Quickcheck: produce arbitrary elements of an arbitrary set
假设我正在为 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))
其中 f :: Set a -> a
是通过任意方式适当定义的。然而,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
if x
是否已经不是一个来测试删除给定集合的成员。
这取决于您可用的发电机。例如,如果您已经有 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
然而,即使您不写 setOf1
或 setElements
,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))
如果你提供setElements
和NonEmptySet
,这可以写成
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
仅用于那些您实际需要随机选择元素的测试。
假设我正在为 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))
其中 f :: Set a -> a
是通过任意方式适当定义的。然而,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
if x
是否已经不是一个来测试删除给定集合的成员。
这取决于您可用的发电机。例如,如果您已经有 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
然而,即使您不写 setOf1
或 setElements
,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))
如果你提供setElements
和NonEmptySet
,这可以写成
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
仅用于那些您实际需要随机选择元素的测试。