¿Qué pruebas debemos hacerle a nuestro software y para qué?


“Los test no son opcionales”. Esto que parece a muchos una verdad de perogrullo, sigue siendo uno de los temas pendientes en el mundo del desarrollo de aplicaciones de software actual.

Sí, increíblemente aún hay muchos compañeros “del metal” que no son conscientes que programar sin pruebas no solo es como hacer acrobacias en el trapecio sin red de seguridad, sino además una fuente de errores, malas prácticas y ansiedad.

Y por ello quiero repasar los fundamentos básicos de las pruebas que debiéramos aplicar, cada uno en su necesidad, a nuestros desarrollos.

¿Por qué hacer pruebas?

Para que lo entienda hasta el más novel de los lectores, hacer pruebas es la forma de asegurarse que lo que queremos que haga nuestro programa, lo haga, y lo haga sin errores.

La construcción de software implica conocimiento, experiencia, talento, capacidad intelectual y un punto de arte. Es decir, es una labor muy difícil, y falta aún mucho para que eso cambie a mejor. De hecho, la complejidad está tendiendo al crecimiento de una forma, como en todo lo relacionado con el Front-End en Javascript, al absurdo.

Habiendo superado, hace ya décadas, la capacidad humana de aprensión y memorización; lo que implica necesariamente que los fallos y errores son inevitables si los intentamos evitar con solo nuestras capacidades humanas.

Las pruebas no son opcionales. Un software sin pruebas es una bomba a punto de estallar

¿A quien no le ha pasado que ha dejado su código medio año en un cajón, y a la vuelta de ponerse a toquetearlo tener la sensación de que lo ha escrito otra persona? No reconocemos a nuestra propia criatura.Y no hablemos cuando estamos integrados en un equipo, o recibimos el “regalito” de soportar o evolucionar un código heredado.

Por ello las pruebas son imprescindibles, ya que nos permiten garantizar que las aplicaciones cumplen las funcionalidades que se esperan de ellas y las expectativas de calidad (no solo de código); ayudando a encontrar esos errores o defectos que aún no se han descubierto; reduciendo el costo del desarrollo, el de propiedad para los usuarios; y desarrollar confianza en los clientes al evitar los molestos errores de regresión.

Eso sin hablar de la sensación de seguridad incremental que se obtiene cuanto más cerca estamos de un despliegue, ya que a más código que tenemos, más pruebas nos aseguran (en forma de una tupida malla) que todo funciona correctamente.

DevOps y la herencia de la automatización

Devops

La llegada de las metodologías Agiles, desde los años 90 del siglo pasado, fue un revulsivo en la organización y ejecución de pruebas dentro de procesos Waterfall.

En estos últimos, se podría generalizar, las pruebas principalmente eran manuales; definidas meticulosamente en voluminosos documentos de planes de pruebas; y que se realizaban solamente una vez acabada la codificación del software.

Xtreme Programming, en cambio, hizo mucho hincapié en la automatización y en el concepto de pruebas orientadas a la prevención de los finales de los años 80; marcando de esta forma, la futura filosofía Agile. Por ello, actualmente utilizamos frameworks de pruebas que permiten realizar automáticamente la mayoría de los test en todos los ámbitos de la aplicación.

Las pruebas manuales y automatizadas son complementarias

Y más cuando se pretende adoptar el concepto de Integración Continua, como parte imprescindible de DevOps, donde la propia construcción y validación de la Build por medio de todo tipo de pruebas automáticas es parte inherente del proceso.

Siendo esto aún más crítico en niveles altos de madurez en donde llegaríamos a aplicar despliegue automatizado o, incluso, continuo.

La importancia que han ido ganando las pruebas ha sido tal que la propia forma de codificar el software también ha sufrido cambios profundos. El nacimiento de TDD (desarrollo orientado a las pruebas) y su forma de supeditar el código a los test, implica que hacer software testeable es un requisito imprescindible en el código de calidad.

Y, aunque no lleguemos a utilizar esta avanzada técnica de desarrollo (que no es nada fácil), el objetivo de poder probar de forma automática nuestro código, ha reforzado prácticas tan importantes en la programación orientada a objetos como es SOLID.

Pruebas automatizadas vs manuales

Tortoise And Hare

Tenemos una primera gran división en el mundo de las pruebas entre las automatizadas y las manuales.

Como indica su nombre, las primeras dependen de una herramienta de pruebas que implica, en casi todos los casos, un lenguaje o subconjunto del lenguaje propio. Es decir, si las hago en nUnit va a ser muy complicado pasarlas a MS Test.

Las pruebas manuales requieren de interacción humana. El probador se pone en la piel del rol de usuario que se tenga que validar, y realiza todas aquellas operaciones que tenga definidas en un plan de pruebas, o le busca “las cosquillas” al sistema para llegar allí donde ningún “luser” ha llegado anteriormente...

Como ves, ambos tipos de ejecución de pruebas son complementarios e importantes para garantizar un software de calidad.

La automatización es rápida y puede probar muchas variaciones sutiles en los datos; también puede repetir fácilmente las pruebas a medida que el software evoluciona; y debido a que es ejecutado por el sistema, se evita la fatiga y los errores que a veces acompañan a las tareas repetitivas.

En cambio, aunque las pruebas manuales generalmente tardan más en ejecutarse (ya que las realiza una persona), a menudo requieren mucho menos tiempo de configuración. Es una buena opción para las pruebas que solo deben ejecutarse ocasionalmente, o en los casos en que el costo/tiempo de la configuración de automatización supere los beneficios.

Un universo de tipos de pruebas

Siguiendo los pasos de la complejidad inherente de nuestra industria, las pruebas también sufren de una miríada inacabable de tipos, versiones, evoluciones y clases. Pero centrémonos en las más importantes e imprescindibles, según cada caso y contexto.

Prueba unitaria: las pruebas unitarias son pruebas automatizadas que verifican la funcionalidad en el componente, clase, método o nivel de propiedad.

Tddmantra

El objetivo principal de las pruebas unitarias es tomar la pieza más pequeña de software comprobable en la aplicación, aislarla del resto del código y determinar si se comporta exactamente como esperamos. Cada unidad se prueba por separado antes de integrarlas en los componentes para probar las interfaces entre las unidades.

Las pruebas unitarias deben escribirse antes (o muy poco después) de escribir un método; siendo los desarrolladores que crean la clase o el método, quienes diseñan la prueba. 

Así, conseguimos mantener el foco en lo que debe hacer el código, y se convierte en una poderosa herramienta para aplicar KISS, JIT, y mantener el foco en lo que tiene que hacer en vez de en el cómo, evitando introducir complejidad sin valor.

Pruebas de integración: desde una perspectiva de prueba, las unidades individuales se integran juntas para formar componentes más grandes. En su forma más simple, dos unidades que ya han sido probadas se combinan en un componente integrado y se prueba la interfaz entre ellas.

Las pruebas de integración – o de componentes - identifican problemas que ocurren cuando las unidades se combinan. Los nuevos errores que surgen probablemente estén relacionados con la interfaz entre las unidades en lugar de dentro de las propias unidades; simplificando la tarea de encontrar y corregir los defectos.

Pruebas de regresión: cada vez que se realizan cambios en un proyecto, es posible que el código existente ya no funcione correctamente o que se presenten errores no descubiertos previamente. Este tipo de error se llama regresión.

Para detectar estos defectos, todo el proyecto debe someterse a una regresión: una nueva prueba completa de un programa modificado, en lugar de una prueba de solo las unidades modificadas, para garantizar que no se hayan introducido errores con las modificaciones.

Como se puede deducir, este tipo de pruebas debe ser automatizado porque puede estar compuesto por decenas o miles de pruebas unitarias, de integración o más.

Una versión menos costosa, podría ser construir pruebas que repliquen las acciones que provocaron la regresión, y comprueben que han sido corregidos al no volver a sucederse los errores; además de añadir los test unitarios que aseguren que el código que ha corregido la regresión funciona correctamente.

Pruebas de funcionalidad: pruebas automatizadas o manuales que prueban las funcionalidades de la aplicación o módulo construidos desde el punto de vista del usuario final, con sus diferentes roles, para validar que el software hace lo que debe y, sobre todo, lo que se ha especificado.

En su versión automática son pruebas que se automatizan para "ahorrar tiempo de pruebas". A partir de los casos de prueba de las pruebas manuales, se automatizan los casos de prueba para que se repitan en las ejecuciones. Esos casos suelen ser los más importantes (happy flow) de los módulos o procesos de negocio "vitales" de la aplicación. Es decir, los procesos que siempre tienen que funcionar y que bajo ningún concepto pueden fallar. El objetivo de las pruebas funcionales automáticas es comprobar que no haya regresiones.

Las pruebas obligan a hacer un código desacoplado y promueven la calidad

En el caso de las manuales, las ejecuta un tester como si fuese un usuario, pero siguiendo una serie de pasos establecidos en el plan de pruebas, diseñado en el análisis de los requisitos para garantizar que hace lo que debe (casos positivos), que no falla (casos negativos) y que es lo que se ha solicitado. 

El tester realizará las acciones indicadas en cada paso del caso de prueba comprobando que se cumple el resultado esperado. Si el resultado es distinto, se reportará un defecto con todo detalle: descripción, datos utilizados, capturas de pantalla, etc., para facilitar la solución.

El mayor problema con el que se enfrentan las pruebas funcionales para ser automatizadas es su fragilidad. Cada prueba testea miles de líneas de código, centenares de integraciones en todos los tiers, y una interfaz de usuario cambiante. Llegando a no ser sostenible el conjunto de pruebas en relación con su definición, coste y mantenimiento.

Llevando las aplicaciones a su límite

Test De Carga

Ya tenemos probada y desplegada nuestra aplicación. Ahora viene la parte de operaciones y también se debe probar de forma automática las capacidades y debilidades del software y de la plataforma sobre la que corre (infraestructura y dependencias), llevándola al límite, para comprobar su disponibilidad, estabilidad y resiliencia.

Pruebas de estrés: las pruebas a pequeña escala, como un usuario único que ejecuta una aplicación web o una base de datos con solo un puñado de registros, pueden no revelar problemas que suceden cuando la aplicación se usa en condiciones "reales".

La prueba de estrés empuja los límites funcionales de un sistema. Se realiza sometiendo el sistema a condiciones extremas, como volúmenes de datos máximos o una gran cantidad de usuarios simultáneos.

También se utilizan para, llevado el sistema al colapso o degradación, comprobar su funcionamiento continuado por encima de su límite y, una vez liberado de la carga, evaluar su capacidad de resiliencia volviendo a su estado óptimo de funcionamiento.

Lleva la aplicación al límite, busca que rompa, y luego observa como se reconstruye

Y en la actualidad cada vez más se utilizan las capacidades de la Cloud tanto para crear un gran número de usuarios, distribuir las peticiones en todo el mundo, como para obtener los recursos de procesamiento, memoria y almacenamiento necesarios en operaciones de este calibre.

Prueba de rendimiento: determinan la capacidad de respuesta, el rendimiento, la confiabilidad y/o la escalabilidad de un sistema bajo una carga de trabajo determinada.

En aplicaciones web, las pruebas de rendimiento a menudo están estrechamente relacionadas con las pruebas de estrés, la medición del retraso y la capacidad de respuesta bajo una carga pesada.

En otras aplicaciones (escritorio y aplicaciones móviles, por ejemplo), las pruebas de rendimiento miden la velocidad y la utilización de recursos, como el espacio en disco y la memoria.

Si almacenamos todos los resultados de las pruebas de rendimiento durante un plazo de tiempo, podemos conocer el estado de salud de la aplicación, pudiendo obtener tendencias y previsiones de funcionamiento; y optimizando cada despliegue según el rendimiento necesario en cada caso.

Pruebas de seguridad: validan los servicios de seguridad de una aplicación e identifican posibles fallos y debilidades.

Muchos proyectos utilizan un enfoque de caja negra para las pruebas de seguridad, lo que permite a los expertos, sin conocimiento del software, probar la aplicación en busca de agujeros, fallos, exploit y debilidades.

Y esto es solo el principio

Funcionales, Usabilidad, Exploratorios, Aceptación, Infraestructura, etc. El universo de las pruebas es inmenso, siendo una de las ramas de la mal llamada informática, que requiere de una especialización especifica.

Y más cuando la llegada de la Infraestructura como Código, automatiza y mejora procesos conocidos como el Estado de Configuración Deseada, añadiendo capacidades de lógica de negocio en la construcción, mantenimiento y pruebas a nivel de plataforma.

No lo olvides nunca: las pruebas no son opcionales.

Comentarios

Entradas populares