Haskell Language
Snabb kontroll
Sök…
Förklara en fastighet
På det enklaste är en egenskap en funktion som returnerar en Bool
.
prop_reverseDoesNotChangeLength xs = length (reverse xs) == length xs
En egendom förklarar en hög nivå invariant av ett program. QuickCheck-testlöparen utvärderar funktionen med 100 slumpmässiga ingångar och kontrollerar att resultatet alltid är True
.
Enligt konvention har funktioner som är egenskaper namn som börjar med prop_
.
Kontrollerar en enda egendom
Funktionen quickCheck
testar en egenskap på 100 slumpmässiga ingångar.
ghci> quickCheck prop_reverseDoesNotChangeLength
+++ OK, passed 100 tests.
Om en egenskap misslyckas med viss inmatning, skriver quickCheck
ut ett motexempel.
prop_reverseIsAlwaysEmpty xs = reverse xs == [] -- plainly not true for all xs
ghci> quickCheck prop_reverseIsAlwaysEmpty
*** Failed! Falsifiable (after 2 tests):
[()]
Kontrollera alla egenskaper i en fil
quickCheckAll
är en mall Haskell-hjälper som hittar alla definitioner i den aktuella filen vars namn börjar med prop_
och testar dem.
{-# 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
Observera att return []
är obligatorisk. Det gör definitioner textuellt ovanför den raden synliga för Mall Haskell.
$ runhaskell QuickCheckAllExample.hs
=== prop_sortIdempotent from tree.hs:7 ===
+++ OK, passed 100 tests.
Slumpmässigt generera data för anpassade typer
Klassen Arbitrary
är för typer som kan skapas slumpmässigt av QuickCheck.
Den minimala implementeringen av Arbitrary
är den arbitrary
metoden, som körs i Gen
monaden för att producera ett slumpmässigt värde.
Här är ett exempel på Arbitrary
för följande datatyp av icke-tomma listor.
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
]
Använd implikation (==>) för att kontrollera egenskaper med förutsättningar
prop_evenNumberPlusOneIsOdd :: Integer -> Property
prop_evenNumberPlusOneIsOdd x = even x ==> odd (x + 1)
Om du vill kontrollera att en fastighet rymmer med tanke på att en förutsättning gäller, kan du använda operatören ==>
. Observera att om det är mycket osannolikt att godtyckliga ingångar matchar förutsättningen kan QuickCheck ge upp tidigt.
prop_overlySpecific x y = x == 0 ==> x * y == 0
ghci> quickCheck prop_overlySpecific
*** Gave up! Passed only 31 tests.
Begränsar storleken på testdata
Det kan vara svårt att testa funktioner med dålig asymptotisk komplexitet med snabbcheck eftersom slumpmässiga ingångar vanligtvis inte är avgränsade till storlek. Genom att lägga till en övre gräns på ingångens storlek kan vi fortfarande testa dessa dyra funktioner.
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
Genom att använda quickCheckWith
med en modifierad version av stdArgs
vi begränsa storleken på ingångarna till högst 10. I detta fall, eftersom vi genererar listor, betyder det att vi genererar listor med upp till storlek 10. Vår permutationsfunktion gör inte tar för lång tid att köra för dessa kortlistor men vi kan fortfarande vara rimligt säkra på att vår definition är korrekt.