Hamcrest




Un post técnico que me ha resultado genial probar y me gustaría compartir.

Hamcrest

Cuando trabajas haciendo test, se te pide tener en cuenta lo siguiente:
  1.  Los tests deben ser clarisimos para el lector y transmitir lo que estamos tratando de testear de la forma mas simple posible
  2. Nuestros tests que fallan deben ser claros acerca del problema presente, no tenemos porque gastar mucho tiempo averiguando cual es la causa del problema
  3. Nosotros debemos en lo posible seguir la regla TDD "Un Assert por Test" - o lo mas cercano a ese punto.
  4. Implementar el metodo equals() solamente para que podamos hacer comparaciones en esting es demasiado engorroso para la practica.
El último punto porque puede ser engorroso porque basicamente puedes tener un id y considerar que dado ese id los dos objetos a comparar son iguales.
Ademas hay que ser sinceros, a veces usamos librerías de terceros y  la realidad que encontramos es que no implementan equals(), hashCode() o toString(). Entonces, no podemos usar assertEquals directamente y si lo hicieramos la salida obtenida sería completamente inutil.


Bueno, vamos a probar el ejemplo del tutorial (http://code.google.com/p/hamcrest):

http://pastie.org/2725256

(código fuente)

Nota: Si van a probarlo primero añadan la librería de hamcrest luego la de junit. Me salio un error cuando hice un proyecto java simple y añadir primero junit y luego hamcrest. En fin, segui este tip y lo solucione con http://danmalec.blogspot.com/2010/08/solving-javalangsecurityexception-when.html

 Como bien dice la documentación  este assertThat es un método estilizado para hacer un test assertion. El sujeto de el assertion es el objeto biscuit que el primer argumento del metodo. El segundo argumento del metodo es un matcher para objetos Biscuit, aqui el matcher chequea que un objeto es igual a otro usando el metodo equals. El test pasa puesto que la clase Biscuit define un metodo equals.

Pero que pasa si no se implementa equals

Fuente: http://blogs.atlassian.com/developer/2009/06/how_hamcrest_can_save_your_sou.html

¿Que pasa si queremos comparar dos objetos que no implementan ni equals, ni hashCode?


assertEquals(thisLightsaber.isSingleBladed(), thatLightsaber.isSingleBladed());
assertEquals(thisLightsaber.getColor(), thatLightsaber.getColor());
assertEquals(thisLightsaber.getHilt(), thatLightsaber.getHilt());
 
Usando el simple assertEquals de JUnit sería de la forma mostrada. 
El tema es que si se cae en una linea, nos resultaría dificil saber donde.


Si aplicamos hamcrest podemos ubicar el error de una manera mas especifica.

http://pastie.org/964394  (código fuente)

Si ejecutamos esto nos saldría un error como este:

java.lang.AssertionError:
Expected: is {singleBladed is , color is , hilt is }
     but: {color was }
    at org.hamcrest.MatcherAssert.assertThat(MatcherAssert.java:20)
    at org.hamcrest.MatcherAssert.assertThat(MatcherAssert.java:8)
    at pe.joedayz.samples.LightsaberTest.assertThatAnakinsLightsaberIsLukesFirstLightsaber(LightsaberTest.java:21)
 
El ejemplo también nos muestra como crear nuestro Matcher personalizado.
Otra cosa interesante que nos muestra tambien hamcrest desde su versión 1.2 es que tenemos un:


static void reportMismatch(String name, Matcher matcher, Object item, Description mismatchDescription, boolean firstMismatch)
{
    if (!firstMismatch)
    {
        mismatchDescription.appendText(", ");
    }
    mismatchDescription.appendText(name).appendText(" ");
    matcher.describeMismatch(item, mismatchDescription);
}








 Donde podemos personalizar mas la salida y ver en donde tenemos la falla.


Trabajandolo con otros Test frameworks

Lo  bueno de Hamcrest es que ha sido diseñado para integrarse con Junit, TestNG.
También se puede usar con mock objects frameworks usando adaptadores. Hay para JMock y EasyMock.

Sugar


assertThat(theBiscuit, equalTo(myBiscuit));
assertThat(theBiscuit, is(equalTo(myBiscuit)));
assertThat(theBiscuit, is(myBiscuit));

Todos estos son equivalentes.


Creando Matchers personalizados

Código fuente: http://pastie.org/2725591

¿Porque crear un matcher personalizado?  pues para eliminar duplicación de código y hacer nuestros tests mas leíbles.

Al copiar el código y ejecutar el test verás que assertThat recibe un argumento de tipo Matcher. Y en este caso se necesita un Matcher. En la documentación explican que se utiliza TypeSafeMatcher porque este ya castea a Double por nosotros. La unica responsabilidad de nosotros es implementar el método matchesSafely que chequea si el Double es NaN y el metodo describe() que se usa para productir un mensaje de falla cuando el test falla. 


fails with the message
java.lang.AssertionError: Expected: is not a number
    got : <1.0> 



Conclusiones

Nos podemos expresar mejor con Hamcrest. En lugar de usar:

    @Test
    public void deberiaObtenerIgv(){
        double total = 119;
        CalculadoraFinanciera calculadoraFinanciera = new CalculadoraFinanciera();
        double impuesto = calculadoraFinanciera.obtenerIgv(total);
        assertEquals(impuesto, 19, 0);
    }

Podemos usar:

     @Test
    public void deberiaObtenerIgvConHamcrest(){
        double total = 119.0;
        CalculadoraFinanciera calculadoraFinanciera = new CalculadoraFinanciera();
        double impuesto = calculadoraFinanciera.obtenerIgv(total);
        assertThat(impuesto, is(19.0));
    }

En caso falle nos muestra un error mas entendible:


java.lang.AssertionError:
Expected: is <19.0>
     but: was <44.705882352941174>


Si trabajas con colecciones:

    @Test
    public void deberiaTrabajarConArreglos(){
        String[] colors = new String[]{"red", "green", "blue"};
        String color = "yellow";
        assertThat(color, not(isIn(colors)));
    }


Continuará.







Comentarios

Entradas populares