🇺🇦 Слава Україні! Consulta cómo puedes ayudar a Ucrania desde España u otros países en supportukrainenow.org.

JUnit 4

Anotaciones Before y After

• Duración: 6:34 • #java #testing #junit #unit-testing

Con las anotaciones Before y After se puede indicar código que debe ser ejecutado antes y después de cada uno de los Test que se ejecuten con JUnit. La anotación Before se puede aprovechar para ejecutar código antes de un test, y típicamente se usa para preparar código que de otro modo tendríamos que repetir en cada uno de los Tests que conforman un test case, mientras que con After podemos ejecutar un código después de cada uno de los tests para no tener que repetirlo uno a uno, como por ejemplo, la rutina de vaciado de una tabla de base de datos usada para pruebas.

Cuando una clase tenga demasiados tests, nos podemos encontrar en la situación de que estos tests siempre tengan el mismo preámbulo y por lo tanto haya código duplicado en ellos. Por ejemplo, si estamos probando una clase con 10 métodos, y hacemos 10 tests para probar cada uno de esos métodos, tal vez cada uno de ellos tenga en la parte superior de su código la misma instanciación de la misma clase.

@Test
public void testName() {
    Customer cus = new Customer("Juan", "Palomo");
    assertEquals("Juan", cus.getName());
}

@Test
public void testSurname() {
    Customer cus = new Customer("Juan", "Palomo");
    assertEquals("Palomo", cus.getSurname());
}

En este caso, tener duplicada la rutina de inicialización es un problema porque si cambia la forma en la que se instancian objetos de esta clase, tendremos que repetir el mismo cambio 10 veces. Similarmente, si después de trabajar con este objeto hay que limpiar otras instancias de clases colaboradoras que han participado en un test, habrá que tener ese código duplicado.

Con Before y After podemos evitar esto. Se tratan de dos anotaciones que pondremos a métodos de una clase de test. Cuando un método está etiquetado con la anotación @Before, lo que JUnit hace es ejecutar ese método antes de cualquier otro método anotado como test en la misma clase. Similarmente, si anotamos un método con la anotación @After, JUnit ejecutará ese método después de correr los métodos declarados como test en una clase.

Si vas a usar Before y After, tienes que tener en cuenta que las variables locales de los métodos no se comparten. Por lo tanto, lo normal en estos casos será utilizar atributos de instancia dentro de la clase del tests. Puedes imaginarlo como un constructor especializado para usar en tus tests.

public class CustomerTest {
    private Customer cus;

    @Before
    public void before() {
        cus = new Customer("Juan", "Palomo");
    }

    @Test
    public void testName() {
        assertEquals("Juan", cus.getName());
    }

    @Test
    public void testSurname() {
        assertEquals("Palomo", cus.getSurname());
    }
}

Un ejemplo más completo que nos permitiría imprimir por la salida estandar del runner el ciclo de ejecución de un test:

public class LifecycleTest {
    @Test
    public void testPrueba1() {
        System.out.println("test - Prueba 1");    
    }

    @Test
    public void testPrueba2() {
        System.out.println("test - Prueba 2");    
    }

    @Before
    public void before() {
        System.out.println("before");
    }

    @After
    public void after() {
        System.out.println("after");
    }
}

En este caso, veremos por pantalla lo siguiente:

before
test - Prueba 1
after
before
test - Prueba 2
after

Podemos imaginar las tres primeras líneas como el ciclo before-test-after de uno de los tests, y las otras tres como el ciclo before-test-after del otro test. Aquí, de hecho, vemos una consecuencia muy importante: Before y After se van a ejecutar tantas veces como tests haya en una clase, y siempre van a tratar al test como si fuese el queso de un sandwich, es decir, siempre va a quedar por fuera el Before y el After, envolviendo al Test.

Desplegar transcripción del episodio

Hola a todos, qué tal, como estáis y bienvenidos al tercer episodio de este tutorial de JUnit. En este episodio vamos a mostrar más características que nos ofrece la API de JUnit, y en particular hoy os vengo a hablar acerca de las anotaciones Before y After, que nos permiten ejecutar código antes y después de una determinada prueba.

Para ello voy a introducir una serie de cambios en la calculadora. Voy a hacer que deje de ser una calculadora con métodos static, y vamos a convertirla en una clase de verdad, con su propio constructor. Principalmente porque lo que me interesa es establecer un método y, bueno, establecer una serie de métodos, pero también establecer una serie de campos, como por ejemplo Ans. Si teneis una calculadora... yo que sé, de estas marcas Casio, reconoceréis la tecla Ans: es esa tecla que guarda siempre último resultado... realmente no sé exactamente cuál es el nombre en las calculadoras, pero bueno, yo lo voy a llamar simplemente tecla Ans probablemente Answer. Cuando nosotros creamos una Calculadora, cuando la reseteamos, inicialmente va a valer 0. Pero lo que yo quiero es que, por ejemplo, cada vez que efectúe un cálculo, se guarde en Ans el resultado de dicho cálculo. De modo que, por ejemplo, puedo tener un método llamado add(), que nos devuelva mismamente las sumas de a y b, y que haga por ejemplo que Ans sea igual a a+b, y se devuelva Ans. Y puedo tener un método llamado sub(), que lo que haga es lo mismo pero con a-b y que nos devuelva la resta. Vamos a aprovechar que tenemos Ans para hacer otro tipo de métodos interesantes. Por ejemplo, un método llamado add() que sea acumulativo, es decir que le pasen un único valor y que lo sume a Ans para devolvérnoslo. Y un método llamado sub() que nos haga lo mismo pero con restas. Y por último un método llamado ans(), que simplemente nos devuelva el valor de Ans, por si lo queremos recuperar.

Entonces, ¿qué podemos hacer ahora? Podemos crear pruebas sanitarias para probar todo esto que estamos haciendo... podríamos hacer @Test public void testSum(), y podríamos decir: a ver, Calculadora calc = new Calculadora(). Y podríamos decir calc.add(3, 2), y decir, int result, int esperado es 5, asumimos. Y decir assertEquals, por ejemplo, de esper con result. Y podemos crear un método llamado testAnsSum(), para probar que después de hacer una suma del valor de Ans es el esperado; por ejemplo, Calculadora calc = new Calculadora(), que haga algo parecido: calc.add(3,2), pero esta vez decimos decimos que el resultado es calc.ans() y que el esperado es 5, porque evidentemente hemos hecho la suma de 3 + 2. En este caso, se supone, que si yo comparo esper con result me debe dar 5. Estoy escribiendo aquí código por encima de lo que debería ser, podría simplemente escribirlo así, vale. Realmente tampoco pasa nada y seguramente quede más corto. Pero en este caso no me quiero centrar en eso como forma de reducir el número de líneas de código. Sí, es cierto que esto si lo corro como una prueba unitaria me va a funcionar. Veis que funciona sum() y que funciona ansSum(), pero hay código que estoy repitiendo en todos los casos. Y es sin duda la inicialización de la calculadora. Si tenemos muchísimos métodos, estaría bastante bien que si tenemos que ejecutar código antes de que venga realmente la auténtica prueba (que no son más que estas líneas de aquí), pues que se haga de una forma común y que se separe un poquito. Y por eso tenemos una serie de anotaciones, como por ejemplo la Before, que sirve para ejecutar código antes de que se ejecute una prueba como tal.

Entonces, yo tengo aquí un método llamado before(), que tiene que ser public void, evidentemente. Y para que comprobéis que esto se ejecuta voy a hacer que se imprima un println aquí que ponga before(), para se vea que estamos en el Before. Y aquí podemos ejecutar código como si nada. Entonces, uno de los elementos más habituales en JUnit es quitar estas líneas de aquí para que no sean variables los objetos que estamos instanciando, y en su lugar simplemente hacerlo objetos como tal. Y ya que estamos aquí, haremos new Calculadora(), y así tenemos escrito el código de inicialización de la calculadora una vez. Esto tiene la ventaja de que realmente no forma parte de la prueba, simplemente es algo que necesitamos como precondición para poder hacer nuestra prueba. Además que si más adelante queremos cambiar el código de la calculadora y del constructor vamos a tener problemas porque tendremos que cambiarlo muchas veces. Este código, el Before, como ya digo se ejecuta antes de cualquier otro método, entonces si yo por ejemplo hago un println() siempre de todos los métodos que voy probando, por ejemplo de sum(), o por ejemplo de ansSum(), si ahora corro mi prueba unitaria ocurrirá lo siguiente. Como veis, al margen de lo que ocurre aquí, que sabemos que está muy bien, en la consola veis que aparece primero before(), después sum(), después before() y después ansSum(). Es decir, que con Before podemos hacer que se ejecute el código antes de que se ejecute la prueba como tal.

Pero del mismo modo que tenemos Before, también tenemos a After. After es otra anotación que podemos aplicar a cualquier clase con pruebas, y básicamente se va a ejecutar ese código después de efectuar una prueba, ¿vale? Es decir, que antes de ejecutar el Test te va a ejecutar el Before y después de ejecutar el Test se va a ejecutar el After. En este caso no tengo nada que me interese destruir, pero bueno... bueno, podría podría probar a hacer un clear, por ejemplo. Entonces voy a crear un método que se llame hace ac()... sí, ¿porque no? No sé exactamente cómo se llama la tecla. Por si acaso, voy a hacer un clear, simplemente, que diga ans =0, y por probar un poco pues podría hacer calc.clear(). Y poner aquí println de after(). De modo que ahora cuando yo ejecute mi prueba, como veis ocurre lo siguiente: tengo before(), tengo sum(), tengo after(); tengo before(), tengo ansSum(), tengo after(). Before se ejecuta antes, After se ejecuta después. Ya veis aquí para que se puede utilizar: para inicializar el valor de un campo. Utilizarlo en donde lo veáis conveniente.

No confundáis Before con BeforeClass, ni After con AfterClass. Estos son otras anotaciones que también tendremos que ver dentro de muy poco porque también son importantes pero que no es lo mismo.