jueves, 30 de julio de 2009

Parametrizar un test JUnit

Supongamos que queremos ejecutar un test varias veces con distintos valores. Una opción podría ser algo como lo siguiente:

package es.gmr.test;

import java.util.Arrays;
import java.util.List;

import org.junit.Before;
import org.junit.Test;

import junit.framework.Assert;

public class TestParametrizar {

private List<Integer> datos;

@Before
public void setUp() throws Exception {
this.datos = Arrays.asList(2, 4, 5, 6);
}

@Test
public void test() {
for (final Integer num : this.datos) {
testEsPar(num);
}
}

public void testEsPar(final Integer x){
Assert.assertEquals(x + " no es un número par", 0, (x % 2));
}
}

Lo malo de esta opción, es que si la ejecutamos (desde Eclipse por ejemplo) veremos lo siguiente:

Es decir, si uno de los valores del listado no supera el test, no se continuará con los siguientes. Para evitar esto, JUnit 4 nos permite generar parámetros para lanzar varias veces un test con dichos parámetros. Para ello, primero deberemos anotar nuestra clase con @RunWith(Parameterized.class). También tendremos que tener un método anotado con @Parameters, que será el encargado de generar la lista de parámetros. Por último, la clase debe tener un constructor con un argumento del tipo de los parámetros, ya que cada vez que se lance un test, se llamará a este constructor para inicializar el parámetro. Con todo esto, obtenemos el siguiente test:

package es.gmr.test;

import java.util.ArrayList;
import java.util.Collection;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
import org.junit.runners.Parameterized.Parameters;

import junit.framework.Assert;

@RunWith(Parameterized.class)
public class TestParametrizar {

private final Integer num;

public TestParametrizar(final Integer x) {
this.num = x;
}

@Parameters
public static Collection<Integer[]> init() throws Exception {
final Collection<Integer[]> datos = new ArrayList<Integer[]>();
datos.add(new Integer[] {2});
datos.add(new Integer[] {4});
datos.add(new Integer[] {5});
datos.add(new Integer[] {6});
return datos;
}

@Test
public void test() {
Assert.assertEquals(this.num + " no es un número par", 0, (this.num % 2));
}
}

Ahora, se lanzarán tantos tests como parámetros hayamos definido, y el resultado sería algo así:

Vemos que cada parámetro hace que se ejecute un test, y aunque con uno de ellos el test falle, se sigue tratando lo siguientes.

3 comentarios:

  1. Interesante novedad de JUnit. La pega que le veo es que se presta un poco a que perdamos de vista qué es lo que queremos probar, y por qué.

    En el test que pones de ejemplo, lo que quieres probar es la funcion que dice si un numero es par o no. Por simplicidad has puesto la propia funcion dentro del assert pero lo normal es que fuera una funcion en si misma. Se entiende que es por acortar. Si lo que queremos probar es que la funcion distingue entre numero par y numero impar, tendriamos que escribir dos tests, uno para cada caso. Y cada uno necesita sólo un numero como entrada, no un vector. Por otra parte, si necesitas probar que una determinada funcion es capaz de recibir un array y colaborar con la funcion que discrimina entre par e impar, tendrias que identificar las responsabilidades de cada una y escribir un test basado en la interaccion donde valides que la colaboracion se está haciendo.

    A veces la potencia de las herramientas nos puede hacer perder de vista cual es el objetivo de nuestras acciones :-)

    ResponderEliminar
  2. Gracias Carlos, lo que comentas es muy interesante. Es verdad que a veces tendemos a adaptar el problema a las herramientas, y no al revés... y eso es peligroso. En el caso del ejemplo, es como dices, intenté mostrar lo que hace la herramienta usando el menor número de líneas posibles. En definitiva es un ejemplo que muestra una utilidad más que podemos tener en nuestro repertorio y usarla sólo si realmente la necesitamos para falicitar las cosas, pero como dices, sin perder de vista qué queremos hacer.
    Aclarar, que igual no te entendí bien, que al final el test se ejecuta varias veces con un elemento, no con el vector... el vector realmente se trata de forma interna para realizar tantas llamadas al test como elementos tenga.
    De todas formas, espero que a partir de septiembre, pueda poner ejemplos más claros y apropiados ;)

    Un saludo.

    ResponderEliminar
  3. Sí, veo tu ejemplo, entiendo que el test se ejecuta con un sólo número y también que intentaste usar el menor numero de lineas de codigo. Solo quise comentarte que como dicen en ingles "the evil is in the details".

    Debo autocorregirme por cierto ya que hablé de validacion de interaccion pero para ese caso lo mas conveniente es validacion de estado en ambos casos. Ya lo veremos en el curso :-)

    ResponderEliminar