Está en la página 1de 20

JAVA AVANZADO

Clase 4
Tests Unitarios & TDD
Les damos la bienvenida
Vamos a comenzar a grabar la clase
Clase 04 Clase 05

CLASE 4 - Test Unitarios (TDD) CLASE 5 - Estructuras de Datos


(Introducción)
● Qué es TDD?
● Test Unitarios. ● Introducción
● Ejercitación. ● Tipos de Estructuras
● Vectores y Matrices
● Listas
TESTING
Un proyecto de software tiene implicado un equipo de desarrollo así como múltiples dependencias de otros

proyectos y librerías. No es difícil que se vuelva insostenible probar que un pequeño cambio no produce

errores en otra parte del código. En estos casos habría que probar manualmente toda la aplicación para saber

si todo sigue bien, y aún así pueden haber condiciones que se pasen por alto y produzcan un error en el

momento menos esperado.

Al escribir código que comprueba nuestro propio código estamos automatizando el proceso de testing.

Como programadores, con cada compilación podremos lanzar un conjunto de pruebas básicas, llamadas test

unitarios, para comprobar que los cambios introducidos no afectan al comportamiento esperado del método

bajo los casos de prueba definidos, como si actuara independientemente al resto.


VENTAJAS

1. Fiabilidad: Lo más evidente es que permite la detección temprana e inmediata de errores.


Esto permite avanzar en la implementación de un proyecto o en la inclusión de nuevas
funcionalidades asegurando que estos cambios no rompen funcionalidades ya existentes
que funcionan correctamente.
2. Rapidez: Una vez implementadas, las pruebas automatizadas se pueden ejecutar todas
las veces que sean necesarias sin ningún coste adicional en comparación con las pruebas
realizadas manualmente. Esto no implica que no se tenga que realizar testing manual sino
que este se invierta en aquellas pruebas en las que aporte verdadero valor.
3. Mantenimiento: Obliga a que el código de la aplicación sea razonablemente fácil de
testear lo que implica el seguimiento de buenas prácticas como los principios SOLID. Este
efecto colateral ayuda a aumentar la calidad del código facilitando su mantenimiento.
Cada test de este tipo sobre un componente del sistema se denomina test unitario. Existen otros tipos de

tests encargados de verificar la interacción entre componentes del sistema llamados tests de integración.

Por último, existen tests sobre el sistema en su totalidad llamados tests de sistema.

Los tests unitarios ofrecen numerosas ventajas:

● Sirven como documentación: describen el comportamiento esperado de cada componente.

● Aumentan la reusabilidad del código: para implementar tests unitarios, el software debe estar

dividido en componentes y ser modular.

● Facilitan los cambios en el código y determinar su impacto.


¿Qué es JUNIT?
JUnit se trata de un Framework Open Source para la automatización de las pruebas (tanto
unitarias, como de integración) en los proyectos Software. El framework provee al usuario
de herramientas, clases y métodos que le facilitan la tarea de realizar pruebas en su
sistema y así asegurar su consistencia y funcionalidad.

En primer lugar tenemos que decir que existen principalmente 2 tipos de pruebas con las

que vamos a trabajar, estas son:

● Pruebas unitarias: Consisten en probar la correcta funcionalidad del módulo en

cuestión como si actuara independiente de los demás.

● Pruebas de integración: Como su nombre indica, se prueba la correcta integración

de cada módulo (ya probado con pruebas unitarias) con los demás.
Supongamos que el sistema está implementado,
probado y ya en funcionamiento, pero se desea
añadir una nueva funcionalidad al módulo 4. Tras
el desarrollo de éste, ¿estamos seguros de que
los demás módulos siguen funcionando
correctamente y no se ha alterado su
funcionalidad?.
La manera más rudimentaria y primitiva es probar
a mano todos los módulos relacionados…¿No
sería más fácil automatizar este proceso?.
Automatizando la tarea de realizar las pruebas
conseguimos, además de ganar tiempo cada vez
que tengamos que hacer las pruebas,
asegurarnos que se realizan todas las pruebas
necesarias.
Mediante pruebas de integración no solo se nos permite probar la interacción de un módulo con otro

dentro del mismo sistema, sino que podemos probar las conexiones con otros sistemas externos como

por ejemplo un gestor de Base de datos.

Por otro lado, se ha demostrado que una forma muy eficaz, limpia y profesional de trabajar es

realizar una programación basada en pruebas. Esto consiste en invertir el proceso natural de

desarrollo, cogiendo los requisitos, realizando una batería de pruebas para desarrollar el sistema

que los cumpla.


MÉTODOS

● Método setUp: Asignamos valores iniciales a variables antes de la ejecución de cada test.

Si solo queremos que se inicializan al principio una vez, el método se debe llamar

«setUpClass»

● Método tearDown: Es llamado después de cada test y puede servir para liberar recursos o

similar. Igual que antes, si queremos que solo se llame al final de la ejecución de todos los

test, se debe llamar «tearDownClass»

● Métodos Test: Contienen las pruebas concretas que vamos a realizar.

● Métodos auxiliares.
ANOTACIONES

Se trata de palabras clave que se colocan delante de los métodos y que indican a las librerías
de JUnit instrucciones concretas.
Ejemplos:
● @RunWith: Se le asigna una clase a la que JUnit invocará en lugar del ejecutor por
defecto de JUnit.
● @Before: Indicamos que el método se debe ejecutar antes de cada test (precede al
método setUp).
● @After: Indicamos que el método se debe ejecutar después de cada test (precede al
método tearDown).
● @Test: Indicamos a JUnit que se trata de un método de Test.
Funciones de Aceptación/Rechazo
Una vez hemos creado las condiciones para probar que una funcionalidad concreta funciona es necesario
que un validador nos diga si estamos obteniendo el resultado esperado o no. Para esta labor se definen
una lista de funciones (incluidas en la clase Assert). Ejemplos:

● assertArrayEquals: Recibe como parámetro 2 arrays y comprueba si son iguales. Devuelve

assertionError si no se produce el resultado esperado

● assertEquals: Realiza la comprobación entre 2 valores de tipo numérico. Devuelve assertionError

si no se produce el resultado esperado

● assertTrue: Comprueba si una condición se cumple. Devuelve assertionError si no se produce el

resultado esperado

● fail: devuelve una alerta informando del fallo en el test


Vamos a utilizar JUnit para probar los métodos anteriores. Para ello deberemos crear una serie de clases en las que
implementaremos las pruebas diseñadas. Esta implementación consistirá básicamente en invocar el método que está siendo
probado pasándole los parámetros de entrada establecidos para cada caso de prueba, y comprobar si la salida real coincide con la
salida esperada. Esto en principio lo podríamos hacer sin necesidad de utilizar JUnit, pero el utilizar esta herramienta nos va a ser
de gran utilidad ya que nos proporciona un framework que nos obligará a implementar las pruebas en un formato estándar que
podrá ser reutilizable y entendible por cualquiera que conozca la librería. El aplicar este framework también nos ayudará a tener
una batería de pruebas ordenada, que pueda ser ejecutada fácilmente y que nos muestre los resultados de forma clara mediante
una interfaz gráfica que proporciona la herramienta. Esto nos ayudará a realizar pruebas de regresión, es decir, ejecutar la misma
batería de pruebas en varios momentos del desarollo, para así asegurarnos de que lo que nos había funcionado antes siga
funcionando bien.

Para implementar las pruebas en JUnit utilizaremos dos elementos básicos:

● Por un lado, marcaremos con la anotación @Test los métodos que queramos que JUnit ejecute. Estos serán los
métodos en los que implementemos nuestras pruebas. En estos métodos llamaremos al método probado y
comprobaremos si el resultado obtenido es igual al esperado.
● Para comprobar si el resultado obtenido coincide con el esperado utilizaremos los métodos assert de la librería JUnit.
Estos son una serie de métodos estáticos de la clase Assert (para simplificar el código podríamos hacer un import
estático de dicha clase), todos ellos con el prefijo assert-. Existen multitud de variantes de estos métodos, según el
tipo de datos que estemos comprobando (assertTrue, assertFalse, assertEquals, assertNull, etc). Las
llamadas a estos métodos servirán para que JUnit sepa qué pruebas han tenido éxito y cuáles no.
Cuando ejecutemos nuestras pruebas con JUnit, se nos mostrará un informe con el número de pruebas éxitosas y fallidas, y un
detalle desglosado por casos de prueba. Para los casos de prueba que hayan fallado, nos indicará además el valor que se ha
obtenido y el que se esperaba.
Además de estos elementos básicos anteriores, a la hora de implementar las pruebas con JUnit deberemos seguir una serie de buenas
prácticas que se detallan a continuación:

● La clase de pruebas se llamará igual que la clase a probar, pero con el sufijo -Test. Por ejemplo, si queremos probar la
clase MiClase, la clase de pruebas se llamará MiClaseTest.
● La clase de pruebas se ubicará en el mismo paquete en el que estaba la clase probada. Si MiClase está en el paquete
es.ua.jtech.lja, MiClaseTest pertenecerá e ese mismo paquete. De esta forma nos aseguramos tener acceso a
todos los miembros de tipo protegido y paquete de la clase a probar.
● Mezclar clases reales de la aplicación con clases que sólo nos servirán para realizar las pruebas durante el desarrollo no es
nada recomendable, pero no queremos renunciar a poner la clase de pruebas en el mismo paquete que la clase probada.
Para solucionar este problema lo que se hará es crear las clases de prueba en un directorio de fuentes diferente. Si los
fuentes de la aplicación se encuentran normalmente en un directorio llamado src, los fuentes de pruebas irían en un
directorio llamado test.
● Los métodos de prueba (los que están anotados con @Test), tendrán como nombre el mismo nombre que el del método
probado, pero con prefijo test-. Por ejemplo, para probar miMetodo tendríamos un método de prueba llamado
testMiMetodo.
● Aunque dentro de un método de prueba podemos poner tantos assert como queramos, es recomendable crear un
método de prueba diferente por cada caso de prueba que tengamos. Por ejemplo, si para miMetodo hemos diseñado tres
casos de prueba, podríamos tener tres métodos de prueba distintos: testMiMetodo1, testMiMetodo2, y
testMiMetodo3. De esta forma, cuando se presenten los resultados de las pruebas podremos ver exactamente qué caso
de prueba es el que ha fallado.
Vamos a ver ahora cómo hacer esto desde Eclipse. Lo primero que deberemos hacer es crear un nuevo directorio de fuentes en nuestro
proyecto, para tener separados en él los fuentes de prueba. Por ejemplo, podemos llamar a este directorio test. Una vez hayamos
hecho esto, pincharemos con el botón derecho sobre la clase que queramos probar y seleccionaremos New > JUnit Test Case.
Test Driven Development (TDD)
Desarrollo guiado por pruebas de software, o Test-driven development
(TDD) es una práctica de ingeniería de software que involucra otras dos
prácticas: Escribir las pruebas primero (Test First Development) y
Refactorización (Refactoring). Para escribir las pruebas generalmente se
utilizan las pruebas unitarias (unit test en inglés). En primer lugar, se escribe
una prueba y se verifica que la nueva prueba falla. A continuación, se
implementa el código que hace que la prueba pase satisfactoriamente y
seguidamente se refactoriza el código escrito. El propósito del desarrollo
guiado por pruebas es lograr un código limpio que funcione. Cada prueba será
suficientemente pequeña como para que permita determinar unívocamente si
el código probado pasa o no la verificación que esta le impone. El diseño se
ve favorecido ya que se evita el indeseado "sobre diseño" de las aplicaciones
y se logran interfaces más claras y un código más cohesivo.
En primer lugar se debe definir una lista de requisitos y después se ejecuta el siguiente ciclo:
1. Elegir un requisito: Se elige de una lista el requisito que se cree que nos dará mayor conocimiento del problema y
que a la vez sea fácilmente implementable.
2. Escribir una prueba: Se comienza escribiendo una prueba para el requisito. Para ello el programador debe
entender claramente las especificaciones y los requisitos de la funcionalidad que está por implementar. Este paso
fuerza al programador a tomar la perspectiva de un cliente considerando el código a través de sus interfaces.
3. Verificar que la prueba falla: Si la prueba no falla es porque el requisito ya estaba implementado o porque la
prueba es errónea.
4. Escribir la implementación: Escribir el código más sencillo que haga que la prueba funcione. Se usa la expresión
"Déjelo simple" ("Keep It Simple, Stupid!"), conocida como principio KISS.
5. Ejecutar las pruebas automatizadas: Verificar si todo el conjunto de pruebas funciona correctamente.
6. Eliminación de duplicación: El paso final es la refactorización, que se utilizará principalmente para eliminar código
duplicado. Se hace un pequeño cambio cada vez y luego se corren las pruebas hasta que funcione.
7. Actualización de la lista de requisitos: Se actualiza la lista de requisitos tachando el requisito implementado.
Asimismo se agregan requisitos que se hayan visto como necesarios durante este ciclo y se agregan requisitos de
diseño (P. ej que una funcionalidad esté desacoplada de otra).
Tener un único repositorio universal de pruebas facilita complementar TDD con otra práctica recomendada por los procesos ágiles
de desarrollo, la "Integración Continua". Integrar continuamente nuestro trabajo con el del resto del equipo de desarrollo permite
ejecutar toda la batería de pruebas y así descubrir, si nuestra última versión es compatible con el resto del sistema. Es
recomendable y menos costoso corregir pequeños problemas cada pocas horas, que enfrentarse a problemas enormes cerca de la
fecha de entrega fijada.
Recordá:
● Revisar la Cartelera de Novedades.
● Hacer tus consultas en el Foro.

Todo en el Aula Virtual.


Muchas gracias por tu atención.
Nos vemos pronto

También podría gustarte