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
Haskell



Otro tipo de pruebas en
haskell


Pruebas Unitarias en
otros lenguajes


Junit
Otros



Hunit
Ventajas e Inconvenientes
QuickCheck
Ventajas
Conclusiones
Bibliografía
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 {
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");
TestSuite ts= new TestSuite(DivisaTest.class);
SetUp() : se ejecuta antes de cada test
//ts.add(otroTest);
d8USD = new Divisa(8, "USD");
TearDown(): se ejecutan despues de cada test
d17EUR= new Divisa(17, "EUR");} junit.textui.TestRunner.run(ts);









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()));
}





}


}
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:


Luego se llama a la funcion que ejecuta las
pruebas:


assertEqual "nombre_prueba" <resultado> <funcion_a_probar>
runTestTT <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:
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)
3.
Nombrar los test y agruparlos:
tests = TestList [TestLabel "test1" test1, TestLabel "test2" test2]
4.
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:
<nombre_prop> <vbles> = <prop>
where <supuesto> = <vble>::<tipo>

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:
<condición> ==> <propiedad>


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 <generador> $ \<patron> -> <propiedad>

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:
<condición> `trivial` <propiedad>

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:
classify <condición> <string>$ <propiedad>

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 <expresión>$ <propiedad>
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)



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:


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

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/
Descargar

Pruebas unitarias en Haskell