Как запустить определенное количество тестов в QuickCheck? - PullRequest
0 голосов
/ 11 июня 2019

Я пытаюсь изучить Haskell и, в частности, QuickCheck.Хотя у Haskell много информации в Интернете, я пытаюсь создать какой-нибудь случайный тест с помощью QuickCheck.

Например, у меня есть следующий скрипт:

import Test.QuickCheck

whatAge :: Int -> Int -> Int -> Int -> Bool

whatAge age1 age2 age3 age4
  | age1 + age2 + age3 + age4 == 5 = True
  | otherwise = False

main = do
    verboseCheck  whatAge

При запуске он показывает:

*** Failed! Falsifiable (after 1 test): 
0
0
0
0

Достаточно справедливо, что он показал тест, в котором функция была ложной.

Я хотел бы сделать следующее:

  1. Создать 200 случайных тестовдаже в случае сбоя (иначе, даже если вывод функции whatAge имеет значение false)
  2. Возможность задать диапазон параметров моей функции, например:

       x1 range from 1 to 30
    
       x2 range from 1 to 40
    
       x3 range from 1 to 50
    
       x4 range from 1 to 60
    
  3. Быть способным генерировать неповторяющиеся тесты

Насколько я понимаю, nr 3 на самом деле невозможно с QuickCheck, для этого мне придется использовать smallCheck, но я не уверен впункт 1 и 2.

1 Ответ

0 голосов
/ 11 июня 2019

Для простых свойств вашего ввода вы можете создать новый тип с соответствующим экземпляром Arbitrary, который их захватывает.Итак:

{-# LANGUAGE AllowAmbiguousTypes #-}
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
{-# LANGUAGE KindSignatures #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TypeApplications #-}

import Data.Proxy
import GHC.TypeLits
import Test.QuickCheck

newtype Range (m :: Nat) (n :: Nat) a = Range { getVal :: a }
    deriving (Eq, Ord, Read, Show, Num, Real, Enum, Integral)

numVal :: forall n a. (KnownNat n, Num a) => a
numVal = fromInteger (natVal @n Proxy)

instance (KnownNat m, KnownNat n, Arbitrary a, Integral a) => Arbitrary (Range m n a) where
    arbitrary = fromInteger <$> choose (numVal @m, numVal @n)
    shrink hi = go (numVal @m) where
        go lo | lo == hi = [] | otherwise = lo : go ((lo+hi+1)`div`2) -- overflow? what's that? lmao

whatAge :: Range 1 30 Int -> Range 1 40 Int -> Range 1 50 Int -> Range 1 60 Int -> Bool
whatAge (Range age1) (Range age2) (Range age3) (Range age4)
    = age1 + age2 + age3 + age4 == 5

В ghci:

> verboseCheck whatAge
Failed:  
Range {getVal = 17}
Range {getVal = 29}
Range {getVal = 3}
Range {getVal = 16}

Failed:                                  
Range {getVal = 1}
Range {getVal = 29}
Range {getVal = 3}
Range {getVal = 16}

Failed:                                               
Range {getVal = 1}
Range {getVal = 1}
Range {getVal = 3}
Range {getVal = 16}

Failed:                                                
Range {getVal = 1}
Range {getVal = 1}
Range {getVal = 1}
Range {getVal = 16}

Failed:                                                
Range {getVal = 1}
Range {getVal = 1}
Range {getVal = 1}
Range {getVal = 1}

*** Failed! Falsifiable (after 1 test and 4 shrinks):  
Range {getVal = 1}
Range {getVal = 1}
Range {getVal = 1}
Range {getVal = 1}

Для более сложных свойств, где непонятно, как напрямую создать случайное значение, которое удовлетворяет свойству, вы можете использовать QuickCheck's (==>) оператор.Например, для проверки диапазона, как указано выше:

> verboseCheck (\x -> (1 <= x && x <= 30) ==> x*2 < 60)
Skipped (precondition false):
0

Passed:                
1

*** Failed! Falsifiable (after 33 tests):                  
30

Чтобы выполнить ровно 200 тестов, вы можете вызвать quickCheckWith, чтобы сделать один тест 200 раз;или вы можете напрямую generate проверить результаты, позвонив в вашу собственность по arbitrary вручную.

...