Fundamentos de Pruebas Unitarias y JUnit

kHJAgAAAABJRU5ErkJggg==

zaAAAAABJRU5ErkJggg==

a3PB95lAanEAAAAASUVORK5CYII=

WebZ2M7I4aRn78olXw+Yec9XpRKXrf9EHbWoxi9rnUrRb3+PzAJ2HreG2J2AAAAAElFTkSuQmCC

Asertos Comunes en JUnit

  • assertEquals(): Comprueba que el valor esperado y el valor resultado sean el mismo.
  • assertNotEquals(): Comprueba que el valor esperado y el valor resultado no sean el mismo.
  • assertTrue(): Comprueba que la expresión se evalúe a true.
  • assertFalse(): Comprueba que la expresión se evalúe a false.

Ejemplo de Fallo de Prueba

Un ejemplo para forzar un fallo en un test podría ser: 3 + 2 = 200 (lo cual es incorrecto).

wGjGYmRPZFupgAAAABJRU5ErkJggg==

Anotaciones de Comportamiento de Test

  • @Test(expected = Excepcion.class): Indica que se espera que un método de prueba lance una excepción específica. La prueba fallará si la excepción no es lanzada o si se lanza una excepción diferente.
  • @Test(timeout = milisegundos): Define un tiempo máximo en milisegundos para la ejecución de un método de prueba. La prueba fallará si el método tarda más de lo especificado.

lztsOat2HEgAAAABJRU5ErkJggg==

we0nHZpAFQUegAAAABJRU5ErkJggg==

D4VrHsx+jJa1AAAAAElFTkSuQmCC

Ciclo de Vida de las Pruebas con JUnit

  • @BeforeEach (antes @Before): El código anotado se ejecuta antes de cada método de prueba. Ideal para inicializar recursos comunes.
  • @AfterEach (antes @After): El código anotado se ejecuta después de cada método de prueba. Útil para liberar recursos.
  • @BeforeAll (antes @BeforeClass): El código anotado se ejecuta una única vez antes de todas las pruebas en la clase. Debe ser un método static.
  • @AfterAll (antes @AfterClass): El código anotado se ejecuta una única vez después de todas las pruebas en la clase. Debe ser un método static.

Nota: Los métodos anotados con @BeforeAll y @AfterAll deben ser static, ya que se ejecutan antes de que se cree cualquier instancia de la clase de prueba.

wFXk9oVobaaUgAAAABJRU5ErkJggg==

j9BsJv4NwJVyQAAAABJRU5ErkJggg==

8DpjMImfUYVEYAAAAASUVORK5CYII=

Pruebas Parametrizadas

Las pruebas parametrizadas permiten ejecutar el mismo método de prueba múltiples veces con diferentes conjuntos de datos. Esto reduce la duplicación de código y mejora la cobertura de pruebas.

w8ebVFOEnShRQAAAABJRU5ErkJggg==

Q7WxgMBoPBYDAYfzRsoQmDwWAwGAwG48lEYXNzM+7dk38PK4PBYDAYDAbj2YPVFDIYDAaDwWAwnkwUcvWEmZmZLIwMBoPBYDAYzzisppDBYDAYDAaD8WSisKWlhTYGg8FgMBgMxrMNqylkMBgMBoPBYDyZKMzIyIBYLGYPr2YwGAwGg8F4xmE1hQwGg8FgMBiMJxOFbW1ttDEYDAaDwWAwnm1YTSGDwWAwGAwG48lEYXp6OtLS0lhNIYPBYDAYDMYzDqspZDAYDAaDwWA8mShsb2+njcFgMBgMBoPxbMNqChkMBoPBYDAYTyYKuXrC1NRUVlPIYDAYDAaD8YzDagoZDAaDwWAwGE8mCjs6OmhjMBgMBoPBYDzbsJpCBoPBYDAYDMaTicKUlBQkJyezmkIGg8FgMBiMZxxWU8hgMBgMBoPBYKKQwWAwGAwGAwz8P2QwIqp3VsiGAAAAAElFTkSuQmCC

Control de Versiones con Git: Comandos Esenciales y Flujo de Trabajo

Comandos Básicos de Git

  • git init: Inicializa un nuevo repositorio Git en el directorio actual.
  • git status: Muestra el estado de los archivos en el directorio de trabajo y el área de preparación (staging area).
  • git add <fichero> / git add <directorio> / git add .: Añade archivos o directorios al área de preparación para el próximo commit.
  • git commit -m "MENSAJE": Guarda los cambios del área de preparación en el historial del repositorio con un mensaje descriptivo.
  • git log: Muestra el historial de commits.
  • git log --oneline: Muestra un historial de commits más conciso, una línea por commit.

Inspección de Cambios

  • git diff: Muestra las diferencias entre el directorio de trabajo y el área de preparación, o entre commits.
  • git difftool: Abre una herramienta de comparación visual para ver los cambios de forma más detallada.

Configuración de Ignorados con .gitignore

El archivo .gitignore permite a Git ignorar la existencia de ciertos archivos o directorios, evitando que se les realice seguimiento. Es ideal para incluir archivos autogenerados, temporales, compilados, etc.

Admite diferentes comodines y expresiones:

  • Comentarios: Líneas precedidas por #.
  • *.tmp: El asterisco (*) indica cero o más caracteres. Ej: fichero.tmp, fichero_prueba.tmp, log.tmp.
  • fichero?.tmp: La interrogación (?) indica exactamente un carácter. Ej: fichero1.tmp, fichero2.tmp.
  • **/fichero.txt: El doble asterisco (**) sustituye a cero o más directorios. Ej: src/components/component1/fichero.txt, src/api/fichero.txt, /fichero.txt.
  • fichero_[a-z].tmp: Permite usar conjuntos de caracteres definidos por rangos. Ej: fichero_a.tmp, fichero_b.tmp.
  • fichero_[0123].tmp: Permite usar conjuntos específicos de caracteres. Ej: fichero_1.tmp, fichero_2.tmp, fichero_3.tmp.
  • !fichero.tmp: Permite incluir excepciones a archivos previamente ignorados.
  • directorio/: La barra final (/) ignora todo el directorio y su contenido. Sin la barra, ignoraría tanto directorios como archivos con ese nombre.
  • directorio/subdir/fichero.txt: Ignora un archivo específico en una ruta concreta.

Gestión de Archivos en Git

  • git rm <fichero>: Borra un archivo del directorio de trabajo y lo elimina del seguimiento de Git.
  • git mv <fichero_antiguo> <fichero_nuevo>: Renombra o mueve un archivo dentro del repositorio Git.

Trabajo con Repositorios Remotos (GitHub)

Para vincular un repositorio local con uno remoto en GitHub y subir los cambios:

  1. Inicializar un repositorio local: git init
  2. Vincular con el repositorio remoto: git remote add origin <URL_del_repositorio>
  3. Añadir archivos al área de preparación: git add .
  4. Realizar un commit: git commit -m "Mensaje del commit"
  5. Subir los cambios al repositorio remoto: git push origin <nombre_rama_principal> (ej. main o master)

Deshacer Cambios en Git

  • git checkout <ID_commit> -- <fichero> o git checkout <ID_commit> .: Permite restaurar un archivo o todo el directorio de trabajo a un estado anterior (un commit específico).
  • git reset HEAD <fichero>: Deshace la adición de un archivo al área de preparación (staging area), moviéndolo de nuevo al directorio de trabajo.
  • git reset --hard <ID_commit>: Es una operación potente que elimina los cambios tanto del directorio de trabajo como del área de preparación, volviendo el repositorio al estado exacto del commit especificado.

Diferencias Clave entre git checkout y git reset:

  • git checkout: Generalmente se usa para cambiar de ramas o para restaurar archivos específicos del directorio de trabajo a un estado anterior. Mueve el puntero HEAD si se usa con una rama o commit, pero no altera el historial de commits.
  • git reset: Se usa para deshacer commits o para mover archivos entre el área de preparación y el directorio de trabajo. Puede reescribir el historial de commits si se usa con opciones como --hard.

Ramas (Branches) en Git

  • Una rama permite trabajar en un entorno de desarrollo aislado, como un “directorio de trabajo” nuevo.
  • Un repositorio Git puede contener múltiples versiones del código base simultáneamente a través de ramas.
  • Las ramas son ideales para desarrollar nuevas funcionalidades o experimentar sin afectar la línea principal de desarrollo. Una vez completado y aprobado, el trabajo de la rama se puede fusionar (merge) con la rama principal.

Etiquetas (Tags) en Git

  • git tag -a <nombre_etiqueta> -m "Mensaje de la etiqueta": Añade una etiqueta anotada a un commit específico, útil para marcar versiones importantes (ej. v1.0).
  • git tag: Muestra una lista de todas las etiquetas existentes en el repositorio.

Code Smells y Refactorización: Mejora de la Calidad del Código

Identificación de Code Smells

Los “Code Smells” (malos olores en el código) son indicadores de posibles problemas en el diseño o la implementación del software que pueden dificultar su mantenimiento y evolución. Algunos de los más comunes incluyen:

  • Repetición de Código (Duplicated Code): Fragmentos de código idénticos o muy similares en múltiples lugares, a menudo resultado de copiar y pegar en lugar de reutilizar o aplicar herencia.
  • Rigidez (Rigidity): El software es difícil de cambiar porque un cambio en un lugar requiere muchos cambios en otros lugares. Indica alta dependencia entre componentes.
  • Fragilidad (Fragility): El software se rompe fácilmente en muchos lugares cuando se realiza un cambio en un solo lugar.
  • Complejidad Innecesaria (Unnecessary Complexity): Código más complicado de lo necesario, con áreas inaccesibles o código obsoleto que no se utiliza.

Tipos Específicos de Code Smells y Soluciones

Código Difícil de Entender/Leer
  • Mezcla de Lenguajes: Usar diferentes idiomas (ej. inglés y español) para nombres de variables, métodos o comentarios dentro del mismo código fuente.
  • Mal Aspecto del Código: Falta de indentación consistente, espaciados incorrectos o no seguir un estándar de codificación.

Solución: Adoptar convenciones de nomenclatura y estilo de código consistentes, y usar un único idioma para el código y los comentarios.

Métodos Muy Largos (Long Method)

Son difíciles de entender y reutilizar. Cuanto más corto es un método, más fácil es su comprensión y su potencial de reutilización.

Solución: Descomponer el método en métodos o clases más pequeños y con responsabilidades únicas.

Código Duplicado (Duplicated Code)

El mismo código aparece en más de un lugar.

Solución: Extraer el código duplicado a un nuevo método o clase y reutilizarlo donde sea necesario.

Demasiados Parámetros (Long Parameter List)

Un método con una lista excesivamente larga de parámetros es difícil de comprender y propenso a errores al ser invocado.

Solución: Pasar como parámetros solo aquellos que sean estrictamente necesarios. Considerar agrupar parámetros relacionados en un objeto.

Lista Larga de Imports (Large Import List)

Una lista de importaciones muy extensa puede indicar que una clase tiene demasiadas responsabilidades o que se están importando clases innecesariamente.

Nota: Sustituir por import *; (importación de paquete completo) no impacta negativamente el rendimiento en Java, ya que el compilador solo carga las clases que realmente se usan.

Solución: Refactorizar la clase para reducir sus dependencias o agrupar clases relacionadas en paquetes más específicos.

Clases Muy Grandes (Large Class)

Si una clase intenta resolver demasiados problemas, acumulará un gran número de métodos y atributos, violando el Principio de Responsabilidad Única.

Solución: Distribuir el código en nuevas clases, cada una con una responsabilidad bien definida.

Nombres que No Indican un Propósito (Obscure Names)

Nombres de variables, métodos o clases que no describen claramente su función o propósito.

Solución: Utilizar nombres descriptivos y significativos que reflejen la intención del código.

Código Obsoleto (Dead Code)

Código que no se utiliza o al que no se puede acceder.

Solución: Los IDEs modernos suelen identificarlo. Debe ser eliminado para mantener el código limpio y legible.

Clase Solo Datos (Data Class)

Clases que solo contienen datos (atributos) y no muestran ningún comportamiento (métodos significativos).

Solución: Las clases deberían encapsular tanto datos como el comportamiento asociado a esos datos. Considerar mover la lógica que opera sobre estos datos a la propia clase.

Ventajas de la Refactorización

  • Incremento de la Facilidad de Lectura y Comprensión: El código refactorizado es más claro y fácil de entender para los desarrolladores.
  • Detección Temprana de Fallos: El proceso de refactorización a menudo revela errores o inconsistencias ocultas en el código.
  • Aumento de la Velocidad de Desarrollo: Un código base limpio y bien estructurado permite añadir nuevas funcionalidades de manera más rápida y segura.
  • Mejora de la Mantenibilidad: Facilita futuras modificaciones y correcciones de errores.
  • Reducción de la Deuda Técnica: Disminuye la acumulación de problemas de diseño y calidad.