Как вы пишете новый модификатор в QuickCheck
Я сталкивался с несколькими случаями в моем тестировании с QuickCheck, когда в некоторых случаях было бы проще написать свои собственные модификаторы, но я не совсем уверен, как это можно сделать. В частности, было бы полезно узнать, как написать модификатор для генераторов списков и чисел (таких как Int
). Я в курсе NonEmptyList
, а также Positive
а также NonNegative
, которые уже находятся в библиотеке, но в некоторых случаях это сделало бы мои тесты более понятными, если бы я мог указать что-то вроде списка, который является не только NonEmpty, но и NonSingleton (то есть он имеет по крайней мере 2 элемента) Int
это больше, чем 1, а не только NonZero
или же Positive
или Int(egral)
это четное / нечетное и т. д.
1 ответ
Есть много способов, которыми вы можете сделать это. Вот несколько примеров.
Функция комбинатора
Вы можете написать комбинатор как функцию. Вот тот, который генерирует не-одиночные списки из любого 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)
Обратите внимание, что это работает для любого Functor f
не только для Gen a
, Так как, однако, Gen a
это Functor
, вы все еще можете использовать evenInt
:
allIsEven :: Gen Bool
allIsEven = do
i :: Integer <- evenInt arbitrary
return $ even i
arbitrary
вызов функции здесь создает неограниченную Integer
стоимость. evenInt
затем делает это даже путем умножения его на два.
Произвольные новинки
Вы также можете использовать 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 для многих других встроенных комбинаторов. Я особенно нахожу choose
, elements
, oneof
, а также suchThat
полезно для выражения дополнительных ограничений.