Hablemos sobre la Calidad
Cuando hablamos de «calidad», para cada persona es distinto según lo que busca en el producto o servicio. Para algunos, es que el producto o servicio sea rápido; para otros, es que cumpla su objetivo; y para otros, es que sea simple e intuitivo. Si lo resumimos, nuestro objetivo es cumplir las expectativas de nuestros usuarios.
¿Cómo cumplir esas expectativas?
Para lograr cumplir esas expectativas que tienen nuestros usuarios, debemos entender qué es lo que buscan, en qué situaciones usarán nuestro producto, el grupo etario y otros aspectos relevantes. Tomando esta información como base, es que tenemos que diseñar el producto para luego desarrollarlo.

Nos enfocaremos en la etapa de desarrollo. Aquí el equipo de desarrolladores cumple un rol fundamental en la calidad del software, ya que son los encargados de implementar la solución diseñada. Y es que una funcionalidad no es más que un conjunto de métodos distribuidos en múltiples piezas que, en su conjunto, permiten resolver la problemática. Estos métodos reciben elementos de entrada llamados argumentos o parámetros, conocidos coloquialmente, que, según las reglas de negocio, producen un resultado que será usado potencialmente por otro método.
Es aquí, en esta colaboración entre métodos que reciben y resuelven, donde es importante asegurarnos de que estos componentes respondan correctamente ante diferentes argumentos. Si alguno de ellos resuelve de una forma inesperada, puede producir comportamientos inesperados y no resolver adecuadamente la solicitud del usuario, lo que provoca una mala experiencia. Esto puede llevar a que el usuario desconfíe del producto o servicio, dependiendo de la gravedad del problema.
Calidad en la realidad
Imaginemos que tenemos una aplicación que permite al usuario comprar en diferentes comercios usando su celular sin sacar su tarjeta: las famosas billeteras virtuales. El usuario realiza las compras del mes en el comercio de Pedro y, al momento de pagar, la aplicación le indica que falló su pago, pero le llega la notificación de su banco de que se hizo un cargo por un monto a su cuenta.
El mensaje es confuso para el usuario, ya que verá que el monto no está disponible en su cuenta. Además, el cajero no recibió la autorización de la compra, por lo que no podrá llevarse los productos.
Aquí pasara lo siguiente:
- El usuario se preguntara que paso con su pago, ya que, no lo tiene en su cuenta pero tampoco se confirmo (visualmente) su pago.
- No podrá sacar sus productos.
- Tendrá que comunicarse con soporte para revisar el caso y toma tiempo.
- Perdida de tiempo para el usuario.
- Retraso en el flujo normal de la caja.
Esto se traducirá a una pésima experiencia tanto para el comercio asociado como para el usuario. Esté ultimo puede que no utilice más la billetera perdiendo su fidelización y el comercio si es recurrente puede tomar la decisión de no operar más con el producto. En resumen una gran perdida potencial.
Que podemos hacer a nivel tecnico
A nivel de código, el equipo de desarrollo tiene una gran responsabilidad de asegurar que sus desarrollos cumplan su propósito y programar los diferentes comportamientos límites que se puedan dar según los argumentos que les puedan llegar.
En este contexto es cuando debemos poner a prueba nuestro código y para ello tenemos diferentes técnicas que nos permiten probar desde lo más mínimo mediante pruebas unitarias, hasta la funcionalidad completa mediante pruebas end-to-end.

- Pruebas Unitarias.
- Pruebas de Integración.
- Pruebas end-to-end.
La base de la pirámide corresponde a las pruebas básicas que todo proyecto debiese tener, seguido de las pruebas de integración, las cuales ayudarán mucho al integrar nuevas dependencias o migrar servicios. Finalmente, están las pruebas e2e que permiten validar que todo el flujo esté funcionando correctamente.
Pruebas unitarias
Como se mencionó, estas pruebas son las más básicas y cualquier desarrollador puede implementarlas. La mayoría de los frameworks de desarrollo cuentan con bibliotecas integradas que permiten probar el código, y su finalidad es comprobar la unidad de código.

Las pruebas unitarias de código implican probar el comportamiento de solo el método, sin ningún tipo de dependencia ni integraciones reales con almacenamiento de datos y/o servicios.
Pero ¿cómo es eso? ¿Entonces, cómo puedo probar el método?
¡Muy buena pregunta! Pues la solución es emular, o como se le conoce, implementar un Mock de esa dependencia. De esta forma, simularemos las distintas respuestas que puede devolver esa dependencia y, según esa respuesta, validaremos el resultado esperado del método probado.
Muchos desarrolladores, incluyéndome a mí, cometemos o cometimos el error de levantar una base de datos en memoria o intentar llamar a un servicio. Esto se conoce como Test de Integración y es distinto a los Test Unitarios.
En esta estrategia de prueba, lo que importa de la dependencia no es su comportamiento, sino la posible respuesta que esta puede dar y cómo esto afecta el flujo que se desarrolló.
Otro error que se comete es probar solo el flujo feliz. Y si bien debe estar claramente dentro de las pruebas, lo que se debe hacer es tener la intención de romper el flujo introduciendo valores no esperados como argumentos y también emulando diferentes respuestas de las dependencias del flujo. En palabras simples, maltrata tu código, sin pena, sin miedo. Lo peor que puede pasar es que necesites manejar ese caso, pero te aseguras de haberlo cubierto, así cuando ocurra, el método responderá de una forma conocida.
Teoría v/s la realidad
Sin embargo, debemos enfrentar la realidad. Tal como mencionó Manuel en su Recetario Construyendo MVPs: Acelerando Ideas con Firebase y Angular, el desarrollo de productos es tan rápido que se necesita lanzar un producto mínimo viable en el menor tiempo posible. Conscientes -o forzadas por la presión-, las células de trabajo toman la decisión de posponer los test unitarios para más adelante. Esta deuda es difícil de pagar debido a la constante entrega de trabajo que implica trabajar con MVPs.
Debido a lo difícil, aquí te entregamos algunos consejos para tomar esta deuda y pagarla paulatinamente.
- Comienza implementando pruebas unitarias a métodos pequeños. No es necesario fijarse una cobertura del 100% del código. Empieza poco a poco y forma el hábito de probar tu código, seleccionando métodos pequeños y simples para implementar sus pruebas.
- Conversen en equipo cómo pagar esta deuda. Pueden decidir dejar como DOD (Definición of Done) un porcentaje de cobertura mínima, que puede ser fácilmente alcanzado sin afectar la entrega.
- Es importante reconocer la importancia de implementar pruebas unitarias en las diferentes áreas. Tu habilidad para comunicar esto de manera transparente a otras áreas es muy importante. La forma más efectiva de lograrlo es relacionar el potencial problema con el aspecto económico, es decir, cuánto puede costar no tener cubierto cierto flujo.
- Coordina con DevOps dejar en el pipeline del repositorio una cobertura mínima para habilitar el merge o bloquear el paso a la rama objetivo. ¡Pero ojo! Esto puede impactar en el delivery. Aquí es importante tener una radiografía del estado de los tests unitarios en el proyecto y plantearse un porcentaje realista. ¡No se coloquen la soga al cuello solos!
Resumen
Es muy probable que las operaciones puedan fallar por diferentes motivos. Lo importante es que nuestro software sea lo suficientemente resiliente para lograr un correcto tratamiento de la información.
Las pruebas unitarias son una estrategia más que se suma al esfuerzo de asegurar la calidad de nuestro software. No es lo único y debe complementarse con otras estrategias de pruebas, como las pruebas de integración y las pruebas end-to-end, así como con procesos de certificación realizados por un área especializada en aseguramiento de la calidad.
En los próximos recetarios abordaremos las pruebas de integración y cómo eso nos ayuda a asegurar la calidad de nuestro software.

