Pruebas unitarias en Haskell José Daniel Pérez Vigo Juan Diego Ruiz Perea Indice



Descargar 25,43 Kb.
Fecha de conversión25.04.2017
Tamaño25,43 Kb.

Pruebas unitarias en Haskell

  • José Daniel Pérez Vigo
  • Juan Diego Ruiz Perea

Indice

  • Introducción
    • eXtreme Programming (XP)
    • Test Driven Development (TDD)
    • Pruebas Unitarias
  • Pruebas Unitarias en otros lenguajes
    • Junit
    • Otros

Introducción

  • eXtreme Programming (XP)
  • Test Driven Development (TDD)
  • Pruebas Unitarias

eXtreme Programming

  • La programación extrema o eXtreme 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

  • 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.

  • Corrección de todos los errores antes de añadir nueva funcionalidad. Hacer entregas frecuentes.
  • Refactorización del código, es decir, reescribir ciertas partes del código para aumentar su legibilidad y mantenibilidad pero sin modificar su comportamiento.
  • Propiedad del código compartida.
  • Simplicidad en el código: es la mejor manera de que las cosas funcionen.

Test Driven Development (TDD)

  • O desarrollo guiado por pruebas es una metodología, utilizada de XP, consistente en escribir las pruebas antes que el código.
  • 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.
  • 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.
  • Estas propiedades permiten una rápida retroalimentación en el diseño y la corrección.

Pruebas unitarias

  • 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.
  • 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

  • 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

  • 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.
  • Gracias a JUnit, los códigos Java tienden a ser mucho mas robustos, fiables, y libres de errores.
  • JUnit (a su vez inspirado en Smalltalk SUnit) ha inspirado una familia entera de herramientas xUnit, trasladando los beneficios de las pruebas de unidad a una amplia gama de lenguajes.

Diagrama UML de JUnit

Ejemplo Junit: Clase Divisa

  • public class Divisa {
  • private int importe;
  • private String denominación;
  • public Divisa(int imp, String den) {
  • importe = imp;
  • denominación = den;
  • }
  • public int importe(){ return importe; }
  • public String denominación(){
  • return denominació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");
  • }
  • }
  • }

public class DivisaTest extends TestCase {

  • public class DivisaTest extends TestCase {
  • private Divisa d5EUR, d12EUR , d17EUR, d8USD, expected;
  • public static void main(String[] args) {
  • junit.textui.TestRunner.run(DivisaTest.class);
  • }
  • public void setUp(){
  • d5EUR= new Divisa(5, "EUR");
  • d12EUR= new Divisa(12, "EUR");
  • d8USD = new Divisa(8, "USD");
  • d17EUR= new Divisa(17, "EUR");}
  • public void testSumar() {
  • try{
  • expected= (Divisa) d5EUR.sumar(d12EUR);
  • /*assertEqual(expected,d17EUR)*/
  • assertTrue(expected.equals(d17EUR));
  • expected= (Divisa) d5EUR.sumar(d5EUR);
  • assertFalse(expected.equals(d17EUR));
  • expected= (Divisa) d5EUR.sumar(d8USD);
  • fail(“No se ha lanzado ninguna excepción”);
  • }catch (DivisaException e) {
  • assertTrue(e.equals(new DivisaException()));
  • }
  • }
  • }
  • TestSuite ts= new TestSuite(DivisaTest.class);
  • //ts.add(otroTest);
  • junit.textui.TestRunner.run(ts);
  • SetUp() : se ejecuta antes de cada test
  • TearDown(): se ejecutan despues de cada test

Herramientas para otros lenguajes

  • NUnit (.NET: C#,J#,VB y C++)
  • CPPUnit (C++)
  • DUnit (Delphi)
  • pyUnitPerf(python)
  • accessUnit(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

  • 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.

De la misma manera que en Junit, el programador especifica una serie de pruebas del tipo:

  • De la misma manera que en Junit, el programador especifica una serie de pruebas del tipo:
    • assertEqual "nombre_prueba"
  • Luego se llama a la funcion que ejecuta las pruebas:
    • runTestTT
  • Ésto muestra por pantalla los resultados de las pruebas.
  • En el módulo donde se creen los test debemos importar el módulo de Hunit.
    • Import Hunit
  • Definir los casos de prueba:
    • test1 = TestCase (assertEqual “para (foo 3)," (1,2) (foo 3) )
    • test2 = TestCase (do (x,y) <- partA 3
    • assertEqual “para el primer resultado," 5 x
    • b <- partB y
    • assertBool ("(partB " ++ show y ++ ") failed") b)
  • Nombrar los test y agruparlos:
  • tests = TestList [TestLabel "test1" test1, TestLabel "test2" test2]
  • Ejecutar un grupo de casos de prueba.
  • > runTestTT tests
  • Cases: 2 Tried: 2 Errors: 0 Failures: 0
  • > runTestTT tests
  • ### Failure in: 0:test1
  • 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 (TestCase), y los casos de prueba se combinan en tests.
  • Hunit también provee de características avanzadas para especificaciones de test más adecuadas.

Asertos

  • Es el bloque básico para construir test.
    • data Assertion = IO ()
    • assertFailure :: String -> Assertion
    • assertFailure msg = ioError (userError ("HUnit:" ++ msg))
    • assertBool :: String -> Bool -> Assertion
    • assertBool msg b = unless b (assertFailure msg)
    • assertString :: String -> Assertion
    • assertString s = unless (null s) (assertFailure s)
  • assertEqual :: (Eq a, Show a) => String -> a -> a -> Assertion
  • assertEqual preface expected actual =
  • unless (actual == expected) (assertFailure msg)
  • where msg = (if null preface then "" else preface ++ "\n") ++ "expected: " ++ show expected ++ "\n but got: " ++ show actual
  • 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(CdP)

  • Es la unidad de una ejecución de prueba, es decir, CdP distintos, son ejecutados independientemente. El fallo de uno es independiente del fallo de cualquier otro.
  • Un CdP consiste en un aserto simple o posiblemente colectivo. Un CdP puede involucrar una serie de pasos, cada uno terminado en un aserto, donde cada paso debe tener éxito para poder continuar con el CdP.
  • Para crear un CdP desde un aserto se aplica el constructor TestCase. Ejemplos:
    • TestCase (return ())
    • TestCase (assertEqual “para x,” 3 x)
  • Se han implementado también operadores para crear CdP de manera mas sencilla, estos son: @?, @=?, @?= ~?, ~=?, ~?= , ~:

Tests

  • Cuando se tiene más de un CdP se hace necesario nombrarlos y agruparlos en listas, para ello usamos:
  • data Test = TestCase Assertion
          • | TestList [Test]
          • | TestLabel String Test
  • Para conocer el número de CdP que componen un test podemos usar la función:
  • testCaseCount :: Test -> Int

Ejecución de Pruebas

  • 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 CdP

Ejecución de Pruebas (cont)

  • 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)
  • cases: número de CdP incluidos en el test. 
  • tried: número de CdP que han sido ejecutados.
  • errors: número de CdP cuya ejecución termina con una excepción no esperada.
  • failures: número de CdP cuya ejecución termina en fallo.

Ejecución de Pruebas (cont)

  • 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 CdP actual).
  • error: Cuando un CdP finaliza con un error, el mensaje de error es reportado, junto con el camino del CdP y os contadores actuales (incluyendo el CdP actual).
  • failure: Cuando un CdP finaliza con un fallo, el mensaje de fallo es reportado, junto con el camino del CdP y os contadores actuales (incluyendo el CdP actual).

Ventajas e Inconvenientes

  • 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.
      • 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)

  • 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

    • QuickCheck
    • Ventajas

Quickcheck

  • 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.
  • QuickCheck entonces prueba que las propiedades se cumplan en un gran número de casos generados aleatoriamente.
  • Definir una propiedad:
    • =
    • where = ::
  • Para ejecutar la prueba de la propiedad:
    • quickCheck < nombre_prop >
    • verboseCheck < nombre_prop >

Propiedades

  • 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 = (x1 :: t1, x2 :: t2, ...)
  • 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

  • Las propiedades pueden tomar la forma:
  • ==>
  • 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:
  • forAll $ \
    ->
  • Donde generador es un generador de datos de prueba que veremos más adelante.

Propiedades triviales

  • Sirven para mostrar estadísticas del número de casos triviales que han aparecido y tienen la forma:
  • `trivial`
  • 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

  • Se pueden clasificar de la forma:
  • 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

  • Otra propiedad seria:
    • collect $
  • El primer argumento de collect es evaluado en cada caso de prueba, y la distribución de valores es reportada.
  • Todas las observaciones vistas hasta ahora pueden ser combinadas libremente.

Generadores de datos de prueba: Tipo Gen

  • QuickCheck define generadores por defecto para una amplia colección de datos:
  • …..
  • El programado puede usar los suyos propios con forAll.
  • 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

  • Ventajas
    • QuickCheck da valor a las especificaciones ofreciendo recompensas a corto plazo.
    • QuickCheck fomenta formular especificaciones precisas y formales, en Haskell. Como otras especificaciones formales, las propiedades de QuickCheck tienen un significado claro y no ambiguo.
    • QuickCheck 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 QuickCheck documentan como validar tu programa de manera que cualquier programador que vea tu código sabrá que propiedades han sido validadas y cuales no.
    • QuickCheck reduce el tiempo invertido en validar, generando muchos CdP automaticamente.
  • 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

  • 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 CdP 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.
  • Los CdP son útiles en cualquier momento del desarrollo del SW aunque cambiemos la implementación siempre que se mantenga la interfaz.
  • Si los CdP 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

  • [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)
    • http://sf.gds.tuwien.ac.at/00-pdf/z/zinc-project/manual-tecnico-1.0.0.pdf
  • 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:
    • 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

Programación extrema (XP):

  • Programación extrema (XP):
    • http://www.xprogramming.com
    • http://www.xprogramming.com/software.htm: encontramos software para el desarrollo de pruebas unitarias en prácticamente todos los lenguajes existentes.
    • http://www.asturlinux.org : Asociación de Usuarios Asturianos de Linux.
  • 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/ws04-05/fmsd/ ejercicios propuestos para resolver en hunit (universidad de bremen)
  • QuickCheck:
    • 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-junit4.html
    • http://www.junit.org/


La base de datos está protegida por derechos de autor ©absta.info 2016
enviar mensaje

    Página principal