¡Acceso ilimitado 24/7 a todos nuestros libros y vídeos! Descubra la Biblioteca Online ENI. Pulse aquí
¡Acceso ilimitado 24/7 a todos nuestros libros y vídeos! Descubra la Biblioteca Online ENI. Pulse aquí
  1. Libros
  2. Aprender la Programación Orientada a Objetos con el lenguaje Java
  3. Las pruebas
Extrait - Aprender la Programación Orientada a Objetos con el lenguaje Java (con ejercicios prácticos y corregidos)
Extractos del libro
Aprender la Programación Orientada a Objetos con el lenguaje Java (con ejercicios prácticos y corregidos) Volver a la página de compra del libro

Las pruebas

Introducción

Cuando un cliente pide un desarrollo, hay unas especificaciones que describen las funcionalidades de nivel superior en forma de «casos concretos de uso». Este documento servirá más tarde para validar los desarrollos realizados. Estos casos de uso van a poner en acción numerosos objetos desarrollados por el equipo. Estos objetos se van a comunicar entre ellos con los métodos, siguiendo una cronología cuidadosamente pensada durante el análisis. Cada intercambio se realizará la mayor parte del tiempo con argumentos de «ida» y «vuelta». Los rangos admisibles de estos argumentos serán conocidos y estos objetos por lo general funcionarán perfectamente bien cuando reciben lo que se espera, en el momento en que se espera. Pero ¿qué pasará cuando se exceda el tiempo o los argumentos que se pasan estén fuera de los límites?

La solidez de una aplicación se muestra en los casos extremos, gracias a operaciones adaptadas para corregir los defectos y una correcta protección de los datos. Para conseguir este grado de fiabilidad, antes que cualquier otra cosa, cada eslabón de la cadena debe permanecer estable, independientemente de sus condiciones de explotación. Para esto, hay que llevarlos más allá de sus propios límites. El desarrollador deberá imaginar los peores casos...

Entorno de ejecución de las pruebas unitarias

Siempre es posible escribir «pequeñas aplicaciones» autónomas que van a permitir verificar los objetos de la futura «gran aplicación». Por ejemplo, un código que se ha cargado en una consola podrá instanciar la clase que se tiene que probar y, a continuación, desencadenar una serie de llamadas que muestren mensajes de error o escriban los resultados en un archivo. Es posible, pero no muy práctico.

IntelliJ IDEA, con el entorno de pruebas Java JUnit 5, simplifica la redacción, ejecución y análisis de las pruebas unitarias. No hay necesidad de «pequeñas aplicaciones» autónomas; IntelliJ IDEA ofrece directamente la preparación de un conjunto de pruebas, que el desarrollador podrá reproducir en su totalidad, en grupo (playlist) o individualmente, gracias al explorador de pruebas. Los resultados de las pruebas se resumen en una vista tipo «árbol», que utiliza los colores amarillo y verde y permite ir rápidamente a la línea de código que ha fallado en caso de error. Es posible ejecutar pruebas en modo Debug y, por lo tanto, «trazar» los métodos llamados en los objetos que se están probando.

Este capítulo solo trata de una pequeña parte de JUnit 5, que es un entorno de prueba muy potente.

A continuación se muestra...

El proyecto con pruebas unitarias

Las pruebas son métodos de clases que se añaden, la mayor parte de las veces, al proyecto que contiene las clases que se deben probar, ayudándose para ello de los asistentes de IntelliJ IDEA.

 Hagamos un primer proyecto, siempre de tipo consola, llamado... DemoPruebas, que tenga como nombre de package demo.prueba.

 Añadamos a este proyecto una clase Vector, cuyo contenido sea el siguiente:

package demo.prueba;  
  
public class Vector {  
  
   private int x;  
   private int y;  
  
   public int getX() {  
       return x;  
   }  
  
   public void setX(int x) {  
       this.x = x;  
   }  
  
   public int getY() {  
       return y;  
   }  
  
   public void setY(int y) {  
       this.y = y;  
   }  
  
   public void Agregar(Vector v)  
   {  
       x += v.x;  
       y += v.y;  
   }  
} 

La funcionalidad de esta clase...

La clase de pruebas

 Sitúe su ratón en la clase Vector y llame a la opción Create New Test del menú Navigate.

images/01RI09V2.png

 Confirme la inserción de las pruebas en la misma raíz.

images/02ri09v2.png

 En el asistente de creación de pruebas, seleccione la librería JUnit5 y, a continuación, haga clic en el botón Fix para arrancar su instalación en el proyecto.

images/03RI09V2.png

 Llame a la clase de pruebas VectorPrueba y guarde el paquete demo.prueba como Destination package.

 Seleccione el método Agregar como método para probar.

images/04RI09V2.png

 Abra la clase VectorPrueba y utilice el asistente «luz roja» para realizar las adiciones necesarias.

images/05RI09V2.png

El código de partida de nuestra clase de prueba es el siguiente:

package demo.prueba;  
import org.junit.jupiter.api.test;  
  
import static org.junit.jupiter.api.Assertions.*;  
 class VectorPrueba {  
  
   @Prueba  
   void agregar() {  
   }  
} 

En él encontramos el paquete que contiene la clase VectorPrueba que, en sí mismo, contiene el método agregar.

Cada método de pruebas se debe preceder por el atributo @Prueba, que lo distinguirá de otros eventuales métodos de la misma clase.

Puede ser que el asistente haya definido totalmente la ruta del paquete Prueba, escribiendo @org.junit.jupiter.api.test....

Contenido de un método de prueba

La prueba se va a ejecutar directamente en el entorno de desarrollo y, por lo tanto, no necesita método main de una clase ni un proyecto particular. El código del método de prueba debe instanciar la clase destino, llamar a uno de sus métodos y, a continuación, validar el comportamiento esperado. Normalmente, en el marco de pruebas unitarias, solo debe tener una acción sobre el objeto por cada prueba.

Por ejemplo, para un método Sumar, la prueba comprueba que, si se pasan 2 y 3 como argumentos, entonces el resultado devuelto será 5.

Las comprobaciones usan la clase Assertions del paquete org.junit.jupiter.api, que ofrece una colección de métodos que tienen servicios mucho más completos que los asociados a la palabra clave assert, utilizada hasta entonces en este libro.

Entre este juego de métodos se encuentra el método assertTrue y sus sobrecargas.

images/09ri09v2.png

(extracto de http://junit.org/junit5/docs/current/api/org/junit/jupiter/api/Assertions.html)

De esta forma, este método permite comprobar que una condición es verdadera y, si no es el caso, devolver un mensaje en el explorador de pruebas.

Algunos métodos de la clase Assertions ofrecen una versión con mensaje y una versión sin mensaje. Se aconseja utilizar la versión con mensaje para que el análisis de los problemas sea mucho más rápido.

Ejemplo de...

Operaciones de preparación y limpieza

La ejecución de las pruebas se puede preceder por operaciones de inicialización y seguir por operaciones de «limpieza». Estas operaciones opcionales se escriben en los métodos que utilizan atributos especiales, que definen en qué momento se ejecutan durante el script.

Atributo del método en JUnit5

Cuándo se ejecutará el método

@BeforeAll

Una vez al inicio de la serie de pruebas de la clase.

@BeforeEach

Antes de cada prueba.

@AfterEach

Después de cada prueba.

@AfterAll

Una vez al final de la ejecución de la serie de pruebas de la clase.

Extracto de código que muestra la sintaxis de todos los métodos de inicialización

package demo.prueba;  
  
import org.junit.jupiter.api.*;  
  
public class OtrasPruebas {  
  
   public OtrasPruebas(){  
       System.out.println("Constructor de la clase de pruebas"); 
   }  
  
   @BeforeAll  
   public static void Inicialmente(){  
       System.out.println("Antes de lanzar las pruebas (...)");  
   }  
  
   @AfterAll  
   public static void Posteriormente(){  
   ...

Las pruebas con argumentos externos

Imaginemos que vamos a probar el comportamiento de un método en centenares de casos de uso. Escribir y mantener la colección de información en el código será difícil y cualquier cambio necesitará la recompilación de las pruebas: esto no es práctico. Externalizar sus listas en archivos y, a continuación, realizar su carga e iteraciones en la prueba en sí misma no es factible, pero ¿cuál es la función de la prueba? Basarse en el entorno de desarrollo para alimentar nuestras pruebas con datos es, de largo, la solución más confortable y esto es lo que nos ofrece JUnit 5. Se habla por tanto de «data-driven unit tests».

La demostración que sigue utiliza una colección en formato CSV (Comma-Separated Values). Se trata de un archivo en formato texto muy sencillo, donde cada línea representa una colección de argumentos separados por comas. Podemos fácilmente obtener un archivo .csv a partir de un archivo Excel, gracias a su función de exportación.

 Implementamos nuestro primer «data-driven unit tests» para comprobar el funcionamiento de un método que devuelve una cadena de caracteres. Por ejemplo, si pasamos «Hello», debe devolver «olleH».

 Añada al proyecto una clase OperacionesCadena que contenga:

package demo.prueba;  ...

Las suites de pruebas

Ya hemos visto cómo construir clases que contienen métodos de prueba. Estas pruebas se pueden ejecutar desde IntelliJ IDEA «manualmente». La manipulación puede convertirse rápidamente en algo enrevesado si hay un número importante de clases de prueba. Además, si decidimos crear una cadena de «build», es decir, un procedimiento que permita compilar y formatear el producto final, entonces querremos automatizar la ejecución de las pruebas. Para esto vamos a interesarnos por saber qué es una «suite de pruebas».

Una suite de pruebas es un objeto que va a contener una lista de pruebas para realizar. El objetivo de esta funcionalidad es automatizar las pruebas para comprobar que las nuevas funcionalidades están activas y que no hay regresión sobre las antiguas. Podrá construir una «playlist» de pruebas entre todos los métodos que haya codificado.

Ejercicio

1. Enunciado

 Debe escribir:

  • Una clase ClaseCadena que contenga un método DevuelveIniciales que permita devolver las iniciales de los nombres y apellidos que se pasan como argumento en forma de cadena, como se indica a continuación:

String iniciales = ClaseCadena.DevuelveIniciales("Andreas Dulac"); 
// iniciales debe contener "A.D." 

Si el método recibe un argumento incorrecto, debe devolver una cadena vacía.

  • Una serie de pruebas unitarias permiten comprobar que ninguno de los casos de uso del método provocan un funcionamiento incorrecto.

2. Corrección

La corrección de este ejercicio está en el proyecto LabPruebas, que se puede descargar.

Los casos de error son los siguientes:

  • Un argumento de tipo String, por naturaleza, es nullable. El valor null que se pasa como argumento no debe provocar un funcionamiento incorrecto.

  • Se debe tener en cuenta el caso de una cadena vacía.

  • El caso de una cadena que solo contenga una única palabra también se debe probar.

package demo.prueba;  
  
import static org.junit.jupiter.api.Assertions.*;  
  
class ClaseCadenaPrueba {  
  
   @org.junit.jupiter.api.test  
   void PruebaCasoNormal() {  
       String iniciales = ClaseCadena.DevuelveIniciales 
("Andreas Dulac");  
   ...