Tal como estudiamos en la lección 2, en JUnit existe la noción de asertos para representar funciones que se usan dentro de un test case y que lanzan un error si descubren que existe algo en las expresiones que evaluamos que es incorrecto. Estudiamos que fail() es un tipo de aserto que falla siempre, y que assertEquals() es un aserto que falla cuando los parámetros que le proporcionanos no son los mismos. En esta lección, estudiaremos más asertos que podemos usar para enriquecer nuestros tests.
¿Por qué hay tantos assertEquals?
Una primera pregunta que nos podemos hacer es por qué en JUnit existen tantos métodos assertEquals. Como puedes ver, cada uno de estos métodos lleva una signatura diferente y acepta un conjunto de parámetros distinto. El propósito de estas funciones es tener varias alternativas a la hora de probar que dos cosas son iguales.
Tenemos un assertEquals que trabaja con long. Este es el que usamos para probar números enteros. Sí, para cualquier tipo de número entero. Incluso si vamos a probar dos valores de tipo short, usaremos el método assertEquals(long, long):
short actual = check.getVersion();
short expected = 200;
assertEquals(actual, expected); // ¡auto-castea a long!
Al final, la razón por la que esto se hace así es para que el número de versiones que hay del método assertEquals sea controlado. Java casteará implícitamente a long un parámetro de un tipo de datos de menor capacidad, por lo que es transparente. Si lo hiciésemos al revés, no podríamos, puesto que pasar de long a int haría perder precisión.
Tenemos un assertEquals que trabaja con Strings, y que comprobará que dos strings son iguales y contienen los mismos caracteres.
assertEquals de dobles y flotantes
También tenemos otras variantes del assertEquals que no deberíamos usar. Por ejemplo, no deberíamos usar el assertEquals(double, double). Este aserto tiene un problema: a los ordenadores no se les da bien la precisión de números de coma flotante, y puede ocurrir que un test falle porque al hacer las cuentas, un valor que debería dar 9.0 nos dé 9.00001, haciéndolo fallar.
En su lugar, podemos usar la variante de tres parámetros, assertEquals(double expected, double actual, double delta). Esta variante pide además un delta, que sirve para declarar un margen de error. Cuando se compruebe si dos doubles son iguales, tomará un margen de error de tal manera que si la distancia entre el número real y el número esperado es menor al delta, lo considerará como si fuese correcto. Por ejemplo:
assertEquals(0.5, 0.500001, 0.01)será un test exitoso, incluso aunque los valores esperados y reales no coincidan en términos absolutos. Existe una diferencia despreciable entre ambos números que es mucho menor al margen de error que le hemos dado a este test (0.01).assertEquals(0.3, 0.30002, 0.00001)será un test que falle, porque en este caso hemos puesto un delta mucho menor, y la distancia entre 0.3 y 0.30002 supera el límite fijado por ese delta.
Estos conceptos son un poco complicados de entender al principio, sobre todo si nunca has tenido que pelearte con deltas y márgenes de error en alguna asignatura de ciencias mientras estudiabas, pero con el tiempo los puedes acabar comprendiendo.
assertArrayEquals
Otro método que hay disponible como assertEquals pero que nos conviene no utilizar es el que trabaja con arrays. En su lugar, si queremos comprobar que dos arrays son iguales, deberíamos usar el aserto assertArrayEquals, que se ocupará de comprobar que los arrays tengan el mismo número de elementos, y que comprobará uno a uno que los elementos que hay en los mismos índices coincidan.
// El siguiente test fallará debido a que no tiene el
// mismo número de elementos un array respecto al otro.
int[] uno = new int[] { 1, 2, 3 };
int[] dos = new int[] { 1, 2 };
assertArrayEquals(uno, dos);
// El siguiente test fallará debido a que el elemento
// en la posición 1 del array no coincide.
int[] tres = new int[] { 1, 2, 3 };
int[] cuatro = new int[] { 1, 0, 3 };
assertArrayEquals(tres, cuatro);
// El siguiente test es exitoso porque los arrays tienen
// el mismo número de elementos y además para cada índice,
// a[i] y b[i] son iguales.
int[] cinco = new int[] { 1, 2, 3 };
int[] seis = new int[] { 1, 2, 3 };
assertArrayEquals(cinco, seis);
assertTrue, assertFalse, assertNull y assertNotNull
Estos asertos son fáciles: prueban que la expresión o variable que le proporcionamos coincida con una constante según la intención del aserto. Son una forma rápida de no tener que escribir manualmente un assertEquals respecto a true, false o null.
Por ejemplo, los assertNull nos permiten comprobar que una expresión sea nula, mientras que los assertNotNull nos permiten probar lo contrario. No existe noción de assertNotFalse porque a eso le llamaríamos assertTrue en primer lugar.
assertSame y assertNotSame
Finalmente, otro tipo de aserto útil es el assertSame. Este comprueba que dos expresiones sean exactamente las mismas, no tanto por igualdad, sino por identidad. Java es un lenguaje de programación donde solo porque instancies dos veces el mismo objeto no va a ser necesariamente la misma variable. Dicho sea de otro modo, aunque yo llame dos veces a un constructor con los mismos parámetros, las dos variables como tal serán referencias a objetos distintos que haya en el heap.
Instant i1 = Instant.parse("2016-05-01T15:00:00");
Instant i2 = Instant.parse("2016-05-01T15:00:00");
Posiblemente en este caso, i1 e i2 serán equivalentes si los comparamos mediante .equals(). Por esta razón, en el caso de assertEquals el test saldrá bien. Sin embargo, debido a que son dos instanciaciones separadas, apuntarán a objetos diferentes del heap, por lo que i1 e i2 no serán la misma referencia.
assertSame comprueba que la referencia sea la misma, motivo por el cual en este caso se lanzaría un error de aserto si trato de comprobar que i1 e i2 sean lo mismo. assertNotSame comprobará que las referencias apunten a objetos diferentes.
La gente que vea la versión en video de esta lección se dará cuenta que al principio, mi demostración falla debido a que trato de usar strings. En Java, los strings son un poco especiales y si tal cual tratas de instanciar dos veces el mismo string estático, sí que va a ser reutilizado, motivo por el cual un assertSame() de dos cadenas de caracteres que se han instanciado estáticamente (es decir, que tal cual se ponen entre comillas), sí va a ser verdadero. Con una clase como un StringBuilder, los resultados podrían haber sido diferentes. (¡Gracias a la gente que aportó contexto en los comentarios del vídeo!)
Mensajes personalizados
Muchos de los asertos que hemos visto en este capítulo tienen versiones alternativas donde se acepta un parámetro adicional de tipo String que típicamente se llamará message. El objetivo de esta versión es poder pasarle un mensaje de error propio que será lo que se muestre en el runner en caso de que el aserto falle. De este modo, una persona que esté leyendo la salida de la suite de tests puede comprender mejor un mensaje de error de un aserto complicado. También los podemos usar como comentarios para dejarnos notas tanto a la hora de leer la salida de un test, como a la hora de leer el propio test como tal en el código fuente.