Вопрос по haskell – Лучшие практики Haskell QuickCheck (особенно при тестировании классов типов)

18

Я только начал использовать QuickCheck с кучей кода на Haskell. Я отстал от времени, я знаю. Этот вопрос состоит из двух частей:

Во-первых, каковы общие рекомендации по быстрой проверке? До сих пор я обнаружил следующее:

Name your tests prop_* (annoying, because everything else is camelCase) Test exported code (if you're testing internals you're likely doing it wrong) Test properties, not examples Don't say X is out of range, Y is in range Instead, say if x is out of range, normalize x ≠ x (or some other such property)

Но я все еще цепляюсь за другие лучшие практики. В частности:

Where are properties kept? The same file? in a test/ directory? (If so, then how do you import the stuff in src/?) in a Properties/ directory under src?

Самое главное, как мы склонны идти о тестировании свойств на классах типов? Например, рассмотрим следующий (упрощенный) тип класса:

class Gen a where
    next :: a -> a
    prev :: a -> a

Я хотел бы проверить собственность∀ x: prev (next x) == x, Конечно, это включает в себя написание тестов для каждого экземпляра. Утомительно писать одно и то же свойство для каждого экземпляра, особенно когда тест более сложный. Каков стандартный способ обобщения таких тестов?

Для импорта при использовании параллельногоsrc/ а такжеtest/ каталоги, которые вы хотите установитьHs-Source-Dirs: src, test в вашем.cabal файл, так что оба каталога находятся в пути поиска модуля. hammar
Почему внутренние компоненты не могут иметь свойства? alternative
Они, конечно, могут, просто сложнее получить к ним тесты и (по моему опыту) гораздо полезнее тестировать экспортированное поведение, а не детали реализации. So8res

Ваш Ответ

3   ответа
10

prop_ Соглашение пришло из КК, идущего со скриптом, который запускал все функции, которые начинались сprop_ как тесты. Так что нет никакой реальной причины для этого, но этоdoes визуально выделяются (поэтому свойство для функцииfoo являетсяprop_foo).

И нет ничего плохого в тестировании внутренних устройств. Есть два способа сделать это:

Put the properties in the same module as the internals. This makes the module bigger, and requires an unconditional dependency on QC for the project (unless you use CPP hackery).

Have internals in a non-exported module, with the functions to actually be exported re-exported from another module. Then you can import the internal module into one that defines the QC properties, and that module is only built (and has a QC dependency) if a flag specified in the .cabal file is used.

Если ваш проект большой, то имея отдельныйsrc/ а такжеtest/ каталоги могут быть полезны (хотя различие может помешать вам тестировать внутренние компоненты). Но если ваш проект не так уж велик (и в любом случае находится под одной общей иерархией модулей), тогда нет реальной необходимости разделять его таким образом.

Как сказал Норман Рэмси в своем ответе, для классов типов вы можете просто определить свойство какon класс типов и использовать соответственно.

Error: User Rate Limit Exceededtest-suiteError: User Rate Limit Exceeded
18

Вы этого не делаете. Вы пишете свойство один раз для класса:

class Gen a where
    next :: a -> a
    prev :: a -> a

np_prop :: (Eq a, Gen a) => a -> Bool
np_prop a = prev (next a) == a

Затем, чтобы проверить это, вы приведете к определенному типу:

quickCheck (np_prop :: Int -> Bool)
quickCheck (np_prop :: String -> Bool)

Другие ваши вопросы, с которыми я не могу помочь.

3

{-# LANGUAGE GADTs, ScopedTypeVariables #-}
import Test.QuickCheck hiding (Gen)

class Gen a where
  next :: a -> a
  prev :: a -> a

np_prop :: SomeGen -> Bool
np_prop (SomeGen a) = prev (next a) == a

main :: IO ()
main = quickCheck np_prop

instance Gen Bool where
  next True = False
  next False = True
  prev True = False
  prev False = True

instance Gen Int where
  next = (+ 1)
  prev = subtract 1

data SomeGen where
  SomeGen :: (Show a, Eq a, Arbitrary a, Gen a) => a -> SomeGen

instance Show SomeGen where
  showsPrec p (SomeGen a) = showsPrec p a
  show (SomeGen a) = show a

instance Arbitrary SomeGen where
  arbitrary = do
    GenDict (Proxy :: Proxy a) <- arbitrary
    a :: a <- arbitrary
    return $ SomeGen a
  shrink (SomeGen a) =
    map SomeGen $ shrink a

data GenDict where
  GenDict :: (Show a, Eq a, Arbitrary a, Gen a) => Proxy a -> GenDict

instance Arbitrary GenDict where
  arbitrary =
    elements
    [ GenDict (Proxy :: Proxy Bool)
    , GenDict (Proxy :: Proxy Int)
    ]

data Proxy a = Proxy

Класс типов преобразуется в экзистенциально количественно определяемый словарь, в которомArbitrary экземпляр определен. этоArbitrary Экземпляр словаря затем используется для определения экземпляраArbitrary для экзистенциально количественных значений.

Другой пример приведен наhttps://github.com/sonyandy/var/blob/4e0b12c390eb503616d53281b0fd66c0e1d0594d/tests/properties.hs#L217.

Это может быть далее обобщено (и шаблон сокращен), если вы хотите использоватьConstraintKinds, Следующее определяется только один раз.

data Some c where
  Some :: (Show a, Arbitrary a, c a) => a -> Some c

instance Show (Some c) where
  showsPrec p (Some a) = showsPrec p a
  show (Some a) = show a

instance Arbitrary (Dict c) => Arbitrary (Some c) where
  arbitrary = do
    Dict (Proxy :: Proxy a) :: Dict c <- arbitrary
    a :: a <- arbitrary
    return $ Some a
  shrink (Some a) =
    map Some $ shrink a

data Dict c where
  Dict :: (Show a, Arbitrary a, c a) => Proxy a -> Dict c

data Proxy a = Proxy

class (c a, d a) => (c &&# d) a
instance (c a, d a) => (c &&# d) a

Для каждого класса типа, который вы хотите проверить,Arbitrary экземплярDict необходимо.

instance Arbitrary (Dict (Eq &&# Gen)) where
  arbitrary =
    elements
    [ Dict (Proxy :: Proxy Bool)
    , Dict (Proxy :: Proxy Int)
    ]

np_prop :: Some (Eq &&# Gen) -> Bool
np_prop (Some a) = prev (next a) == a

Похожие вопросы