Programación Avanzada

Práctica 0

Pruebas Unitarias con JUnit

Introducción

Asegurar la calidad del software es un requerimiento transversal en todo proyecto de desarrollo.

La calidad del software se comprueba mediante la realización de pruebas sobre este.

En esta primera práctica veremos el primer paso hacia la calidad del software: las pruebas unitarias.

Pero antes de ello, vamos a ver una herramienta para la construcción de proyectos en Java muy potente llamada Maven.

Bibliografía

Contenidos

  1. Construcción de proyectos con Apache Maven.
  2. ¿Qué son las pruebas unitarias?
  3. Los principios FIRST.
  4. El framework JUnit.
  5. Crear pruebas unitarias.
  6. Definir una prueba con la etiqueta @Test
  7. Otras etiquetas de JUnit.
  8. La biblioteca hamcrest.
  9. Pruebas parametrizadas.
  10. Suites de pruebas.
  11. Un par de plug-ins muy útiles.

Construcción de proyectos con Maven

¿Por qué necesitamos un herramienta de construcción de proyectos?

Piensa en las etapas las tareas típicas durante la creación de un proyecto software:

  • Obtener las bibliotecas de la que depende mi proyecto.
  • Obviamente escribir el código.
  • Compilar el código.
  • Realizar test.
  • Empaquetar mi aplicación.
  • Ejecutar mi aplicación.
  • En el caso de una aplicación web, desplegarla en un servidor
  • ... y un largo etcétera de tareas repetitivas.

Construcción de proyectos con Maven

Apache Maven nos permite automatizar estas tareas.

Definimos, de modo declarativo, lo que necesitamos (dependencias) en nuestro proyeto, y luego lanzamos las tareas maven adecuadas (plugins).

Construcción de proyectos con Maven

Construyamos nuestro primer proyecto con Maven: File → New project:

Pulsa Next

Construcción de proyectos con Maven

Introduce el nombre y elige el directorio para tu nuevo proyecto:

En GroupId introduce una url al reves.

En ArtifactId introduce, de nuevo, el nombre del proyecto.

Construcción de proyectos con Maven

Este es el aspecto final del proyecto:

En el fichero pom.xml se encuentra la descripción de tu proyecto. De momento no hay mucho, pero en breve verás como añadir una nueva biblioteca para que forma parte de tu proyecto.

¿Qué son las pruebas unitarias?

Las pruebas unitarias pretenden probar el comportamiento correcto de las clases de manera aislada.

Esto significa que se prueba la clase aislándola de su interacción con otras clases.

Principios FIRST para la escritura de pruebas unitarias

F: Fast, los test se han de ejecutar rápidamente.

I: Isolated, los test se realizan sobre una clase sin interacción con otras.

R: Repeatable, el orden de ejecución de los test no debe influir en el resultado final.

S: Self-validating, los test se han de ejecutar de modo automático.

T: Timely, se han de crear al mismo tiempo que el software que se está creando.

El framework JUnit

Existen frameworks para realizar pruebas unitarias para prácticamente cualquier lenguaje de programación.

En el caso de Java podemos incluso elegir entre varias opciones.

Una de ellas es JUnit, puede que la más consolidada.

JUnit este es el enlace a la página principal de este framework.

Crear pruebas unitarias

Lo primero que hay que saber es que JUnit ya está integrado en IntelliJ. Cuando te descargas IntelliJ también te estás descargando JUnit y los plug-ins de IntelliJ para el trabajo con este framework.

No obstante, nosotros vamos a utilizar Maven para añadir la biblioteca Junit.

Crear pruebas unitarias

Añade, en el fichero pom.xml las siguiente líneas de declaración:

<properties>
    <java.version>1.8</java.version>
    <junit.jupiter.version>5.0.2</junit.jupiter.version>
    <junit.platform.version>1.0.2</junit.platform.version>
    <maven.compiler.plugin.version>3.8.1</maven.compiler.plugin.version>
</properties>

Crear pruebas unitarias

Las siguientes líneas de dependencias del compilador:

<dependencies>
        <dependency>
            <groupId>org.junit.jupiter</groupId>
            <artifactId>junit-jupiter-api</artifactId>
            <version>${junit.jupiter.version}</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.junit.jupiter</groupId>
            <artifactId>junit-jupiter-engine</artifactId>
            <version>${junit.jupiter.version}</version>
        </dependency>
</dependencies>

Crear pruebas unitarias

Estas líneas para la versión del compilador de java:

 <build>
 <finalName>aritmetica</finalName>
    <plugins>
        <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-compiler-plugin</artifactId>
            <version>${maven.compiler.plugin.version}</version>
            <configuration>
                <target>${java.version}</target>
                <source>${java.version}</source>
            </configuration>
        </plugin>
    </plugins>
</build>

Crear pruebas unitarias

Supongamos que queremos crear un test para esta clase.

public class Aritmetica {
    private float ultimoResultado;
    public float suma(float primerSumando, float segundoSumando) {
        return ultimoResultado = primerSumando + segundoSumando;
    }
    public float resta(float minuendo, float sustraendo) {
        return ultimoResultado = minuendo - sustraendo;
    }
    public float multiplicacion(float primerFactor,
        float segundoFactor) {
        return ultimoResultado = primerFactor * segundoFactor;
    }
    public float division(float dividendo, float divisor) {
        return ultimoResultado = dividendo / divisor;
    }
    public float getUltimaResultado() {
        return ultimoResultado;
  }
}

La etiqueta @Test

En el paquete test crea la clase AritmeticaTest, y dentro de esa clase crea el siguiente método:

La etiqueta @Test marca un método como método de prueba.

Los métodos de prueba siempre deben ser public void.

@Test
public void testSuma() {
    fail("Not yet implemented");
}

El método fail(String) hace fallar el test.

Ejecuta el método situando el curso sobre su nombre y pulsado Shift+Ctrl+F10, verás como falla el test.

La etiqueta @Test

Escribamos el código de prueba.

@Test
public void testSuma() {
    Aritmetica aritmetica = new Aritmetica();
    assertEquals(2, aritmetica.suma(1, 1));
}
  1. Creamos una instancia de la clase.
  2. assertEquals(valorEsperado, valorReal, error) compara el valor esperado con el real dentro de un error.
  3. Ejecutamos el test.

La etiqueta @Test

Ejecuta de nuevo el test y verás como pasa.

Ahora escribe los tests de la página siguiente.

La etiqueta @Test

@Test
public void testResta() {
    Aritmetica aritmetica = new Aritmetica();
    assertEquals(3, aritmetica.resta(4, 1));
}
@Test
public void testMultiplicacion() {
    Aritmetica aritmetica = new Aritmetica();
    assertEquals(6, aritmetica.multiplicacion(2, 3));
}
@Test
public void testDivision() {
    Aritmetica aritmetica = new Aritmetica();
    assertEquals(5, aritmetica.division(10, 2));
}

Ejecútalos todos tests situando el cursor del ratón sobre el nombre de la clase y pulsando Shift+Crtl+F10.

La etiqueta @Test

Una de las cosas buenas de utilizar Maven es que no necesitas usar ningun entorno de desarrollo para compilar/probar/ejecutar nuestros proyectos, podemos hacerlo desde un terminal: mvn test-compile test para compilar y lanzar la ejecución de las pruebas.

La etiqueta @BeforeEach

Fíjate, que para cada test hemos tenido que crear una instancia de la clase Aritmetica.

Sería muy útil poder indicar que un método se ejecuta repetidamente antes de cualquier test, y en él crear las infraestructuras que necesitamos.

Eso es precisamente lo que nos proporciona la etiqueta @BeforeEach, marca un método que se ejecuta siempre antes que cualquier test

La etiqueta @BeforeEach

public class AritmeticaTest {
    private Aritmetica aritmetica;
    @BeforeEach
    public void init() {
        aritmetica = new Aritmetica();
    }
    @Test
    public void testSuma() {
        assertEquals(2, aritmetica.suma(1, 1), 0);
    }
    @Test
    public void testResta() {
        assertEquals(3, aritmetica.resta(4, 1), 0);
    }
    ...
}

Podemos marcar con @BeforeEach más de un método.

La etiqueta @AfterEach

De igual modo, la etiqueta @AfterEach nos premite realizar tareas de limpieza después de realizar cada uno de los test.

public class AritmeticaTest {
    private Aritmetica aritmetica;

    @BeforeEach
    public void init() {
        aritmetica = new Aritmetica();
    }

    @AfterEach
    public void finish() {
        aritmetica = null;
    }
    ...

Podemos marcar con @AfterEach más de un método.

La etiqueta @BeforeAll

Antes de mostrar cómo se usa la etiqueta @BeforeAll cambiemos la definición de la clase Aritmética:

public class Aritmetica {
    public float suma(float primerSumando, float segundoSumando) {
        return primerSumando + segundoSumando;
    }
    public float resta(float minuendo, float sustraendo) {
        return minuendo - sustraendo;
    }
    public float multiplicacion(float primerFactor,
        float segundoFactor) {
        return primerFactor * segundoFactor;
    }
    public float division(float dividendo, float divisor) {
        return dividendo / divisor;
    }
}

La etiqueta @BeforeAll

¿Cuál es la principal diferencia de este versión de la clase Aritmética con respecto de la anterior?

No tiene estado (atributos).

De hecho, podíamos definir todos sus métodos como static, pero no lo haremos para que se vea claro el siguiente ejemplo.

La etiqueta @BeforeAll

En los test anteriores, el método marcado con @BeforeEach se ejecuta antes que cualquier método marcado con @Test, pero fíjate que ahora la clase Aritmetica no tiene estado, hemos eliminado su único atributo ultimaResultado.

Para clases sin estado, es interesante crear una única instancia, y sólo una, antes de que se ejecute cualquier test.

Esto es precisamente lo que podemos hacer con la etiqueta @BeforeAll, el método que se marque con esta etiqueta se ejecutará una única vez antes que cualquier método de prueba.

La etiqueta @BeforeAll

public class AritmeticaTest {
    private static Aritmetica aritmetica;

    @BeforeAll
    public static void init() {
        aritmetica = new Aritmetica();
    }
    ...

Fíjate que estamos obligados a que el método marcado con @BeforeAll sea estático, y por lo tanto el atributo al que accede también debe serlo.

La etiqueta @AfterAll

De igual modo podemos usar la etiqueta @AfterAll para marcar un método de limpieza que se ejecute solo una vez después de todos los métodos de test.

public class AritmeticaTest {
    private static Aritmetica aritmetica;

    @BeforeAll
    public static void init() {
        aritmetica = new Aritmetica();
    }
    @AfterAll
    public static void finish() {
        aritmetica = null;
    }...

Observa que el método marcado con @AfterAll también debe ser static.

Cuando utilizar @BeforeEach/@AfterEach y cuando @BeforeAll/@AfterAll

Los test deben ser independientes, si la clase tiene estado, debemos garantizar que su estado no influya en la ejecución de test diferentes (Isolation), utilizaremos @BeforeEach/@AfterEach.

Si la clase no tiene estado, con una instáncia única es suficiente, y en este caso utilizaremos @BeforeAll/@AfterAll.

La biblioteca hamcrest

La biblioteca hamcrest nos permite escribir test con mayor expresividad.

Para poder usarla añade la siguiente propiedad en el fichero pom.xml

<hamcreast.library>2.1</hamcreast.library>

Y la siguiente dependencia:

<dependency>
    <groupId>org.hamcrest</groupId>
    <artifactId>hamcrest-library</artifactId>
    <version>${hamcreast.library}</version>
    <scope>test</scope>
</dependency>

La biblioteca hamcrest

Ahora puedes escribir tests como los siguientes:

@Test
public void testSuma() {
    assertThat(aritmetica, notNullValue());
    assertThat(aritmetica.suma(2, 3), is(5.0f));
//  assertEquals(2, aritmetica.suma(1, 1), 0);

Observa el uso del método assertThat(valorReal, matcherValorEsperado) y la posibilidad de utilizar los métodos notNullValue() e is(). De este modo, los test son mucho más legibles.

Pruebas parametrizadas

Antes de escribir pruebas parametrizadas, añade la siguiente dependencia en el fichero pom.xml.

<dependency>
    <groupId>org.junit.platform</groupId>
    <artifactId>junit-platform-runner</artifactId>
    <version>${junit.platform.version}</version>
</dependency>

Pruebas parametrizadas

Si queremos probar un mismo métodos con varios casos de entrada, por ejemplo el método de resta para varias parejas (minuendo, sustraendo), es tedioso tener una prueba diferente para cada caso.

JUnit nos proporciona un método para escribir pruebas parametrizadas en las que podemos definir cuantos caso de prueba queramos para un mismo método. Veamos como hacerlo.

Pruebas parametrizadas

Lo primero es añadir una nueva dependencia al proyecto:

<dependency>
    <groupId>org.junit.jupiter</groupId>
    <artifactId>junit-jupiter-params</artifactId>
    <version>${junit.jupiter.version}</version>
    <scope>test</scope>
</dependency>

Pruebas parametrizadas

Ahora creamos un método estático que devuelva las tuplas para cada una de las pruebas.

static Stream<Arguments> datos() {
    return Stream.of(
            Arguments.of(1.0f, 1.0f, 0.0f),
            Arguments.of(2.0f, 1.0f, 1.0f),
            Arguments.of(1.0f, 2.0f, -1.0f)
    );
}

Pruebas parametrizadas

Lo siguiente es crear un método de prueba anotado con @ParameterizedTest, y con @MethodSource con el método de donde extraer las tuplas para las pruebas.

@ParameterizedTest
@MethodSource("datos")
public void restasTest(float minuendo, float sustraendo, float resultado) {
    assertThat(aritmetica.resta(minuendo, sustraendo), is(resultado));

}

Ya puedes ejecutar las pruebas.

Las suites de pruebas

Usualmente, en nuestros proyectos tendremos una gran cantidad de clases de prueba. Tener que lanzar cada una de ellas de manera individual es tedioso.

JUnit nos permite lanzar todas las clases de prueba creando una Test Suite donde especificaremos todas las clases de prueba que queremos lanzar, una tras otra.

Veamos cómo crear una Suite aunque en el ejemplo sólo tenemos una clase de prueba.

Las suites de pruebas

Añadimos una nueva dependencia:

<dependency>
    <groupId>org.junit.platform</groupId>
    <artifactId>junit-platform-runner</artifactId>
    <version>${junit.platform.version}</version>
    <scope>test</scope>
</dependency>

Las suites de pruebas

Y ahora una clase con las siguientes anotaciones:

@RunWith(JUnitPlatform.class)
@SelectPackages("aritmetica")
public class TestSuite {
}

Si ejecutas esta clase, se ejecutarán todas las clases de pruebas del paquete aritmetica.

IntelliJ también permite la ejecución de todos los tests dentro de un paquete. Para ello, selecciona el paquete en la solapa Project, haz click con el botón derecho en el paquete, y selecciona Run Tests in...

Resumen

En esta práctica introductoria, hemos querido hacer patente la importancia de la calidad del código.

Un mecanismo útil para asegurar la calidad del código es realizar pruebas sobre él.

JUnit es un excelente framework de pruebas unitarias que está integrado en IntelliJ y facilita enormemente la escritura de clases de prueba.

Donde seguir aprendiendo

  • El libro Clean code de Robert C. Martin es una excelente referencia sobre cómo escribir buen código.
  • Este enlace al blog de Alfredo Casado es un resumen personal del libro anterior.
  • La página web de JUnit es referencia imprescindible sobre este framework.