Tests más legibles con AssertJ

0
5609

Este tutorial explica qué es AssertJ y por qué conviene usarlo en nuestras clases de prueba. ¡Manos a la obra!

Índice de contenidos

Introducción

AssertJ es un framework que proporciona métodos de prueba específicos, y los encadena de modo similar al lenguaje humano.

assertThat(xFile).exists().isFile().isRelative();

Este código se centra en qué queremos hacer, ocultando el cómo queremos hacerlo en la implementación de cada método. El resultado son pruebas más legibles, y recordemos que legibilidad es mantenibilidad, que es una de los tres motivos por los que escribimos pruebas.

¿Porqué probamos el código?

  • Verificar la especificación. También conocida como “lo que debería hacer el programa”, que puede expresarse como precondiciones, postcondiciones, y expectativas sobre el resultado obtenido.
  • Forzarnos a escribir una tarea por método. Si un método hace una sola cosa, su nombre es representativo de su contenido, lo que me ahorra leer su implementación.
  • Documentar el código que estamos probando. Una prueba de unidad ilustra una funcionalidad concreta (unitaria) del código que probamos. Leyendo los tests por tanto, podemos ver como usar el código probado.

Existen varios frameworks con características similares a AssertJ. Uno de ellos es Hamcrest que también hemos visto en Adictos.

¿Qué es AssertJ?

AssertJ es un proyecto de código abierto que ha surgido a partir del desaparecido Fest Assert. Es compatible con otras librerías como Guava, Joda Time y Neo4J. También, dispone de un generador automático de comprobaciones para los atributos de las clases, lo que añade más semántica a nuestras clases de prueba.

Podemos añadir AssertJ a nuestros proyectos usando Maven:

org.assertj
assertj-core

2.3.0
test

o Gradle:

testCompile 'org.assertj:assertj-core:2.3.0'
// use 3.3.0 for Java 8 projects
testCompile 'org.assertj:assertj-core:3.3.0'

Tras añadirlo al proyecto lo importamos así:

import static org.assertj.core.api.Assertions.*;

Este import estático da acceso a todos lo métodos de AssertJ.

Tipos de comprobaciones

A continuación muestro las comprobaciones más comúnes que incluye AssertJ. El propio código ilustra su funcionalidad, pero lo comento en español por si hay alguna duda.

Comprobaciones de números

Esta comprobaciones valen para datos de tipo numérico como int, double, o float.

@Test
public void shouldCheckNumberAsserts() 
{
    // Comprobamos que un número es mayor que otro
    assertThat(100).isGreaterThan(50);
    
    // Comprobamos que un número es menor que otro
    assertThat(50).isLessThan(100);
    
    // Comprobamos que un número es mayor o igual que otro
    assertThat(100).isGreaterThanOrEqualTo(50);
    
    // Comprobamos que un número es menor o igual que otro
    assertThat(50).isLessThanOrEqualTo(100);
    
    // Comprobamos que un número está entre dos números (inclusive)
    assertThat(50).isBetween(25, 75);
    
    // Comprobamos que un número está cerca de otro (dado un offset o un porcentaje)
    assertThat(50).isCloseTo(75, Offset.offset(25));
    
    // Comprobamos que un número tiene determinado signo
    assertThat(50).isPositive();
    assertThat(-50).isNegative();
    
    assertThat(-50).isNotPositive();
    assertThat(50).isNotNegative();
    
    // Comprobamos si el número es cero o no
    assertThat(0).isZero();
    assertThat(50).isNotZero();
}

Comprobaciones de cadenas

Comprobaciones para el tipo de dato String.

@Test
public void shouldCheckStringAsserts() 
{
    // Comprobamos que una cadena está vacía o es nula o no
    assertThat("").isEmpty();
    assertThat("hi!").isNotEmpty();
    
    // Comprobamos que una cadena empiece o termine por una determinada secuencia o no
    assertThat("hi!").startsWith("hi");
    assertThat("hi!").endsWith("!");
    
    assertThat("hi!").doesNotStartWith("hello");
    assertThat("hi!").doesNotEndWith(":D");
    
    // Comprobamos que una cadena se corresponda con una determinada expresión regular
    assertThat("hi!").matches(Pattern.compile("h.!"));
    assertThat("hi!").doesNotMatch(Pattern.compile("h...o"));
    
    // Comprobamos que una cadena sólo tenga caracteres numéricos
    assertThat("1337").containsOnlyDigits();
    
    // Comprobamos que una cadena sólo contiene una determinada expresión una única vez
    assertThat("1337").containsOnlyOnce("33");
}

Comprobaciones de colecciones

Comprobaciones para colecciones de elementos.

@Test
public void shouldCheckCollectionsAsserts() {
List strings = Arrays.asList("1", "2", "3", "4", "5");

// Comprobamos que contiene un determinado elemento en una determinada posición
assertThat(strings).contains("1");
assertThat(strings).contains("1", atIndex(0));

// Comprobamos si la colección está ordenada
assertThat(strings).isSorted();

// Comprobamos que los elementos de una colección cumplen cierta condición
Condition even = new Condition("even") {
    @Override
    public boolean matches(String value) {
        return Integer.parseInt(value) % 2 == 0;
    }
};

Condition odd = new Condition("odd") {
    @Override
    public boolean matches(String value) {
        return Integer.parseInt(value) % 2 != 0;
    }
};

assertThat(strings).are(anyOf(even, odd));
}

Comprobaciones comunes

Comprobaciones para diversos tipos de datos.

@Test
public void shouldCheckCommonAsserts() {
    // Comprobamos que un objeto es nulo o no
    String example = null;
    assertThat(example).isNull();
    assertThat("").isNotNull();
    
    // Comprobamos que un objeto es igual o no a otro
    assertThat(100).isEqualTo(100);
    assertThat("hi!").isNotEqualTo("hello :D");
    
    // Comprobamos que un objeto está contenido o no en una colección
    List integers = Arrays.asList(1, 2, 3, 4, 5);
    assertThat(3).isIn(integers);
    assertThat(6).isNotIn(integers);
    
    List strings = Arrays.asList("1", "2", "3", "4", "5");
    assertThat("3").isIn(strings);
    assertThat("6").isNotIn(strings);
    
    // Comprobamos que un objeto cumple una condición personalizada o no
    Condition numberSix = new Condition("numberSix") {
        @Override
        public boolean matches(String value) {
            return Integer.parseInt(value) == 6;
        }
    };
    
    assertThat("6").is(numberSix);
    assertThat("4").isNot(numberSix);
    
    assertThat("6").has(numberSix);
    assertThat("4").doesNotHave(numberSix);
    
    // Comprobamos que un objeto cumple una serie de condiciones o alguna de las condiciones
    Condition even = new Condition("even") {
        @Override
        public boolean matches(String value) {
            return Integer.parseInt(value) % 2 == 0;
        }
    };
    
    Condition odd = new Condition("odd") {
        @Override
        public boolean matches(String value) {
            return Integer.parseInt(value) % 2 != 0;
        }
    };
    
    assertThat("6").is(allOf(numberSix, even));
    assertThat("13").is(anyOf(numberSix, odd));
    
    assertThat("6").has(allOf(numberSix, even));
    assertThat("13").has(anyOf(numberSix, odd));
}

Comprobaciones del modelo

Muchas veces queremos comprobar que un determinado objeto de nuestro modelo tiene una serie de valores en ciertos atributos. O, de otra forma, comprobar condiciones para cada objeto de modelo de forma específica. AssertJ nos hace que esa comprobación sea mucho más legible.

Para ello, vamos a partir de que tenemos la siguiente clase:

public class Player {
    private final String name;

    public Player(String name) {
        this.name = name;
    }

    public String getName() {
        return name;
    }
}

Podemos implementar una clase de comprobaciones de los atributos con AssertJ de la siguiente forma:

public class PlayerAssert extends AbstractAssert {

    public PlayerAssert(Player actual) {
        super(actual, PlayerAssert.class);
    }

    public static PlayerAssert assertThat(Player actual) {
        return new PlayerAssert(actual);
    }

    public PlayerAssert hasName(String name) {
        if(!Objects.areEqual(actual.getName(), name)) {
            failWithMessage("expecting {} to be {}", actual.getName(), name);
        }
        return this;
    }
}

Con esto implementado, nuestro método de prueba quedaría de la siguiente forma:

@Test
public void shouldCheckPlayerAsserts() {
    Player player = new Player("frank");
    PlayerAssert.assertThat(player).hasName("frank");
}

Esto puede ser muy útil ya que podemos crear una clase propia para el modelo con las comprobaciones que se realizan en distintos métodos de prueba, evitando así la duplicidad de este código y facilitando su mantenimiento.

Conclusiones

AssertJ mejora la legibilidad y comprensión del código de prueba. En comparación con JUnit ahorra código y nos hace más productivos. Podemos también declarar nuestras condiciones personalizadas y aplicarlas en grupo sobre objetos o colecciones, lo que nos aporta mucho control sobre la comprobación que estamos realizando.

Es una buena opción emplear este tipo de frameworks porque mejoran la calidad de nuestro código.

Referencias

Documentación de AssertJ

DEJA UNA RESPUESTA

Por favor ingrese su comentario!

He leído y acepto la política de privacidad

Por favor ingrese su nombre aquí

Información básica acerca de la protección de datos

  • Responsable:
  • Finalidad:
  • Legitimación:
  • Destinatarios:
  • Derechos:
  • Más información: Puedes ampliar información acerca de la protección de datos en el siguiente enlace:política de privacidad