2012-04-06 7 views
15

Acabo de empezar a utilizar QuickCheck con un montón de código Haskell. Estoy detrás de los tiempos, lo sé. Esta pregunta es de dos partes:Prácticas recomendadas de Haskell QuickCheck (especialmente cuando se prueban clases de tipo)

En primer lugar, ¿cuáles son las mejores prácticas generales para Quick Check? Hasta ahora, he recogido los siguientes:

  • Nombre su prop_ pruebas * (molesto, porque todo lo demás es camelCase)
  • Prueba código exportado (si se está probando internos probable que estés haciendo equivocadas)
  • propiedades de la prueba, no ejemplos
    • no dicen X is out of range, Y is in range
    • En cambio, decir if x is out of range, normalize x ≠ x (o alguna otra propiedad)

Pero todavía estoy aferrándome a otras mejores prácticas. Particularmente:

  • ¿Dónde se guardan las propiedades?
    • ¿El mismo archivo?
    • en un directorio test/? (De ser así, ¿cómo importa las cosas en src/?)
    • en un directorio Properties/ bajo src?

que es más importante, ¿cómo tienden a ir sobre la prueba de propiedades de tipo clases? Por ejemplo, considere la siguiente (simplificado) Clase Tipo:

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

me gustaría probar la propiedad ∀ x: prev (next x) == x. Por supuesto, esto implica escribir pruebas para cada instancia. Es tedioso escribir la misma propiedad para cada instancia, especialmente cuando la prueba es más complicada. ¿Cuál es la forma estándar de generalizar tales pruebas?

+4

Para las importaciones paralelas cuando se utiliza '/' y '' test/src directorios, tendrá que establecer 'Hs-Fuente-dirs: src, test' en su archivo' .cabal' de manera que ambos directorios están en la ruta de búsqueda del módulo. – hammar

+2

¿Por qué los internos no tienen propiedades? – alternative

+0

Ciertamente pueden hacerlo, es más difícil hacerse pruebas y, en mi experiencia, es mucho más útil probar el comportamiento exportado en lugar de los detalles de implementación. – So8res

Respuesta

10

Creo que la convención prop_ provino de QC viene con un script que ejecutó todas las funciones que comenzaron con prop_ como pruebas. Entonces no hay una razón real para hacerlo, pero hace se destaca visualmente (por lo que la propiedad para una función foo es prop_foo).

Y no hay nada de malo en probar las partes internas.Hay dos formas de hacerlo:

  • Coloque las propiedades en el mismo módulo que las partes internas. Esto hace que el módulo sea más grande y requiere una dependencia incondicional del control de calidad para el proyecto (a menos que use hackers de CPP).

  • Tienen internos en un módulo no exportado, con las funciones para exportar realmente reexportados desde otro módulo. Luego puede importar el módulo interno en uno que defina las propiedades de QC, y ese módulo solo se genera (y tiene una dependencia de QC) si se usa un indicador especificado en el archivo .cabal.

Si su proyecto es grande, teniendo separados src/ y test/ directorios pueden ser útiles (a pesar de tener una distinción puede evitar que partes internas de prueba). Pero si su proyecto no es tan grande (y, de todos modos, reside bajo una jerarquía global de módulos), entonces no hay una necesidad real de dividirlo así.

Como dijo Norman Ramsey en su respuesta, para las clases de tipos, puede definir la propiedad como en typeclass y usar en consecuencia.

+0

Algo que solemos hacer es tener una sección Cabal 'test-suite' que dependa directamente de los módulos internos (en lugar de la biblioteca definida en el mismo archivo .cabal) y de esa manera puede probarlos a costa de alguna compilación adicional hora. – tibbe

15

Es tedioso de escribir la misma propiedad para cada instancia

Usted no lo hace. Usted escribe la propiedad una vez para la clase:

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 

Luego de probarlo, que lances a un tipo particular:

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

Sus otras preguntas que no puedo ayudar.

3

Trate

{-# 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 

La clase de tipo se materializado en un diccionario existencialmente cuantificada, en el que se define una instancia Arbitrary. Esta instancia de diccionario Arbitrary se usa para definir una instancia de Arbitrary para valores cuantificados existencialmente.

Otro ejemplo se da en https://github.com/sonyandy/var/blob/4e0b12c390eb503616d53281b0fd66c0e1d0594d/tests/properties.hs#L217.

Esto se puede generalizar más (y el texto estándar reducido) si está dispuesto a usar ConstraintKinds. Lo siguiente se define solo una vez.

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 

Para cada clase de tipo que desea probar, se requiere una instancia de ArbitraryDict.

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 
Cuestiones relacionadas