¿Qué es JUnit?
JUnit es un conjunto de bibliotecas creadas por Erich Gamma y Kent Beck que son utilizadas en programación para hacer pruebas unitarias de aplicaciones Java. JUnit es un conjunto de clases (framework) que permite realizar la ejecución de clases Java de manera controlada, para poder evaluar si el funcionamiento de cada uno de los métodos de la clase se comporta como se espera.
¿Cómo funciona JUnit?
Basicamente, en funci ́n de alg ́n valor de entrada se evalúa el valor de retorno esperado; si la clase cumple con la especificación, entonces JUnit devolverá que el método de la clase pasó exitosamente la prueba; en caso de que el valor esperado sea diferente al que retornó el método durante la ejecución, JUnit devolverá un fallo en el método correspondiente.
¿Qué es TDD?
Desarrollo guiado por pruebas, o Test-driven development(TDD) es una práctica de programación que involucra otras dos prácticas: Escribir las pruebas primero (Test First Development) y Refactorización (Refactoring).
¿Cómo funciona TDD?
En Primer Lugar se escribe una prueba y se verifica que las pruebas fallen, luego se implementa el código que haga que la prueba pase satisfactoriamente y seguidamente se hace un refactor del código escrito.
Ciclo de desarrollo
TDD se basa en un desarrollo iterativo e incremental, para el cual se recomienda seguir los siguientes pasos:
- Elegir un requisito: Se elige de una lista el requerimiento que se cree que nos dará mayor
conocimiento del problema y que a la vez sea fácilmente implementable. - Escribir una prueba: Se comienza escribiendo una prueba para el requisito. Para ello el programador debe entender claramente las especificaciones y los requisitos de la funcionalidad que está por implementar. Este paso fuerza al programador a tomar la perspectiva de un cliente considerando el código a través de sus interfaces.
- Verificar que la prueba falla: Si la prueba no falla es porque el requerimiento ya estaba implementado o porque la prueba es errónea.
- Escribir la implementación: Escribir el código más sencillo que haga que la prueba funcione.
- Ejecutar las pruebas automatizadas: Verificar si todo el conjunto de pruebas funciona correctamente.
- Eliminación de duplicación: El paso final es la refactorización, que se utilizará principalmente para
eliminar código duplicado. Se hacen de a una vez un pequeño cambio y luego se corren las pruebas hasta que funcionen. - Actualización de la lista de requisitos: Se actualiza la lista de requisitos tachando el requisito implementado. Asimismo se agregan requisitos que se hayan visto como necesarios durante este ciclo.
Ventajas
- Los programadores que usan TDD para un proyecto desde cero son raras las veces que necesitan usar un debbuger.
- Ayuda a producir aplicaciones de más calidad y en menos tiempo.
- Además de validar el cumplimiento de los requisitos, también puede guiar el diseño de un programa.
- Cuando es bien utilizado, nos asegura que todo el código está cubierto por una prueba.
Limitaciones
El desarrollo guiado por pruebas requiere que las pruebas puedan automatizarse. Esto resulta complejo en los siguientes dominios:
- Interfaces Gráfica de usuario (GUIs).
- Objetos distribuidos, aunque los objetos simulados(MockObjects) pueden ayudar.
- Bases de datos. Hacer pruebas de código que trabaja con base de datos es complejo porque requiere poner en la base de datos unos datos conocidos antes de hacer las pruebas. Todo esto hace que la prueba sea costosa de codificar.
JUnit y NetBeans
NetBeans integra la librería JUnit de forma que es muy sencillo crear un test para una clase determinada. Esta librería aparece con el nombre JUnit como uno de los plugins disponibles para descargar.
A continuación vamos a ver en base a un ejemplo como utilizar JUnit con las facilidades que nos proporciona NetBeans.
Ejemplo
Problema: determinar si un año es bisiesto.
¿Cuando un año es bisiesto?
- Un año es bisiesto si es divisible por cuatro lo que provoca que uno de cada cuatro años sea bisiesto.
- Para un mayor ajuste los años divisibles por 100 no serán bisiestos, de tal forma que cada 100 años habrá un año que debería ser bisiesto y no lo es.
- Sin embargo si el año es divisible por 400 sí que es bisiesto, así cada 400 años habrá un año que no debería ser bisiesto pero sí que lo es.
Si bien TDD dice que lo primero es crear clases de test, se puede aprovechar las ventajas de NetBeans. Para ello se crea la clase con el método isLeap que retorna false para que siempre falle, y en base a esta clase se genera la clase de test.
Para ello, como muestra la imágen, al hacer click con el botón derecho podremos crear la clase para test:
La opción anterior nos mostrará la siguiente ventana:
Y el código generado es el siguiente:
Código Java :
import org.junit.After; import org.junit.AfterClass; import org.junit.Before; import org.junit.BeforeClass; import org.junit.Test; import static org.junit.Assert.*; public class YearUtilitiesTest { public YearUtilitiesTest() { } @BeforeClass public static void setUpClass() throws Exception { } @AfterClass public static void tearDownClass() throws Exception { } @Before public void setUp() { } @After public void tearDown() { } public void testIsLeap() { System.out.println("isLeap"); int year = 0; YearUtilities instance = new YearUtilities(); boolean expResult = false; boolean result = instance.isLeap(year); assertEquals(expResult, result); fail("The test case is a prototype."); } }
Ahora bien, ¿Qué son todos esos métodos generados?
JUnit da la facilidad de poder ejecutar cosas antes y después de que se ejecute cada test, o inclusive antes y después de todos los test.
- setUpClass(): Ejecuta lo que el desarrollador desee antes de ejecutar la clase de test
- tearDownClass(): Ejecuta lo que el desarrollador desee después de ejecutar la clase de test
- setUp(): Se ejecuta antes de ejecutar cada uno de los test
- tearDown(): Se ejecuta después de ejecutar cada uno de los test
Estos métodos son útiles para cuando necesitamos inicializar cosas, como bases de datos, borrar archivos resultantes, etc. Si no se necesitase alguno de estos métodos, simplemente se los puede quitar.
Ahora modificamos el código tratando de poner años que cubran todas las opciones. Por otro lado, borramos los métodos setUpClass(), tearDownClass(), setUp() y tearDown() ya que en este caso no necesitamos hacer nada ni antes ni después de la ejecución de los test. Entonces, así nos quedaría el código:
Código Java :
import org.junit.After; import org.junit.AfterClass; import org.junit.Before; import org.junit.BeforeClass; import org.junit.Test; import static org.junit.Assert.*; public class YearUtilitiesTest { public void testIsLeap() { YearUtilities instance = new YearUtilities(); assertTrue(instance.isLeap(4));//es bisiesto assertFalse(instance.isLeap(100));//no es bisiesto assertTrue(instance.isLeap(400));//es bisiesto assertFalse(instance.isLeap(2011));//no es bisiesto assertTrue(instance.isLeap(2012));//es bisiesto } }
E implementamos el método isLeap:
Código Java :
public class YearUtilities { public boolean isLeap(int year){ if(year%4 == 0){ if(year%100 == 0){ if(year%400 == 0){ return true; }else{ return false; } }else{ return true; } }else{ return false; } } }
Ejecutamos el test desde el menú Run->Test Project
Como muestra la figura, nos marca en verde y nos dice que el 100 % de los test fueron superados.
Ahora bien, modifiquemos el método isLeap para que una de las pruebas arroje un error:
Código Java :
public class YearUtilities { public boolean isLeap(int year){ if(year%4 == 0){ if(year%100 == 0){ if(year%400 == 0){ return false; //cambiamos true por false }else{ return false; } }else{ return true; } }else{ return false; } } }
Ejecutamos nuevamente el test:
Como se puede observar, nos dice que uno de los tests falló y desplegando el error mostrará la linea del fallo.
Aserciones de JUnit
Como se pudo observar en el ejemplo, se utilizaron los métodos assertTrue y assertFalse para evaluar si el valor retornado por el método es el esperado.
A continuación se presenta una tabla describiendo las aserciones de JUnit:
- assertEquals: Evalúa si dos objetos pasados por parámetro son iguales.
- assertFalse: Evalúa si la condición pasada es false.
- assertNotNull: Comprueba que la condición no sea nula.
- assertNotSame: Comprueba que dos objetos no sean la misma instancia.
- assertNull: Comprueba si un objeto es nulo.
- assertSame: Opuesto a assertNotSame.
- assertTrue: Opuesto a assertFalse.
- fail: Útil para detectar si estamos en un sitio del programa donde no deberíamos estar.
Un ejemplo de utilización del método fail es el siguiente:
Código Java :
protected void runTest(){ try{ File fichero = new File(nombreFichero); assertTrue("Podemos Leer",fichero.canRead()); }catch(Exception e){ fail("Fallo al identificar Fichero " + nombreFichero); } }
En caso de no poder leer el archivo, lanzará una excepción y el método fail escribirá el error.
Bibliografía:
- http://es.wikipedia.org/wiki/JUnit
- Pruebas de programas Java mediante JUnit, Macario Polo Usaola, Universidad De Castilla.
- JUnit, Eduardo Mosqueira Rey, Universidade da Coruña, España.
- Wikipedia
- http://www.lab.dit.upm.es/~lprg/material/apuntes/pruebas/aceptacion.htm
- http://www.yaxche-soft.com/es/blog/pruebas_ unitarias_junit_45