miércoles, 1 de marzo de 2006

JUnit

Seamos sinceros: nuestras aplicaciones las hacemos usando la metodología ODBC (Ojo De Buen Cubero) que consiste en ir escribiendo todo el código de un tirón calculando "al ojo" que funciona bien... luego ejecutamos toda la aplicación para ver si funciona bien... y si hay un problema, corregimos esa parte y volvemos a ejecutar toda la aplicación " otra vez " para ver si esa pequeña parte ha sido solucionada... y así sucesivamente hasta que ya no haya más problemas... por el momento.

JUnit es un framework que permite, a nosotros los programadores y desarrolladores, realizar pruebas a partes de una aplicación (una rutina, una clase, un algoritmo, etc) antes de la etapa de construcción.

Nuestro IDE favorito (JBuilder, netBeans, eclipse, JDeveloper, etc) permite crear módulos Test. Si aún no lo has utilizado, este sería un buen momento para conocer más tu IDE.

Manos a la obra

Imaginemos que estamos haciendo una aplicación que requiere manejar una lista en forma de pila: el primero en entrar es el último en salir. Para ello tenemos que crear una clase "Pila" que tendrá esta funcionalidad.

Nuestra clase tendrá los métodos poner() para agregar un nuevo objeto, sacar() para obtener el ultimo objeto agregado y tamanio() para conocer el tamaño de la pila en cualquier momento. El método poner() devolverá, además, el tamaño de pila después de agregar un elemento.

Antes de crear nuestra clase, primero pensemos cómo vamos a instanciarla y qué métodos va a tener.

        Pila pila1=new Pila();

pila1.poner("Marisol");
pila1.poner(new Integer(23));

Integer num=(Integer)pila1.sacar();

System.out.println("el tamanio de la pila es "+pila1.tamanio());

Con esto tendremos una idea más clara de cómo será nuestra clase.

Paso 1: creando un TestCase

Si usamos eclipse, al presionar Ctrl+N seleccionamos JUnit JUnit Test Case como se ve en la siguiente pantalla:

Si usamos netBeans, al presionar Ctrl + N seleccionamos JUnit Empty Test como se ven la siguiente pantalla:


También podemos crear un Test Case en jdeveloper. Para ello debemos bajar de la página de oracle la extensión necesaria. Una vez instalado podemos crear un Test Case como se ven la siguiente ventana

Si usamos JBuilder, entraremos al menú File New, y seleccionamos Test Test Case como se ve en la siguiente pantalla



Cualquiera fuera nuestro IDE, crearemos la clase con el nombre PilaTestCase ubicado en el paquete com.jugperu.tutores.junit.

Si nuestro IDE no tiene incorporado este módulo, podemos descargarlo de http://junit.sourceforge.net/ y ejecutarlo directamente.

Ahora, escribiremos el método "testApilar()" donde se harán las llamadas a nuestra clase que aún no hemos creado. Será como nuestro escenario donde se ejecutará nuestra clase. package com.jugperu.tutores.junit;

import junit.framework.*;

public class PilaTestCase extends TestCase {
public PilaTestCase(String name) {
super(name);
}


public void testApilar() {
Pila pila1 = new Pila();
pila1.poner("Marisol");
pila1.poner("Santiago");
pila1.poner("Diego");
pila1.poner(new Integer(23));

assertEquals(pila1.tamanio(),4);

}

}

La línea...

    assertEquals(pila1.tamanio(),4);

... es parte de nuestro test. Consiste en evaluar un resultado obtenido en un determinado momento y que debe coincidir con lo que esperamos. En este caso, despues de apilar 4 objetos, el tamaño de nuestra pila debe ser, naturalmente, 4. "Pero mi algoritmo de la pila va a estar perfecto cuando lo haga... ¿para qué tengo que hacer un test? " podremos decir.. pero ¿cuántas veces hemos codificado algoritmos "perfectos" que al final comenzaba a fallar por algo y "no sale lo que deberia salir" ?

Paso 2: Implementado nuestra clase

Los IDE actuales poseen una característica muy útil llamada "refactor" que permite reducir el tiempo en programar: puede crear sentencias que faltan, cambio de nombres, indentación, cambio de paquete de una clase, etc. Utilizaremos esta característica para crear parte del código de la clase Pila con las sentencias que hemos declarado. El resultado sería el sigueinte:

package com.jugperu.tutores.junit;

public class Pila {
public void poner(Object o) {
}

public int tamanio() {
return 0;
}
}

Ahora, completaremos el código que falta para que funcione nuestra clase.

package com.jugperu.tutores.junit;

import java.util.*;

public class Pila {
private List lista = new ArrayList();

public int poner(Object o) {
lista.add(0, o);
return tamanio();
}

public int tamanio() {
return lista.size();
}
}

Paso 3: probando nuestra clase

Ahora ejecutaremos el TestCase para ver si nuestro algoritmo funcionó. Al ejecutar JUnit siempre evaluará todos los métodos cuyos nombres empiecen con "test". Si uno de ellos falló, seguirá con el siguiente test.

La manera cómo va a ejecutar el TestCase varia dependiendo del IDE que estemos usando. Pero escencialmente da el mismo resultado.

Resultado en Eclipse

Resultado en JDeveloper o JUnit autónomo

Resultado en JBuilder

Resultado en netBeans

Al parecer todo va bien.

Paso 4: Agregando más funcionalidad a nuestra clase

Ahora, incorporaremos la funcionalidad de sacar los objetos de nuestra pila, pero al hacer eso nos aseguramos que los objetos estén en el orden correcto. Agregaremos el siguiente test a nuestro TestCase.

 public void testDesapilar() {
Pila pila1 = new Pila();

pila1.poner("Marisol");
pila1.poner("Santiago");
pila1.poner("Diego");
pila1.poner(new Integer(23));

assertEquals( ( (Integer) pila1.sacar()).intValue(), 23);
assertEquals(pila1.sacar(), "Diego");
assertEquals(pila1.sacar(), "Santiago");
assertEquals(pila1.sacar(), "Marisol");

assertEquals(pila1.tamanio(), 0);

}

Ahora, agregaremos el método que nos falta en nuestra clase Pila.

  public Object sacar() {
return lista.remove(0);
}

Ejecutamos el TestCase y evaluará los dos test.

Cada test lo ejecuta de forma independiente. Esto es, si la variable "pila1" la hubieramos declarado como propiedad de la clase TestCase y hubieramos asumido que al ejecutar el TestCase primero ejecuta "testApilar()" haciendo que el objeto "pila1" ya tenga valores para "testDesapilar()" hubieramos cometido un error. El JUnit carga todo el TestCase por cada test que tenga que hacer.

Si queremos iniciar valores para todos los test que queramos hacer, debemos sobreescribir el método "setUp()". Este método se ejecuta antes de cada test de nuestro TestCase. También existe el método "tearDown()" que se ejecuta al terminar cada test.

Modifiquemos nuestro TestCase para ver estos métodos.

package com.jugperu.tutores.junit;

import junit.framework.TestCase;

public class PilaTestCase extends TestCase {
private Pila pila1;
public PilaTestCase(String name) {
super(name);
}

public void testApilar() {
assertEquals(pila1.tamanio(), 4);
}

public void testDesapilar() {

assertEquals(((Integer) pila1.sacar()).intValue(), 23);
assertEquals(pila1.sacar(), "Diego");
assertEquals(pila1.sacar(), "Santiago");
assertEquals(pila1.sacar(), "Marisol");

assertEquals(pila1.tamanio(), 0);

}

protected void setUp() throws Exception {
System.out.println("cargando valores");
pila1 = new Pila();
pila1.poner("Marisol");
pila1.poner("Santiago");
pila1.poner("Diego");
pila1.poner(new Integer(23));

}

protected void tearDown() throws Exception {
System.out.println("termino este test");
}

}

Ahora bien, supongamos que por alguna razón se saca un objeto más de la pila cuando esta ya está vacia. No sabemos lo que va a pasar, así que mejor lo probamos acá antes de ponerlo en la aplicación final.

        ........
assertEquals(((Integer) pila1.sacar()).intValue(), 23);
assertEquals(pila1.sacar(), "Diego");
assertEquals(pila1.sacar(), "Santiago");
assertEquals(pila1.sacar(), "Marisol");

Object o = pila1.sacar();
......

Ejecutamos el Test y nos dará la excepción "IndexOutOfBoundsException". "Sí, ya lo sabía".. pero es preferible asegurarse.

¿Qué hacemos en este caso? Podriamos modificar nuestra clase Pila para que al tratar de sacar un objeto cuando ya no hay más por sacar, que devuelva null. Entonces, nos aseguraremos que va a devolver un null.

Modificamos nuestro método testDesapilar()

    public void testDesapilar() {

assertEquals(((Integer) pila1.sacar()).intValue(), 23);
assertEquals(pila1.sacar(), "Diego");
assertEquals(pila1.sacar(), "Santiago");
assertEquals(pila1.sacar(), "Marisol");

assertNull(pila1.sacar());

assertEquals(pila1.tamanio(), 0);

}

Y modificamos nuestro método sacar()
    public Object sacar() {
if (tamanio() == 0)
return null;
return lista.remove(0);
}

Ejecutamos nuestro TestCase y veremos los resultados.

Agrupando varios TestCase en un Test Suite

Tambien podemos crear un conjunto de Test Case y ejecutarlos a la vez, indicando qué test deseamos evaluar. Para ello crearemos una clase Test Suite el cual tendrá el siguiente código.

package com.jugperu.tutores.junit;

import junit.framework.*;

public class AppTestSuite {


public static Test suite() {
TestSuite suite = new TestSuite();
suite.addTestSuite(PilaTestCase.class);
return suite;
}
}

También podemos ejecutar solamente un test de un TestCase de la siguiente manera:

       suite.addTest(new PilaTestCase("testDesapilar"));

Ahora que ya sabemos que funciona bien, lo podemos utilizar en nuestra aplicación.

Conclusiones

Es importante realizar siempre tests a cada parte de nuestra aplicación. Hay que evitar probar solo una parte dentro de toda la aplicación en ejecución. Sería como esperar a que todo el automóvil esté construido para ver si está bien el alternador.

El todo es mayor que la suma de sus partes, y si nos aseguramos que sus partes están bien, el todo será lo que hemos esperado.

También hay variantes de JUnit que se utilizan para web, para swing, para Struts, etc.

Si necesitamos hacer un cambio, no olvidemos pasarlo por un test para asegurarnos que funcionará correctamente.