hamcrest logo

JUnit con Hamcrest

Visión general

Hamcrest es un framework utilizado para las pruebas de tests unitarios. Permite escribir el conjunto de pruebas de forma declarativa basándose en objetos de coincidencia y reglas de coincidencia, los matchers. Viene distribuido con JUnit por lo que no es necesario una instalación o configuración extra.

En este tutorial trataremos de forma sencilla la funcionalidad de Hamcrest y aprenderemos cómo aprovecharla para escribir pruebas unitarias más nítidas e intuitivas en nuestro desarrollo de software.

¿Por qué utilizarlo?

La librería de Hamcrest dispone de una serie de matchers que podemos utilizar para escribir nuestros test con un lenguaje más cercano al natural. De esta manera se hace más sencillo comprender lo que están comprobando nuestros test.

La principal diferencia es que, en vez de usar los diferentes asserts de JUnit; como pueden ser assertNotNull, assertTrue, assertEquals, etc, usamos un único assert, assertThat, que es de JUnit. ¿Qué se consigue con esto?, se consigue una mejora de legibilidad al leer los test. Con JUnit sería algo como assert not null objeto (asegura no nulo objeto) y con Hamcrest assert that objeto is not null (asegura que objeto no es nulo).

Imposible nada es. Difícil, muchas cosas son.

Maestro Yoda

Por otra parte, cuando se escriben las pruebas, a veces es difícil encontrar el equilibrio adecuado entre la especificación excesiva de la prueba (hacerla frágil a los cambios), y no especificar lo suficiente (hacerla menos exigente y que continúe pasando, incluso cuando se rompe lo que se está probando).

Hamcrest permite seleccionar la precisión y flexibilidad del código bajo prueba. Esto ayuda mucho en la escritura de pruebas que pueden darse por «correctas». Dichas pruebas fallan cuando el comportamiento del aspecto bajo prueba se desvía del comportamiento esperado, pero continúan pasando cuando se realizan cambios menores y no relacionados en el comportamiento.

Reescritura del test con Hamcrest

En el artículo anterior de Testing con JUnit presentamos una estructura de ejemplo de un test realizado con las aserciones de JUnit. En este caso mostramos, a modo de comparación, dicho test y su homólogo utilizando el framework de Hamcrest. Como se puede observar, hemos pasado de definir los test al estilo del maestro Yoda a hacerlo de una forma más natural.

Test con asserts de JUnit

public class CalculadoraTest {
    @Test
    public void multiplicaPorCeroDebeRetornarCero() {
        Calculadora calcTester = new Calculadora(); 
        assertEquals(0, calcTester.multiplica(10, 0), "10 x 0 debe ser 0");
    }
}

Test con assertThat de JUnit y matchers de Hamcrest

public class CalculadoraTest {
     @Test
     public void multiplicaPorCeroDebeRetornarCero() {
         Calculadora calcTester = new Calculadora(); 
         assertThat("10 x 0 debe ser 0",  calcTester.multiplica(10, 0), equalsTo(0));
     }
 }

Matchers de Hamcrest

En este apartado se han agrupado los matchers más comunes de Hamcrest. Para más simplicidad se han utilizado objetos de tipo String, pudiendo ser de cualquier otro tipo. El conjunto de métodos a evaluar procede del paquete Core de Hamcrest, ya incluido por defecto en JUnit. Se recomienda revisar el Javadoc del proyecto para ver el resto de paquetes disponibles.

Base

is

Hamcrest se esfuerza por hacer que sus pruebas sean lo más legibles posible. Este matcher no agrega ningún comportamiento adicional al matcher. Las siguientes afirmaciones son equivalentes:

@Test
public void isExample() {
    assertThat ("foo", is("foo"));
    assertThat ("foo", equalTo("foo"));
    assertThat ("foo", is (equalTo("foo")));    
}

not

Da la prueba por válida si los objetos no coinciden.

@Test
public void notExample() {
    assertThat("foo", is(not("bar")));
}

Objetos

is – equalTo

Compara ambos objetos mediante el método Object.equals(). En el caso especial de pasar un Array este matcher dará positivo si ambos arrays tienen la misma longitud y objetos lógicamente coincidentes en las mismas posiciones. is(Tipo x) es una abreviatura para is(equalTo(Tipo x)).

@Test
public void equalToExample() {
    assertThat("foo", equalTo("foo"));
    // Distinto objeto pero coincide en longitud e items
    assertThat(new String[] {"foo", "bar"}, is(equalTo(new String[] {"foo", "bar"})));
    //El orden no es el mismo!
    assertThat(new String[] {"bar", "foo"}, is(not(equalTo(new String[] {"foo", "bar"}))));
}

sameInstance

Comprueba si ambos objetos comparados son la misma instancia.

@Test
public void sameInstanceExample() {
    String foo = "foo";
    String sameFoo = foo;    
    assertThat(foo, is(sameInstance(sameFoo)));
}

isA – instanceOf

Compara el tipo de objeto mediante el método Class.isInstance(Object). El matcher creado no asume ninguna relación entre el tipo especificado y el objeto examinado. isA(Clazz.class) es una abreviatura para is(instanceOf(Clazz.class)).

@Test
public void instanceOfExample() {
    assertThat("foo", is(not(instanceOf(Integer.class))));
    assertThat("foo", isA(String.class));
}

any

Compara el tipo de objeto mediante el método Class.isInstance(Object). El matcher creado fuerza una relación entre el tipo especificado y el objeto examinado.

@Test
public void anyExample() {
    // No coincide el tipo! 
    //assertThat("foo", is(not(any(Integer.class))));
    assertThat("foo", is(any(String.class)));
}

Diferencias entre «instanceOf» y «any»
<T> Matcher<T> instanceOf(java.lang.Class<?> type)
Al método le da igual los tipos, en nuestro ejemplo comparamos el tipo String con el tipo Integer.
<T> Matcher<T> any(java.lang.Class<T> type)
El tipo debe de coincidir, el método espera que sean el mismo tipo. Debemos comparar el tipo String con un objeto que satisfaga el tipo String, obligatoriamente.

Cadenas

startsWith

Crea un matcher que evalúa si la cadena examinada comienza con la subcadena pasada.

@Test
public void startsWithExample() {
    assertThat("foo bar baz", startsWith("foo"));
}

endsWith

Crea un matcher que evalúa si la cadena examinada termina con la subcadena pasada.

@Test
public void endsWithExample() {
    assertThat("foo bar baz", endsWith("baz"));
}

containsString

Crea un matcher que busca la subcadena pasada en cualquier posición de la cadena.

@Test
public void constainsStringExample() {
    assertThat("foo bar baz", containsString("bar"));
}

Booleanos

allOf

Da la prueba por válida si todos los matchers que engloba coinciden. Similar al operador Java && en cortocircuito.

@Test
public void allOfExample() {
    assertThat("foo bar baz", allOf(startsWith("foo"), endsWith("baz")));
}

anyOf

Da la prueba por válida si alguno de los matchers que engloba coincide. Similar al operador Java || en cortocircuito.

@Test
public void anyOfExample() {
    assertThat("foo bar qux", anyOf(startsWith("foo"), endsWith("baz")));
}

both

Crea un matcher que da la prueba por válida si se cumplen ambas condiciones.

@Test
public void bothExample() {
    assertThat("foo bar baz", both(startsWith("foo")).and(endsWith("baz")));
}

either

Crea un matcher que da la prueba por válida si se cumple al menos una de las condiciones.

@Test
public void eitherExample() {
    assertThat("foo bar baz", either(startsWith("foo")).or(endsWith("baz")));
}

Colecciones

hasItem

Crea un matcher para elementos Iterables. Realiza un único pase con el elemento a buscar. Se detiene al momento de encontrar la primera ocurrencia.

@Test
public void hasItemExample() {
    assertThat(Arrays.asList("foo", "bar", "baz"), hasItem("bar"));
    assertThat(Arrays.asList("foo", "bar", "baz"), hasItem(startsWith("b")));
}

hasItems

Crea un matcher para elementos Iterables. Realiza tantos pases como elementos a buscar. Se detiene al momento de encontrar la primera ocurrencia.

@Test
public void hasItemsExample() {
    assertThat(Arrays.asList("foo", "bar", "baz"), hasItems("bar", "baz"));
    assertThat(Arrays.asList("foo", "bar", "baz"), hasItems(endsWith("o"), endsWith("z")));
}

Hamcrest con JUnit

Descarga el código completo de los ejemplos mostrados.

Valoración: 1 estrella2 estrellas3 estrellas4 estrellas5 estrellas (Ninguna valoración todavía)
Cargando...

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *