Как вы пишете новый модификатор в 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 полезно для выражения дополнительных ограничений.

Другие вопросы по тегам