Haskell Language
Controllo veloce
Ricerca…
Dichiarare una proprietà
Nel modo più semplice, una proprietà è una funzione che restituisce un Bool
.
prop_reverseDoesNotChangeLength xs = length (reverse xs) == length xs
Una proprietà dichiara un invariante di alto livello di un programma. Il test runner QuickCheck valuterà la funzione con 100 input casuali e verificherà che il risultato sia sempre True
.
Per convenzione, le funzioni che hanno proprietà hanno nomi che iniziano con prop_
.
Controllo di una singola proprietà
La funzione quickCheck
verifica una proprietà su 100 input casuali.
ghci> quickCheck prop_reverseDoesNotChangeLength
+++ OK, passed 100 tests.
Se una proprietà fallisce per qualche input, quickCheck
stampa un controesempio.
prop_reverseIsAlwaysEmpty xs = reverse xs == [] -- plainly not true for all xs
ghci> quickCheck prop_reverseIsAlwaysEmpty
*** Failed! Falsifiable (after 2 tests):
[()]
Controllo di tutte le proprietà in un file
quickCheckAll
è un helper Template Haskell che trova tutte le definizioni nel file corrente il cui nome inizia con prop_
e le prop_
.
{-# LANGUAGE TemplateHaskell #-}
import Test.QuickCheck (quickCheckAll)
import Data.List (sort)
idempotent :: Eq a => (a -> a) -> a -> Bool
idempotent f x = f (f x) == f x
prop_sortIdempotent = idempotent sort
-- does not begin with prop_, will not be picked up by the test runner
sortDoesNotChangeLength xs = length (sort xs) == length xs
return []
main = $quickCheckAll
Si noti che è richiesta la riga return []
. Rende le definizioni testuali sopra quella linea visibili a Template Haskell.
$ runhaskell QuickCheckAllExample.hs
=== prop_sortIdempotent from tree.hs:7 ===
+++ OK, passed 100 tests.
Generazione casuale di dati per tipi personalizzati
La classe Arbitrary
è per tipi che possono essere generati casualmente da QuickCheck.
L'implementazione minima di Arbitrary
è il metodo arbitrary
, che viene eseguito nella Gen
Monad per produrre un valore casuale.
Ecco un'istanza di Arbitrary
per il seguente tipo di dati di liste non vuote.
import Test.QuickCheck.Arbitrary (Arbitrary(..))
import Test.QuickCheck.Gen (oneof)
import Control.Applicative ((<$>), (<*>))
data NonEmpty a = End a | Cons a (NonEmpty a)
instance Arbitrary a => Arbitrary (NonEmpty a) where
arbitrary = oneof [ -- randomly select one of the cases from the list
End <$> arbitrary, -- call a's instance of Arbitrary
Cons <$>
arbitrary <*> -- call a's instance of Arbitrary
arbitrary -- recursively call NonEmpty's instance of Arbitrary
]
Utilizzo dell'implicazione (==>) per controllare le proprietà con le condizioni preliminari
prop_evenNumberPlusOneIsOdd :: Integer -> Property
prop_evenNumberPlusOneIsOdd x = even x ==> odd (x + 1)
Se si desidera verificare che una proprietà sia valida dato che una precondizione è valida, è possibile utilizzare l'operatore ==>
. Nota che se è molto improbabile che gli input arbitrari soddisfino la condizione preliminare, QuickCheck può rinunciare presto.
prop_overlySpecific x y = x == 0 ==> x * y == 0
ghci> quickCheck prop_overlySpecific
*** Gave up! Passed only 31 tests.
Limitazione della dimensione dei dati di test
Può essere difficile testare le funzioni con una scarsa complessità asintotica usando quickcheck in quanto gli input casuali non sono in genere legati alle dimensioni. Aggiungendo un limite superiore alla dimensione dell'input possiamo ancora testare queste costose funzioni.
import Data.List(permutations)
import Test.QuickCheck
longRunningFunction :: [a] -> Int
longRunningFunction xs = length (permutations xs)
factorial :: Integral a => a -> a
factorial n = product [1..n]
prop_numberOfPermutations xs =
longRunningFunction xs == factorial (length xs)
ghci> quickCheckWith (stdArgs { maxSize = 10}) prop_numberOfPermutations
Usando quickCheckWith
con una versione modificata di stdArgs
possiamo limitare la dimensione degli input al massimo 10. In questo caso, dato che stiamo generando liste, questo significa che generiamo liste fino alla dimensione 10. La nostra funzione permutazioni non impiegare troppo tempo per eseguire queste brevi liste, ma possiamo ancora essere ragionevolmente sicuri che la nostra definizione sia corretta.