¿Qué son los Test Unitarios o 'unit testing'?
Unit testing: es la práctica de escribir y ejecutar pruebas automatizadas para validar el funcionamiento correcto de unidades de código. Detecta errores tempranamente, mejora calidad y confianza en el software.
El unit testing, o pruebas unitarias en español, es una práctica ("buena") en el desarrollo de software que consiste en escribir y ejecutar pruebas automatizadas para verificar el correcto funcionamiento de unidades individuales de código, como funciones, métodos o clases. El objetivo del unit testing es identificar y corregir errores en el código de manera temprana, garantizando la calidad y el comportamiento esperado de cada unidad de código de forma aislada. Estas pruebas suelen ser rápidas de ejecuta con confianza a medida que avanzan en el desarrollo del software.
Veamos los principales motivos por lo que son muy necesarias:
Saber si el código hace lo que debe hacer
Quizás esta sea la razón más importante para nosotros los desarrolladores. Con las pruebas unitarias, podemos probar y demostrar que nuestro código funciona en ciertas condiciones y con diferentes parámetros. Si sigues con clases y otros métodos puede ocurrir que algunas pruebas unitarias tengan éxito primero, pero fallen cuando cambies el código. En general, la prueba unitaria manda sobre la funcionalidad, a menos que cambien las especificaciones.
Otra razón por la que las pruebas unitarias son útiles es cuando algo va mal en la aplicación. Si un usuario final avisa de un error, el área de soporte, en muchos casos por desconocimiento de la funcionalidad, resolverá que es un problema de desarrollo (código). Si no tienes pruebas unitarias que validen ese requisito probablemente es porque no estaba pedido, por tanto error de negocio o del Product Owner. Para evitar que te digan que no hiciste tu trabajo, puedes ver si el código realmente se rompe, o si es un error del usuario, o tal vez es un defecto de diseño, o las especificaciones no eran claras. Además tiene otra ventaja: el debug de la aplicación en busca del problema se simplifica muchísimo.
Trabajar en equipo
Si trabajas en un squad, o en un proyecto con varios squads concurrentes, puede ocurrir que alguien esté cambiando el código "sin validar". Hoy día todavia la mayoría de los desarrolladores suben el código "a pelo" sin ningún proceso de QA o testing y por supuesto sin dockerizar. Hace poco nos encontramos con un problema muy grave en un marketplace que da servicio a miles de compras diarias y donde la inversión anual en desarrollo son cifras que dan vértigo... El sitio web estuvo caído varias horas diarias, siempre de madrugada, por un error de codificación, una situación provocada por una integración con un sistema de analítica de ventas que provocaba escenarios que no habían sido tenidos en cuenta.. y al no existir ni una sola prueba unitaria se tardó muchísimo en generar el fix.. si cada hora eran unos cientos de ventas perdidas.. multiplicado por el mes que tardo en corregirse... un drama tanto para negocio como para el pobre desarrollador que no durmió en varios semanas hasta que encontró el problema.
Con pruebas unitarias, podrías evitar estos problemas. Si escribes código con pruebas unitarias puedes configurar CI/CD (integraciones y despliegues continuos) que detendrán la publicación del código en un entorno de producción tan pronto como haya un problema.
Otro factor clave para tener unas pruebas unitarias que "cubran" el máximo de errores es que el Product Owner haya documentado todos los casos de aceptación de cada historia. Estas son la base de las pruebas unitarias del desarrollador.
Prueba tu código sin UI
Cuando necesitas probar si algo funciona creas una prueba de concepto o PoC para abreviar. Esto significa que creas un pequeño proyecto dedicado al elemento que quieres probar. Por ejemplo, si estas en un proyecto grande o con mucho equipo trabajando no puedes esperar a que toda la release este terminada para probar tu funcionalidad. Ademas, esta forma de trabajar no rompe ni contamina el proyecto principal.
El uso de pruebas unitarias de este tipo también mejora el rendimiento y el tiempo. No es necesario el UI para validar una función o servicio que será consumido por una web, se puede testar automatizádamente mediante llamadas.
Conectar procesos - pipelines CI/CD
Las integraciones y despliegues continuos (CI/CD) son realmente importantes si trabajas en grandes proyectos. Te ayuda a codificar y desplegar mucho más rápido. Azure DevOps, Gitlab CI, y el resto de servicios cloud facilitan estos procesos para establecer, configurar y ejecutar CI/CD. Esta tarea en nuestra metodología forma parte de las responsabilidades del devop del Equipo Habilitador.
Otra gran ventaja de estos pipelines es que puedes ejecutar pruebas unitarias durante el CI/CD. En primer lugar, el pipeline descargará el código con GIT del repositorio del proyecto, lo compilará y ejecutará todas las pruebas unitarias. Si una falla, todo el proceso se detendrá y será notificado.
Esto es imprescindible si trabajas en equipo. El agile coach sufrirá menos, el Product Owner tendrá menos pánico cuando le toque presentar la release, y el cliente siempre tendrá un producto mas "resiliente".
Documentación
Las pruebas unitarias son en realidad escenarios de usuario, como hemos indicado antes la fuente de la que deben beber son las "Pruebas de Aceptación" de las "Historias de usuario", que indican lo que ocurre si se utilizan determinadas acciones, parámetros o situaciones. Por lo tanto, puede utilizar las pruebas unitarias como documentación. Te dicen qué hacen los métodos y cómo reaccionan ante ciertas situaciones o qué tipo de excepciones se lanzan.
Es la documentación que Agile propone, pues es la que aporta valor al cliente y para aquellos desarrolladores que vayan a mantener ese código.
Al empezar en un nuevo proyecto en el que vamos a participar, lo primero que hacemos es leer las pruebas unitarias. Normalmente dan una idea muy clara de lo que hace el código y cómo funciona. Como suele pasar en la mayoría de proyectos, no hay pruebas unitarias, por lo tanto intentamos convencer al equipo y al cliente dedique el tiempo necesario a crearlas, sino es prácticamente imposible abordar un trabajo con garantías.
Cobertura del código
¿Qué pasa si no se prueba todo el código? Introducimos un nuevo termino: la cobertura de código o "code coverage".
La cobertura de código es una métrica utilizada en el desarrollo de software para medir la cantidad de código que es ejecutada por las pruebas automatizadas. Indica qué porcentaje del código fuente ha sido probado durante la ejecución de las pruebas.
La cobertura de código se expresa como un porcentaje y proporciona una medida de qué tan exhaustivas son las pruebas en términos de cubrir todas las ramas y caminos posibles en el código. Un alto porcentaje de cobertura de código indica que la mayoría del código fuente ha sido ejecutado y probado, lo que sugiere que hay una mayor confianza en la calidad del software.
La mayoría de los desarrolladores dicen que el 80% de su código debe ser probado. Nosotros sabemos que menos del 90% es muy poca garantía.
Sin embargo, es importante tener en cuenta que la cobertura de código por sí sola no garantiza la ausencia de errores, ya que no evalúa la corrección funcional del software. Es una medida complementaria que ayuda a evaluar la calidad de las pruebas y proporciona información sobre la efectividad de la suite de pruebas automatizadas.
Legibilidad
Realizar las pruebas unitarias mediante especificaciones, facilitará que el código sea más fácil de entender. Las pruebas se convertirán en la documentación, y a la vez obtendrás un código mejor. Pensar y escribir primero las pruebas y luego desarrollar el código es un factor clave que educa en el análisis y mejora el performance del producto.
TDD (Test-Driven Development)
Aquí llegamos por tanto a TDD: es una metodología en la cual se escriben pruebas automatizadas antes de implementar el código, siguiendo un ciclo "Rojo-Verde-Refactor". Esto garantiza que el software cumpla con los requisitos y funcionalidades esperadas, promoviendo la calidad y mantenibilidad del código a través de iteraciones incrementales.
- En primer lugar, se escribe una prueba automatizada que inicialmente falla, representando el estado "Rojo".
- Luego, se implementa el código mínimo necesario para que la prueba pase satisfactoriamente, alcanzando el estado "Verde".
- Una vez que la prueba pasa, se puede proceder al tercer paso, "Refactor", donde se mejora y se optimiza el código sin cambiar su comportamiento externo.
Esto promueve la modularidad, la calidad y la mantenibilidad del código, ya que cualquier cambio en el código debe ser compatible con las pruebas existentes.
Conclusión
Estas son nuestras 8 razones para crear pruebas unitarias, quizás haya más o quizás no estés de acuerdo con algunas, nos encantaría conocer tu opinión. Sabemos que hay un debate sobre su aplicación en frontend y nos gustaría aprender vuestras prácticas para abordarlo.
Lo más importante es que mantengas el código "en forma", libre de bugs y fácil de leer. Aunque no esté libre de bugs al 100%, puedes intentar que lo esté al menos al 90%. Las pruebas unitarias no son malas, no llevan mucho tiempo y son muy útiles. Acostumbrarse a ellas lleva poco tiempo, y la mejora en la calidad que provocan compensa con creces cualquier esfuerzo que haya sido necesario. Nos hacen la vida mucho más fácil, no hay motivo justificado para no hacerlas.