Excepciones con expected y límites de tiempo con timeout

Dos propiedades de la anotación Test que nos permiten hacer pruebas avanzadas para algunos casos especiales. Con Expected podemos comprobar que dentro de un test se tira una excepción. Si le ponemos este atributo a una anotación Test, el test fallará si alcanza el final de la función y no se ha lanzado ninguna ocurrencia de la excepción proporcionada, y es el método que usaremos en caso de querer probar excepciones. Por otro lado, timeout es un parámetro que podemos usar para forzar al test a fallar si supera un tiempo determinado de ejecución.

En esta lección analizaremos dos parámetros que se pueden proporcionar a la anotación @Test. Normalmente cuando hacemos un test escribimos @Test antes de la signatura de la clase. Si nunca has trabajado con anotaciones más allá de esto o del @Override, tal vez esto sea todo lo que sepas sobre anotaciones.

Sin embargo, las anotaciones pueden tener parámetros. En ese caso las escribimos entre paréntesis indicando el nombre de cada parámetro, luego un igual, y luego el valor del argumento:

@Test(expected = ArithmeticException.class, timeout = 5000)
public void testComplejo() {
    // . . .
}

expected: probar excepciones

En primer lugar, el parámetro expected lo podemos usar para comprobar que el test deba lanzar excepciones. Generalmente, si un test lanza una excepción, se marca como inválido, porque por defecto JUnit va a asumir que el propósito del test es ejecutarse de forma satisfactoria.

Esto no se corresponde con lo que obtenemos cuando probamos un caso límite, o cuando probamos precisamente los casos negativos de una función, como probar que en una calculadora se obtiene un error si intentamos dividir por 0. Podemos intentar hacer esto a mano usando un try-catch y aprovechándonos del aserto fail:

@Test public void testDividirPorCero() {
    Calculadora calc = new Calculadora();
    try {
        calc.div(5, 0);
        fail("Si este fail se ejecuta, tu calculadora no falla");
    } catch (ArithmethicException e) {
        // El test está correcto.
    }
}

Sin embargo, una forma más clara sería únicamente usar el parámetro expected para indicar que el test debe fallar.

@Test(expected = ArithmeticException.class)
public void testDividirPorCero() {
    Calculadora calc = new Calculadora();
    calc.div(5, 0);
}

En este caso, JUnit comprobará que el test lanza una excepción de la clase que le proporcionemos como valor. Si el test no lanza ninguna excepción durante su ejecución, JUnit forzará un error indicando que se esperaba una excepción siendo lanzada pero que no tuvo que capturar nada.

Observa también como JUnit te exige que pases tal cual un valor de tipo Class. Deberás utilizar el atributo estático .class, que sirve para obtener tal cual la instancia Class<?> de una clase.

timeout: acotar un test en el tiempo

Este otro parámetro puede ser de utilidad cuando lo que queremos es limitar el tiempo que un test puede permanecer en ejecución. Con timeout podemos especificar un valor en milisegundos que le vamos a dar como límite al test para que termine. Si el test permanece más milisegundos de lo permitido en ejecución, entonces JUnit interrumpirá la ejecución del test y lo marcará como erróneo.

@Test(timeout = 2000)
public void testTemporal() {
    // Este test no debería fallar.
    Calculadora calc = new Calculadora();
    calc.div(5, 2);
}

Podemos usar esto para limitar el tiempo en ejecución de un test. Algunos ejemplos de cuando esto nos puede venir bien:

  • Podemos usar timeout para garantizar que un algoritmo no se vuelve poco óptimo con el tiempo. Imagina que haces un cambio a un método de una clase auxiliar y eso provoca que el rendimiento del algoritmo de cálculo principal se degrade. Si ponemos un timeout al test que ejecuta los algoritmos, podemos detectar cuando hemos hecho por accidente que el rendimiento de nuestro programa empeore. Se trata de un test de regresión.

  • En algunas ocasiones, vamos a hacer tests que no son unitarios, sino de integración. Por ejemplo, puede que un test tenga que conectarse con un servidor o base de datos externa a fin de obtener un parámetro para otro test. En caso de que ese servidor o base de datos falle, tal vez se ralentice toda la ejecución del test. Con un timeout podemos reducir el tiempo que hay que esperar para marcar todo el test como inválido si no se puede establecer una conexión con el servidor.

  • En algunos casos más extremos, podemos usar el timeout para protegernos ante bucles infinitos que nos salten por error de vez en cuando en tests que todavía no sabemos por qué fallan.

@Test(timeout = 2000)
public void testInfinito() throws InterruptedException {
    Thread.sleep(4000);
}

En este caso, el test sólo demorará 2 segundos en ejecutarse, incluso aunque dentro hayamos puesto un sleep() de 4 segundos, debido a que tras 2 segundos JUnit parará la ejecución del test.

Lista de reproducción
  1. 1
    Qué es JUnit y cómo lo configuro
    7 minutos
  2. 2
    Tu primera prueba de JUnit
    7 minutos
  3. 3
    Anotaciones Before y After
    7 minutos
  4. 4
    Los asertos que trae JUnit
    10 minutos
  5. 5
    Excepciones con expected y límites de tiempo con timeout
    6 minutos
  6. 6
    Anotaciones BeforeClass y AfterClass
    6 minutos
  7. 7
    Pruebas parametrizadas con Parameterized
    7 minutos
  8. 8
    Creación de test suites con el runner Suite
    5 minutos