Este tutorial explica qué es AssertJ y por qué conviene usarlo en nuestras clases de prueba. ¡Manos a la obra!
Índice de contenidos
- 1. Introducción
- 2. ¿Qué es AssertJ?
- 3. Tipos de comprobaciones
- 4. Comprobaciones del modelo
- 5. Conclusiones
- 6. Referencias
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.