在使用QuickCheck进行测试时,我遇到了一些实例,在某些情况下可以简化编写自己的修饰符的过程,但是我不确定如何做到这一点。特别是,了解如何为列表和数字的生成器(例如Int
)的生成器编写修饰符将很有帮助。我知道NonEmptyList
,Positive
和NonNegative
已经在库中了,但是在某些情况下,如果我可以指定类似不仅是NonEmpty的列表,而且还是NonSingleton的列表(因此,它至少包含2个元素),或者是Int
大于1的列表,不仅是NonZero
或Positive
,还是Int(egral)
是偶数/奇数,等等。
答案 0 :(得分:2)
有很多方法可以做到这一点。这是一些例子。
您可以将组合器编写为函数。这是一个从任何Gen a
生成非单子列表的人:
nonSingleton :: Gen a -> Gen [a]
nonSingleton g = do
x1 <- g
x2 <- g
xs <- listOf g
return $ x1 : x2 : xs
此类型与内置listOf
函数的类型相同,并且可以以相同的方式使用:
useNonSingleton :: Gen Bool
useNonSingleton = do
xs :: [String] <- nonSingleton arbitrary
return $ length xs > 1
在这里,我利用Gen a
作为Monad
的优势,以便可以使用do
表示法来编写函数和属性,但也可以使用单子组合器来编写它如果您愿意的话。
该函数仅生成两个值x1
和x2
,以及任意大小的列表xs
(可以为空),并创建所有三个的列表。由于保证x1
和x2
是单个值,因此结果列表将至少具有这两个值。
有时候,您只想丢弃一小部分生成的值。您可以使用内置的==>
组合器,直接在属性中使用它:
moreThanOne :: (Ord a, Num a) => Positive a -> Property
moreThanOne (Positive i) = i > 1 ==> i > 1
虽然此属性是重言式的,但它表明您放置在==>
左侧的谓词可确保==>
右侧运行的所有谓词都已通过该谓词。
由于Gen a
是Monad
实例,因此您也可以使用现有的Monad
,Applicative
和Functor
组合器。这是一个将Functor
中的任何数字转换为偶数的数字:
evenInt :: (Functor f, Num a) => f a -> f a
evenInt = fmap (* 2)
请注意,这适用于 any Functor f
,而不仅仅是Gen a
。但是,由于Gen a
是Functor
,因此您仍然可以使用evenInt
:
allIsEven :: Gen Bool
allIsEven = do
i :: Integer <- evenInt arbitrary
return $ even i
此处的arbitrary
函数调用将创建一个不受约束的Integer
值。 evenInt
然后甚至将其乘以2。
您还可以使用newtype
创建自己的数据容器,然后使其成为Arbitrary
实例:
newtype Odd a = Odd a deriving (Eq, Ord, Show, Read)
instance (Arbitrary a, Num a) => Arbitrary (Odd a) where
arbitrary = do
i <- arbitrary
return $ Odd $ i * 2 + 1
如果需要,这还使您能够实现shrink
。
您可以在如下属性中使用newtype
:
allIsOdd :: Integral a => Odd a -> Bool
allIsOdd (Odd i) = odd i
Arbitrary
实例对类型arbitrary
使用a
来生成不受约束的值i
,然后将其加倍并加一个,从而确保该值是奇数。
看看QuickCheck documentation,了解更多内置组合器。我特别发现choose
,elements
,oneof
和suchThat
对于表达附加约束很有用。