Professional Documents
Culture Documents
Trabajo Final
QuickCheck/Haskell
Lisibonny Beato
18 de febrero de 2010
QuickCheck/Haskell _____________________________________________________________________________
ndice general
Introduccin .................................................................................................................................. 3 El lenguaje de programacin Haskell ............................................................................................ 4 Caractersticas principales y aplicaciones ................................................................................. 4 Programa de ejemplo en Haskell .............................................................................................. 4 QuickCheck para Haskell ............................................................................................................... 6 Qu es QuickCheck? ................................................................................................................ 6 Propiedades............................................................................................................................... 6 Propiedades condicionales.................................................................................................... 7 Propiedades cuantificadas .................................................................................................... 7 Propiedades triviales ............................................................................................................. 8 Combinadores especiales.......................................................................................................... 8 Combinador Classify .............................................................................................................. 8 Combinador Collect ............................................................................................................... 9 Generadores de casos de prueba personalizados............................................................... 10 Caso de estudio: Gestin de nombres de archivo....................................................................... 11 Desarrollando un generador de casos de prueba ................................................................... 11 Definiendo la propiedad a probar ........................................................................................... 11 Recolectando informacin de los casos de prueba................................................................. 12 Desarrollando generadores de casos de prueba alternativos ................................................ 13 Modificando funciones y probando nuevamente las propiedades ........................................ 15 Conclusiones ............................................................................................................................... 16 Bibliografa .................................................................................................................................. 17
QuickCheck/Haskell _____________________________________________________________________________
Introduccin
A da de hoy la realizacin de pruebas sobre cdigo fuente es la tcnica ms utilizada para asegurar la calidad del software. Se calcula que ms de la mitad del esfuerzo empleado en el desarrollo de software se dedica a labores de testing. Es por esta razn que la investigacin para la automatizacin de esta tarea sea un rea muy activa en los ltimos aos. Est demostrado que los lenguajes de programacin funcionales estn mejor preparados para dicha automatizacin debido a la naturaleza puramente funcional de los mismos y a la carencia de efectos colaterales. Una de estas iniciativas de automatizacin de pruebas en lenguajes funcionales es QuickCheck, una herramienta originalmente desarrollada para el lenguaje de programacin Haskell y que ha sido implementada en otros lenguajes, tanto funcionales como imperativos. El objetivo de este trabajo es, precisamente, mostrar el funcionamiento de QuickCheck para Haskell y obtener conclusiones acerca de su desempeo, ventajas e inconvenientes todo ello derivado del uso de la herramienta en un caso de estudio concreto. Para ello se ha dividido el trabajo en las siguientes partes: La primera parte describe brevemente el lenguaje de programacin funcional Haskell y se muestran sus principales caractersticas y aplicaciones como base para hablar de la herramienta de pruebas QuickCheck. La segunda parte introduce el concepto de propiedad en QuickCheck y especficamente se tratan las propiedades condicionales, cuantificadas y triviales. Tambin se tratan algunas herramientas de QuickCheck relativas a la clasificacin y recoleccin de casos de pruebas, as como tambin a la construccin de generadores de casos de pruebas personalizados. En la tercera parte se desarrolla un caso de estudio que implementa una librera en Haskell para el manejo de nombres de archivo. Se busca probar distintas propiedades sobre dichas funciones, utilizando QuickCheck, y verificar el desempeo de las mismas con generadores de casos de prueba personalizados.
QuickCheck/Haskell _____________________________________________________________________________
module Main where factorial n = if n == 0 then 1 else n * factorial (n - 1) main = do putStrLn "Cul es el factorial de 5?" x <- readLn if x == factorial 5 then putStrLn "Correcto!" else putStrLn "Incorrecto!"
QuickCheck/Haskell _____________________________________________________________________________ A continuacin se muestra la ejecucin del programa utilizando WinHugs, que es la interfaz para Windows de Hugs, uno de los diversos intrpretes de Haskell.
QuickCheck/Haskell _____________________________________________________________________________
Propiedades
Las propiedades en QuickCheck son expresadas como funciones de Haskell. Como convencin son nombradas comenzando con el prefijo prop_. Dichas propiedades estn universalmente cuantificadas sobre sus parmetros, lo que significa que para cualquier valor que tomen dichos parmetros de entrada la propiedad se mantiene. Deben tener tipos monomrficos y, a menos que se utilicen combinadores especiales, siempre devuelven un valor booleano. A continuacin se muestra la estructura tpica de una propiedad en QuickCheck:
<nombre-de-la-propiedad> <variables> = <propiedad> where <supuestos> = <variable>::<tipo>
El siguiente ejemplo muestra una propiedad que intenta probar si aplicando la funcin reverse dos veces sobre una lista devuelve como resultado la lista original: prop_TransTrans xs = reverse (reverse xs) == xs where types = xs::[Int]
Para ejecutar las propiedades en QuickCheck se pueden utilizar una de estas dos sentencias: quickCheck <nombre-de-la-propiedad>: Ejecuta las pruebas sobre la propiedad y devuelve un resultado. verboseCheck <nombre-de-la-propiedad>: Ejecuta las pruebas y devuelve informacin de los casos de prueba utilizados, conjuntamente con el resultado de las pruebas.
QuickCheck/Haskell _____________________________________________________________________________ En este caso QuickCheck nos indica que de los cien casos de prueba generados, todos han pasado satisfactoriamente las pruebas. Sin embargo, si intentsemos probar que la aplicacin de la funcin reverse sobre una lista es igual a la lista origina, como se especifica en la siguiente propiedad: prop_TransId xs = reverse (xs) == xs where types = xs::[Int] Obtendramos la siguiente salida en Hugs:
Main> quickCheck prop_TransId Falsifiable, after 5 tests: [-3,0,-2]
En este caso QuickCheck nos dice que el quinto caso de prueba no satisface la propiedad especificada. Nos muestra tambin los datos que se utilizaron en dicho caso de prueba. En las siguientes sub-secciones veremos tres tipos especiales de propiedades que se pueden utilizar en QuickCheck: Condicionales, cuantificadas y triviales.
Propiedades condicionales
Este tipo especial de propiedad permite especificar una condicin que los casos generados por QuickCheck deben cumplir en orden de ser utilizados como casos de prueba. Si la condicin no se cumple para un caso de prueba, este es descartado y se prueba con el siguiente. La sintaxis de una propiedad de este tipo es como se especifica a continuacin: <condicin> ==> <propiedad> El siguiente ejemplo muestra una propiedad que busca probar si el mximo entre dos nmeros es siempre el segundo nmero cuando el primer nmero es menor o igual a este: prop_MaxMi x y = x <= y ==> max x y == y where types = (x::Int, y::Int)
Propiedades cuantificadas
Con este tipo de propiedad se puede especificar un generador de casos de prueba personalizado en vez de utilizar el generador por defecto que provee QuickCheck para el tipo de dato en cuestin. Con este tipo de propiedad es posible controlar la distribucin de los casos de prueba y, a diferencia del tipo anterior, no filtra datos por lo que el total de casos de prueba est disponible para ser utilizado. La sintaxis de una propiedad de este tipo es como se muestra a continuacin: forAll <generador> $ \<patrn> -> <propiedad> _____________________________________________________________________________ Software Basado en Componentes 7
QuickCheck/Haskell _____________________________________________________________________________ El siguiente ejemplo utiliza un generador de nmeros mayores que cien para ser utilizado en las pruebas de la propiedad del mximo entre dos nmeros descrita en la seccin anterior: prop_MaxMi x y = forAll mayorquecien $ \(x, y) -> max x y == y where types = (x::Int, y::Int)
Propiedades triviales
Con esta propiedad se pueden obtener estadsticas acerca de los casos triviales o casos en donde la condicin especificada ha sido verdadera. Esta propiedad devuelve la proporcin de estos casos con respecto al total de casos generados. La sintaxis de una propiedad de este tipo es como se muestra a continuacin: <condicin> `trivial` <propiedad> El siguiente ejemplo muestra la cantidad de casos triviales dentro del conjunto de casos de prueba de una propiedad que busca que las listas generadas estn ordenadas en orden de ser usadas como casos de prueba: ordenadas xs = and (zipWith (<=) xs (drop 1 xs)) insertar x xs = takeWhile (<x) xs++[x]++dropWhile (<x) xs prop_Insertar x xs = ordenadas xs ==> null xs `trivial` ordenadas (insertar x xs) where types = x::Int
Combinadores especiales
Combinador Classify
Con este combinador se puede ver la distribucin de los casos de prueba de acuerdo a un criterio especfico. La sintaxis del combinador Classify es como sigue: classify <condicin> <cadena>$ <propiedad>
QuickCheck/Haskell _____________________________________________________________________________ El siguiente ejemplo muestra el porcentaje de casos de prueba en el que las listas utilizadas como casos de prueba eran de tamao menor que uno y mayor que 2: ordenadas xs = and (zipWith (<=) xs (drop 1 xs)) insertar x xs = takeWhile (<x) xs++[x]++dropWhile (<x) xs prop_Insertar x xs = ordenadas xs ==> classify (length xs <= 1) "Listas de tamao < 1" $ classify (length xs > 2) "Listas de tamao > 2" $ ordenadas (insertar x xs) where types = x::Int Si ejecutamos esta propiedad en Hugs, tendramos una salida como la siguiente:
Main> quickCheck prop_Insertar OK, passed 100 tests. 81% Listas de tamao < 1. 7% Listas de tamao > 2.
Combinador Collect
La recoleccin de valores utilizando este combinador es similar al anterior, con la diferencia de que en este caso se recolectan todos los datos generados y la distribucin de los mismos se reporta al final de las pruebas. La sintaxis de este combinador es como sigue: collect <expresin>$ <propiedad> El siguiente ejemplo muestra el porcentaje de casos de prueba de acuerdo a todos los tamaos de las listas generadas: ordenadas xs = and (zipWith (<=) xs (drop 1 xs)) insertar x xs = takeWhile (<x) xs++[x]++dropWhile (<x) xs prop_Insertar x xs = ordenadas xs ==> collect (length xs)$ ordenadas (insertar x xs) where types = x::Int
Main> quickCheck prop_Insertar OK, passed 100 tests. 39% 0. 33% 1. 20% 2. 7% 3. 1% 4.
QuickCheck provee generadores de casos de prueba para la mayora de tipos de datos nativos de Haskell: Int, Char, Float, List. Sin embargo, en algunas ocasiones puede ser necesario utilizar generadores de casos de prueba personalizados en vez de los generadores por defecto de QuickCheck. A continuacin se definen combinadores tiles para la construccin de generadores de casos personalizados: choose: Este generador hace una seleccin aleatoria entre un intervalo especificado. El siguiente ejemplo muestra un generador que utiliza choose para especificar que se generarn dos nmeros enteros, el primero con valores en un rango de 7 a 10 y el segundo en un rango de 9 a 14: randomDouble :: Gen (Integer, Integer) randomDouble = do x1 <- choose (7,10) x2 <- choose (9,14) return (x1, x2)
oneof: Este generador selecciona de una lista de valores especificados. El siguiente ejemplo muestra un generador que utiliza oneof para especificar que se generarn dos nmeros enteros, el primero devolver un valor que ser 1 o 2 y el segundo 2 o 3: randomDouble :: Gen (Integer, Integer) randomDouble = do x1 <- oneof [return 1, return 2] x2 <- oneof [return 2, return 3] return (x1, x2)
frequency: Este generador al igual que el anterior selecciona de una lista de valores especificados, pero asigna un nmero que es usado para indicar la probabilidad con la que se utilizar cada valor en los casos de prueba que se generen. El siguiente ejemplo devuelve el primer entero devuelve los mismos valores que en el caso anterior, pero con frequency se le especifica que el valor 1 se generar el doble de veces el valor 2: randomDouble :: Gen (Integer, Integer) randomDouble = do x1 <- frequency[(2,return 1), (1,return 2)] x2 <- oneof [return 2, return 3] return (x1, x2)
Para tipos de datos definidos por el usuario, dichos generadores deben ser construidos utilizando la clase Arbitrary de Haskell. _____________________________________________________________________________ Software Basado en Componentes 10
QuickCheck/Haskell _____________________________________________________________________________
La cantidad de informacin recolectada puede ser grande y difcil de entender, por lo que podramos utilizar el combinador Classify para obtener una idea ms concreta de cmo se estn generando los nombres de archivo para los casos de prueba. Vamos a clasificarlos utilizando como criterio el tamao de la extensin de los nombres de archivo, especficamente visualizaremos los archivos sin extensin, los archivos de menos de cinco caracteres de extensin (a los cuales llamaremos normales) y los que tienen ms de cinco (a los cuales llamaremos largos). prop_nombresarchivo_correctos_classify :: Nombrearchivo -> Property prop_nombresarchivo_correctos_classify naStr = classify (length ext == 0) "Sin extensin" $ classify (length ext > 0 && length ext < 5) "Extensin normal" $ classify (length ext >= 5) "Extensin larga" $ unirNA (dividirNA na) == na where na = unNA naStr (nombre,ext) = dividirNA na _____________________________________________________________________________ Software Basado en Componentes 12
Si revisamos detenidamente la informacin obtenida, podemos ver que no estamos trabajando con el conjunto completo de nombres de archivo vlidos, como por ejemplo LEEME, .emacs, archivo. o documento.txt.old.
En este caso el generador ser capaz de sortear las dificultades que presentaba el generador anterior. Con una propiedad cuantificada utilizaremos este nuevo generador:
prop_nombresarchivo_correctos_nueva:: Property prop_nombresarchivo_correctos_nueva = forAll nombresarchivo $ \na -> unirNA (dividirNA na) == na
Con este nuevo generador podemos ver que los nombres de archivo son ahora ms variados: 41R8x. 1LAi.k .K3 .wu.mi1kqh8.Y7PKH6.p86.O
QuickCheck/Haskell _____________________________________________________________________________ Y ejecutando la propiedad podemos ver que todos los casos pasan la prueba:
Main> quickCheck prop_nombresarchivo_correctos_nueva OK, passed 100 tests.
Sin embargo algunos de estos nombres de archivo puede que no posean una extensin de archivo real: LEEME y .emacs, por ejemplo, por lo que si aplicamos la funcin dividirNA sobre ellos el resultado debera ser el mismo nombre de archivo. Para verificar si esta funcin es correcta en los casos anteriormente mencionados, vamos a desarrollar un generador de casos de prueba solo para archivos sin extensin y vamos a probar una nueva propiedad con este generador para verificar si la funcin dividirNA se comporta correctamente: NombrearchivosSinExt :: Gen String NombrearchivosSinExt = do nombre <- identifier punto <- opt (return ".") return ( punto ++ nombre ) prop_archivo_iguala_nombrearchivo :: Property prop_archivo_iguala_nombrearchivo = forAll NombrearchivosSinExt $ \na -> let (nombre,ext) = dividirNA na in nombre == na
Ejecutando la funcin dividirNA en Haskell con este contra-ejemplo vemos que identifica al nombre de archivo como la extensin del archivo y no como el nombre:
Main> dividirNA ".5tcd" ("",".5tcd")
QuickCheck/Haskell _____________________________________________________________________________
Con esta nueva funcin de divisin de nombres de archivos vamos a reescribir las propiedades prop_archivo_iguala_nombrearchivo y prop_nombresarchivo_correctos_nueva: prop_archivo_iguala_nombrearchivo_nueva :: Property prop_archivo_iguala_nombrearchivo_nueva = forAll NombrearchivosSinExt $ \na -> let (nombre,ext) = dividirNA_nueva na in nombre == na prop_nombresarchivo_correctos_nueva2:: Property prop_nombresarchivo_correctos_nueva2 = forAll nombresarchivo $ \na -> unirNA (dividirNA_nueva na) == na Ejecutando las propiedades en Hugs podemos ver que ahora todos los casos de prueba pasan las pruebas de ambas propiedades exitosamente:
Main> quickCheck prop_nombresarchivo_correctos_nueva2 OK, passed 100 tests. Main> quickCheck prop_archivo_iguala_nombrearchivo_nueva OK, passed 100 tests.
QuickCheck/Haskell _____________________________________________________________________________
Conclusiones
Despus de trabajar con QuickCheck en el caso de estudio podra concluir que la visin de alto nivel de esta herramienta va muy acorde con la naturaleza funcional de Haskell, dado que para probar los programas no es necesario disear pruebas individuales y planear cuidadosamente los datos que dichas pruebas utilizarn, cosa que si es importante cuando se trata con lenguajes imperativos. Por esta misma razn una de las ventajas de trabajar con QuickCheck es la reduccin de los tiempos de validacin de los programas, que se consigue gracias a que los casos de prueba son generados de forma automtica. QuickCheck, adems, verifica los programas intentando encontrar ejemplos que no cumplen las especificaciones dadas (contra-ejemplos). A pesar de que esto no es una garanta de consistencia, ayuda a reducir dicho riesgo dentro de nuestro cdigo. Otro punto fuerte de esta herramienta es que ayuda en las tareas de documentacin de los programas, debido a que otros programadores que tengan que ver o trabajar con nuestro cdigo pueden saber exactamente qu propiedades hemos probado en nuestros programas, la naturaleza de los casos de prueba que hemos utilizado y los resultados que hemos obtenido en dichas pruebas. Sin embargo, hay que ser cuidadosos con la distribucin de los casos de prueba: si los datos de prueba que se utilizan no estn bien distribuidos las conclusiones que se obtienen pueden no ser correctas.
QuickCheck/Haskell _____________________________________________________________________________
Bibliografa
1. Sitio Web de Haskell, http://www.haskell.org/ 2. Sitio Web de QuickCheck, http://www.cs.chalmers.se/~rjmh/QuickCheck/ 3. Artculo de QuickCheck en Wikipedia, http://en.wikipedia.org/wiki/QuickCheck