Pruebas unitarias en Haskell Jos Daniel Prez Vigo
Pruebas unitarias en Haskell José Daniel Pérez Vigo Juan Diego Ruiz Perea
Indice n Introducción e. Xtreme Programming (XP) ¨ Test Driven Development (TDD) ¨ Pruebas Unitarias n ¨ n Hunit ¨ Ventajas e Inconvenientes ¨ n Pruebas Unitarias en otros lenguajes Junit ¨ Otros ¨ Pruebas unitarias en Haskell Otro tipo de pruebas en haskell Quick. Check ¨ Ventajas ¨ n n Conclusiones Bibliografía
Introducción n e. Xtreme Programming (XP) n Test Driven Development (TDD) n Pruebas Unitarias
e. Xtreme Programming n n n La programación extrema o e. Xtreme Programming (XP) fue formulada por Kent Beck, autor del primer libro sobre la materia, Extreme Programming Explained: Embrace Change en 1996. Se trata del proceso ágil de desarrollo de software más famoso y también el más trasgresor y polémico. Es el más popular entre los MAs: 38% del mercado.
Características fundamentales n n Desarrollo iterativo e incremental: pequeñas mejoras, unas tras otras. Pruebas unitarias continuas, frecuentemente repetidas y automatizadas, incluyendo pruebas de regresión. Se aconseja escribir el código de la prueba antes de la codificación. Programación por parejas Frecuente interacción del equipo de programación con el cliente o usuario. Se recomienda que un representante del cliente trabaje junto al equipo de desarrollo.
n Corrección de todos los errores antes de añadir nueva funcionalidad. Hacer entregas frecuentes. n Refactorización del código, es decir, reescribir ciertas partes del código para aumentar su legibilidad y mantenibilidad pero sin modificar su comportamiento. n Propiedad del código compartida. n Simplicidad en el código: es la mejor manera de que las cosas funcionen.
Test Driven Development (TDD) n O desarrollo guiado por pruebas es una metodología, utilizada de XP, consistente en escribir las pruebas antes que el código. n TDD es muy efectiva gracias a la automatización de las pruebas de programador (programmer unit test) y al hecho de que las herramientas para implementar está técnica son gratis y totalmente funcionales.
n Para que funcione el desarrollo guiado por pruebas, el sistema tiene que ser lo suficientemente flexible como para permitir el testeo automático de software. n Estas propiedades permiten una rápida retroalimentación en el diseño y la corrección.
Pruebas unitarias n O pruebas de unidad, consisten en comprobaciones (manuales o automatizadas) que se realizan para verificar que el código correspondiente a un módulo concreto de un sistema software funciona de acuerdo con los requisitos del sistema. n Primero escribimos un caso de prueba y sólo después implementamos el código necesario para que el caso de prueba se pase con éxito.
Ventajas n n n Al escribir primero los casos de prueba, definimos de manera formal los requisitos que esperamos que cumpla nuestra aplicación. Al escribir una prueba de unidad, pensamos en la forma correcta de utilizar un módulo que aún no existe. Los casos de prueba nos permiten perder el miedo a realizar modificaciones en el código.
Pruebas Unitarias en otros lenguajes ¨ JUnit ¨ Otros
JUnit n n JUnit es la biblioteca de pruebas de unidad estándar de facto para Java, sus dos propósitos básicos son: ofrecer una estructura que permita la especificación de casos de prueba, y proporcionar un controlador capaz de llevar a cabo las pruebas de forma automática. Fue desarrollado por Kent Beck y Erich Gamma, y es sin lugar a dudas la biblioteca más importante de Java perteneciente a terceras empresas.
n Gracias a JUnit, los códigos Java tienden a ser mucho mas robustos, fiables, y libres de errores. n JUnit (a su vez inspirado en Smalltalk SUnit) ha inspirado una familia entera de herramientas x. Unit, trasladando los beneficios de las pruebas de unidad a una amplia gama de lenguajes.
Diagrama UML de JUnit
Ejemplo Junit: Clase Divisa n public class Divisa { n private int importe; private String denominación; n n n public Divisa(int imp, String den) { importe = imp; denominación = den; } n n n public int importe(){ n return importe; } n public String denominación(){ return denominación; } n n public Divisa sumar (Divisa d) throws Exception { Divisa res; if (d. denominación == denominación) { res = new Divisa(d. importe+importe, denominación); return res; }else{ throw new Exception("Denominación diferente: no se pueden sumar"); } n n n n n }
n n public class Divisa. Test extends Test. Case { private Divisa d 5 EUR, d 12 EUR , d 17 EUR, d 8 USD, expected; n public static void main(String[] args) { junit. textui. Test. Runner. run(Divisa. Test. class); } public void set. Up(){ d 5 EUR= new Divisa(5, "EUR"); d 12 EUR= new Divisa(12, "EUR"); Test. Suite ts= new Test. Suite(Divisa. Test. class); Set. Up() : se ejecuta antes de cada test //ts. add(otro. Test); ); // d 8 USD = new Divisa(8, "USD"); Tear. Down(): se ejecutan despues de cada test Tear. Down(): se ejecutan despues d 17 EUR= new Divisa(17, "EUR"); } junit. textui. Test. Runner. run(ts); n n n n n public void test. Sumar() { try{ expected= (Divisa) d 5 EUR. sumar(d 12 EUR); /*assert. Equal(expected, d 17 EUR)*/ assert. True(expected. equals(d 17 EUR)); n n n expected= (Divisa) d 5 EUR. sumar(d 5 EUR); assert. False(expected. equals(d 17 EUR)); n n n expected= (Divisa) d 5 EUR. sumar(d 8 USD); fail(“No se ha lanzado ninguna excepción”); }catch (Divisa. Exception e) { assert. True(e. equals(new Divisa. Exception())); } n n n }
Herramientas para otros lenguajes n n n n NUnit (. NET: C#, J#, VB y C++) CPPUnit (C++) DUnit (Delphi) py. Unit. Perf(python) access. Unit(Access) NEunit(cualquier lenguaje Unix) CUnit (C) ETester (Eiffel)… (para ver más http: //www. xprogramming. com/software. htm)
Pruebas unitarias en Haskell ¨ Hunit ¨ Ventajas e Inconvenientes
HUnit n n n HUnit es un framework para pruebas de unidad realizado para Haskell, e inspirado por la herramienta de Java: JUnit. Fue creado en 2002 por Dean Herington estudiante graduado en la Universidad de Carolina del Norte, en el departamento de Ciencias de la computación. Con HUnit, como con JUnit, podemos fácilmente crear test, nombrarlos, agruparlos dentro de suites, y ejecutarlos con el marco de trabajo que valida los resultados automáticamente.
n De la misma manera que en Junit, el programador especifica una serie de pruebas del tipo: ¨ n Luego se llama a la funcion que ejecuta las pruebas: ¨ n assert. Equal "nombre_prueba" <resultado> <funcion_a_probar> run. Test. TT <pruebas> Ésto muestra por pantalla los resultados de las pruebas.
1. En el módulo donde se creen los test debemos importar el módulo de Hunit. ¨ 2. Import Hunit Definir los casos de prueba: test 1 = Test. Case (assert. Equal “para (foo 3), " (1, 2) (foo 3) ) test 2 = Test. Case (do (x, y) <- part. A 3 assert. Equal “para el primer resultado, " 5 x b <- part. B y assert. Bool ("(part. B " ++ show y ++ ") failed") b)
3. Nombrar los test y agruparlos: tests = Test. List [Test. Label "test 1" test 1, Test. Label "test 2" test 2] 4. Ejecutar un grupo de casos de prueba. > run. Test. TT tests Cases: 2 Tried: 2 Errors: 0 Failures: 0 > run. Test. TT tests ### Failure in: 0: test 1 para (foo 3), expected: (1, 2) but got: (1, 3) Cases: 2 Tried: 2 Errors: 0 Failures: 1
Escribiendo Test Los asertos (Assertions) se combinan creando casos de prueba (Test. Case), y los casos de prueba se combinan en tests. n Hunit también provee de características avanzadas para especificaciones de test más adecuadas. n
Asertos n Es el bloque básico para construir test. data Assertion = IO () assert. Failure : : String -> Assertion assert. Failure msg = io. Error (user. Error ("HUnit: " ++ msg)) )) assert. Bool : : String -> Bool -> Assertion assert. Bool msg b = unless b (assert. Failure msg) assert. String : : String -> Assertion assert. String s = unless (null s) (assert. Failure s)
assert. Equal : : (Eq a, Show a) => String -> a -> Assertion assert. Equal preface expected actual = unless (actual == expected) (assert. Failure msg) where msg = (if null preface then "" else preface ++ "n") ++ "expected: " ++ show expected ++ "n but got: " ++ show actual n Dado que los asertos son computaciones IO pueden ser combinados usando los operadores (>>=) y (>>), y la notación do para formar asertos colectivos. Esta combinación fallará si cualquiera de los asertos que lo componen falla y terminará su ejecución con el primero de los mismos.
Caso de Prueba(Cd. P) n Es la unidad de una ejecución de prueba, es decir, Cd. P distintos, son ejecutados independientemente. El fallo de uno es independiente del fallo de cualquier otro. n Un Cd. P consiste en un aserto simple o posiblemente colectivo. Un Cd. P puede involucrar una serie de pasos, cada uno terminado en un aserto, donde cada paso debe tener éxito para poder continuar con el Cd. P.
n Para crear un Cd. P desde un aserto se aplica el constructor Test. Case. Ejemplos: Test. Case (return ()) Test. Case (assert. Equal “para x, ” 3 x) n Se han implementado también operadores para crear Cd. P de manera mas sencilla, estos son: @? , @=? , @? = ~? , ~=? , ~? = , ~:
Tests n Cuando se tiene más de un Cd. P se hace necesario nombrarlos y agruparlos en listas, para ello usamos: data Test = Test. Case Assertion | Test. List [Test] | Test. Label String Test n Para conocer el número de Cd. P que componen un test podemos usar la función: test. Case. Count : : Test -> Int
Ejecución de Pruebas n n n HUnit esta estructurado para soportar múltiples controladores de test. Todos los controladores comparten un modelo común de ejecución de test. Sólo difieren en como son mostrados los resultados. La ejecución de un test implica la ejecución monádica de cada uno de sus Cd. P
Ejecución de Pruebas (cont) n Durante la ejecución 4 contadores sobre los casos de prueba son mantenidos: data Counts = Counts { cases, tried, errors, failures : : Int } deriving (Eq, Show, Read) n n cases: número de Cd. P incluidos en el test. tried: número de Cd. P que han sido ejecutados. errors: número de Cd. P cuya ejecución termina con una excepción no esperada. failures: número de Cd. P cuya ejecución termina en fallo.
Ejecución de Pruebas (cont) n n Tal y como procede la ejecución de una prueba, son tres las clases de eventos de informe que se comunican al controlador de la prueba. start: Antes de la inicialización del test, se le manda este evento al controlador para reportar los contadores (excluyendo el Cd. P actual). error: Cuando un Cd. P finaliza con un error, el mensaje de error es reportado, junto con el camino del Cd. P y os contadores actuales (incluyendo el Cd. P actual). failure: Cuando un Cd. P finaliza con un fallo, el mensaje de fallo es reportado, junto con el camino del Cd. P y os contadores actuales (incluyendo el Cd. P actual).
Ventajas e Inconvenientes n Ventajas La especificación de pruebas en HUnit es incluso más concisa y flexible que en JUnit gracias a la naturaleza del lenguaje Haskell. ¨ El diseño del tipo Test es conciso, flexible y conveniente para la especificación de pruebas. Es más la naturaleza de Haskell aumenta significativamente estas cualidades. ¨ n n Combinar asertos y otro tipo de código para construir casos de prueba es fácil con la mónada IO. Usando funciones sobrecargadas y operadores especiales, la especificación de asertos y pruebas es extremadamente compacto. Estructurando un árbol de pruebas por valor, más que por nombre como en JUnit, provee de una especificación de juego de test más conveniente, flexible y robusto. En particular, un juego de test puede ser computado más fácilmente sobre la marchar que en otros test frameworks. Las facilidades de poderosa abstracción de Haskell provee de un sopote sin igual para la refactorización de test
Ventajas e Inconvenientes (cont) n Inconvenientes ¨ Según Diego Berrueta [1] la limitación más importante encontrada al escribir las pruebas unitarias consiste en la imposibilidad de comprobar las situaciones de error debido a que Haskell no dispone de un mecanismo común para tratar las condiciones anormales en funciones puras. ¨ Otra dificultad viene determinada por la necesidad, en muchas ocasiones, de crear estructuras de datos complejas necesarias para su uso en todos los casos de prueba que componen las pruebas unitarias.
Otro tipo de pruebas en haskell ¨Quick. Check ¨Ventajas
Quickcheck n n Fue creado por Koen Claessen y John Hughes [2] (Chalmers University of Technology) en el 2000. Es una herramienta para el chequeo automático de programas Haskell. El programador proporciona una especificación del programa a través de propiedades que las funciones deberían satisfacer. Quick. Check entonces prueba que las propiedades se cumplan en un gran número de casos generados aleatoriamente.
n Definir una propiedad: <nombre_prop> <vbles> = <prop> where <supuesto> = <vble>: : <tipo> n Para ejecutar la prueba de la propiedad: ¨ quick. Check < nombre_prop > ¨ verbose. Check < nombre_prop >
Propiedades n n n Las propiedades son expresadas como definiciones de funciones en Haskell, con nombres que comienzan por prop_. Estan universalmente cuantificadas sobre sus parámetros. Deben tener tipos monomórficos Propiedades polimórficas deben ser restringidas a un tipo particular mediante: where supuestos = (x 1 : : t 1, x 2 : : t 2, . . . ) El tipo del resultado de una propiedad debe ser Bool, a menos que sea definida usando combinadores que veremos más adelante.
Propiedades Condicionales y Cuantificadas n Las propiedades pueden tomar la forma: <condición> ==> <propiedad> n n La propiedad condicional se cumplirá si para todos los casos que la condición se cumple, la propiedad también se cumple. O esta otra forma: for. All <generador> $ <patron> -> <propiedad> n Donde generador es un generador de datos de prueba que veremos más adelante.
Propiedades triviales n Sirven para mostrar estadísticas del número de casos triviales que han aparecido y tienen la forma: <condición> `trivial` <propiedad> n Los casos de prueba cuya condición es True son clasificados como triviales, y la proporción de casos triviales sobre el total es mostrada.
Clasificando Casos de Prueba n Se pueden clasificar de la forma: classify <condición> <string>$ <propiedad> n Los casos de prueba que satisfacen la condición son asignados a la clasificación dada, y después del test se informa de la distribución de la clasificación.
Recolectando valores de datos n Otra propiedad seria: collect <expresión>$ <propiedad> El primer argumento de collect es evaluado en cada caso de prueba, y la distribución de valores es reportada. n Todas las observaciones vistas hasta ahora pueden ser combinadas libremente. n
Generadores de datos de prueba: Tipo Gen n n Quick. Check define generadores por defecto para una amplia colección de datos: …. . El programado puede usar los suyos propios con for. All. Los generadores tienen como tipo: Gen a Se pueden construir mediante la función: choose: : Random a => (a, a) -> Gen a do i<-choose (0, length xs-1) return (xs!!i)
Ventajas e inconvenientes n Ventajas ¨ ¨ ¨ Quick. Check da valor a las especificaciones ofreciendo recompensas a corto plazo. Quick. Check fomenta formular especificaciones precisas y formales, en Haskell. Como otras especificaciones formales, las propiedades de Quick. Check tienen un significado claro y no ambiguo. Quick. Check chequea el programa intentando encontrar contraejemplos de sus especificaciones. Aunque esto no puede garantizar que el programa y la especificación son consistentes, reduce enormemente el riesgo de que no lo sean. Es fácil validar las especificaciones con cada cambio que hagamos a un módulo. Las especificaciones de Quick. Check documentan como validar tu programa de manera que cualquier programador que vea tu código sabrá que propiedades han sido validadas y cuales no. Quick. Check reduce el tiempo invertido en validar, generando muchos Cd. P automaticamente.
n Inconvenientes ¨ Es importante tener cuidado con la distribución de los casos de prueba: si los datos de prueba no están bien distribuidos entonces las conclusiones de los resultados de los test pueden no ser válidas. ¨ En particular el operador ==> puede torcer la distribución de los datos de prueba, ya que solo los datos de prueba que satisfagan la condición dada serán utilizados.
Conclusiones n n n Las pruebas unitarias son una herramienta muy útil en el desarrollo y diseño de SW ya que ayudan a garantizar que el programa hace justo lo se especifica en los Cd. P que lo definen. No obstante, hay algunos casos en los que no pueden ser usadas. Herramientas de pruebas unitarias están implementadas en casi cualquier lenguaje de programación.
n n n Los Cd. P son útiles en cualquier momento del desarrollo del SW aunque cambiemos la implementación siempre que se mantenga la interfaz. Si los Cd. P son correctos y completos podremos modificar sin ningún miedo nuestro código y saber si sigue funcionando tal y como debe funcionar. Existen otros tipos de prueba de programa además de las pruebas unitarias.
Bibliografía n [1] Clases de tipo en un lenguaje lógico-funcional, Diego Berrueta Muñoz. Tutor: Jose Emilio Labra Gayo (Universidad Politécnica de Oviedo 2004) ¨ n n Refactoring: Improving the Design of Existing Code. Martin Fowler, Kent Beck, John Brant, William Opdyke. The Addison-Wesley Object Technology Series. (1999) TDD y Nunit: ¨ n http: //sf. gds. tuwien. ac. at/00 -pdf/z/zinc-project/manual-tecnico-1. 0. 0. pdf www. willydev. net : web sin animo de lucro con recursos gratuitos sobre la plataforma. NET y otros contenidos Pruebas unitarias: http: //elvex. ugr. es : web de Fernando Berzal Galiano con software y cursos de libre disposición. ¨ http: //www. lawebdejm. com: información sobre pruebas unitarias, CPPUnit y DUnit ¨
n Programación extrema (XP): http: //www. xprogramming. com/software. htm: encontramos software para el desarrollo de pruebas unitarias en prácticamente todos lenguajes existentes. ¨ http: //www. asturlinux. org : Asociación de Usuarios Asturianos de Linux. ¨ ¨ n HUnit: http: //www. di. uniovi. es/~labra: Jose E. Labra. Profesor titular de la Universidad de Oviedo. Información sobre Pruebas Unitarias, Junit y Hunit ¨ http: //hunit. sourceforge. net/: página principal ¨ http: //sourceforge. net/projects/hunit: sitio web sobre el proyecto ¨ http: //www. informatik. uni-bremen. de/agbkb/lehre/ws 04 -05/fmsd/ ejercicios propuestos para resolver en hunit (universidad de bremen) ¨ n Quick. Check: ¨ n http: //www. cs. chalmers. se/~rjmh : John Hughes profesor de la Universidad de Tecnológica de Chalmers en Suecia Junit: ¨ ¨ http: //www-128. ibm. com/developerworks/java/library/j-junit 4. html http: //www. junit. org/
- Slides: 48