Haskell Language
Comprobación rápida
Buscar..
Declarar una propiedad
En su forma más simple, una propiedad es una función que devuelve un Bool
.
prop_reverseDoesNotChangeLength xs = length (reverse xs) == length xs
Una propiedad declara un invariante de alto nivel de un programa. El corredor de pruebas QuickCheck evaluará la función con 100 entradas aleatorias y verificará que el resultado sea siempre True
.
Por convención, las funciones que son propiedades tienen nombres que comienzan con prop_
.
Comprobando una sola propiedad
La función quickCheck
prueba una propiedad en 100 entradas aleatorias.
ghci> quickCheck prop_reverseDoesNotChangeLength
+++ OK, passed 100 tests.
Si una propiedad falla para alguna entrada, quickCheck
imprime un contraejemplo.
prop_reverseIsAlwaysEmpty xs = reverse xs == [] -- plainly not true for all xs
ghci> quickCheck prop_reverseIsAlwaysEmpty
*** Failed! Falsifiable (after 2 tests):
[()]
Comprobando todas las propiedades en un archivo
quickCheckAll
es un ayudante de Haskell de plantillas que encuentra todas las definiciones en el archivo actual cuyo nombre comienza con prop_
y las prueba.
{-# 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
Tenga en cuenta que la línea de return []
es obligatoria. Hace que las definiciones textuales sobre esa línea sean visibles para la Plantilla Haskell.
$ runhaskell QuickCheckAllExample.hs
=== prop_sortIdempotent from tree.hs:7 ===
+++ OK, passed 100 tests.
Generando datos al azar para tipos personalizados
La clase Arbitrary
es para tipos que QuickCheck puede generar aleatoriamente.
La implementación mínima de Arbitrary
es el método arbitrary
, que se ejecuta en la mónada Gen
para producir un valor aleatorio.
Aquí hay una instancia de Arbitrary
para el siguiente tipo de datos de listas no vacías.
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
]
Usando la implicación (==>) para verificar las propiedades con condiciones previas
prop_evenNumberPlusOneIsOdd :: Integer -> Property
prop_evenNumberPlusOneIsOdd x = even x ==> odd (x + 1)
Si desea verificar que una propiedad se cumple dado que se cumple una condición previa, puede usar el operador ==>
. Tenga en cuenta que si es muy poco probable que las entradas arbitrarias coincidan con la condición previa, QuickCheck puede darse por vencido antes.
prop_overlySpecific x y = x == 0 ==> x * y == 0
ghci> quickCheck prop_overlySpecific
*** Gave up! Passed only 31 tests.
Limitar el tamaño de los datos de prueba
Puede ser difícil probar funciones con poca complejidad asintótica mediante el uso de la comprobación rápida, ya que las entradas aleatorias no suelen tener límites de tamaño. Al agregar un límite superior en el tamaño de la entrada, aún podemos probar estas funciones costosas.
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
Al usar quickCheckWith
con una versión modificada de stdArgs
, podemos limitar el tamaño de las entradas para que sean como máximo 10. En este caso, ya que estamos generando listas, esto significa que generamos listas de hasta tamaño 10. Nuestra función de permutaciones no tomar demasiado tiempo para estas listas cortas, pero todavía podemos estar razonablemente seguros de que nuestra definición es correcta.