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

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

Sí, impresionantemente todavía existen 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 más bien además de esto una fuente de fallos, malas prácticas y ansiedad.

Y por esta razón deseo comprobar los fundamentos básicos de las pruebas que debiésemos aplicar, cada uno de ellos en su necesidad, a nuestros desarrollos.

¿Por qué razón hacer pruebas?

A fin de que lo comprenda hasta el más novel de los lectores, hacer pruebas es la manera de asegurarse que lo que deseamos que haga nuestro programa, lo haga, y lo haga sin fallos.

La construcción de software implica conocimiento, experiencia, talento, capacidad intelectual y un punto de arte. O sea, es una tarea realmente difícil, y falta todavía mucho a fin de que eso cambie a mejor. En verdad, la dificultad está tendiendo al desarrollo de una manera, como en todo lo relacionado con el Front-End en Javascript, al absurdo.

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

Las pruebas no son opcionales. Un software sin pruebas es una bomba a puntito de reventar

¿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 manosearlo tener la sensación de que lo ha escrito otra persona? No reconocemos a nuestra criatura.Y no charlemos cuando estamos integrados en un equipo, o bien recibimos el “regalito” de aguantar o bien evolucionar un código heredado.

Por esta razón las pruebas son indispensables, en tanto que nos dejan asegurar que las aplicaciones cumplen las funcionalidades que se aguardan de ellas y las esperanzas de calidad (no solo de código); ayudando a hallar esos fallos o bien defectos que todavía no se han descubierto; reduciendo el costo del desarrollo, el de propiedad para los usuarios; y desarrollar confianza en los clientes del servicio al eludir los molestos fallos de regresión.

Eso sin charlar de la sensación de seguridad incremental que se consigue cuanto más cerca estamos de un despliegue, puesto que a más código que tenemos, más pruebas nos aseguran (en forma de una tupida malla) que todo marcha apropiadamente.

DevOps y la herencia de la automatización

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

En estos últimos, se podría generalizar, las pruebas eminentemente eran manuales; definidas minuciosamente en grandes documentos de planes de pruebas; y que se efectuaban únicamente una vez acabada la codificación del software.

Xtreme Programming, en cambio, hizo mucho hincapié en la automatización y en el término de pruebas orientadas a la prevención de los finales de los años 80; marcando de este modo, la futura filosofía Agile. Por esta razón, en la actualidad empleamos frameworks de pruebas que dejan efectuar de forma automática la mayor parte de los test en todos y cada uno de los campos de la aplicación.

Las pruebas manuales y automatizadas son complementarias

Y más cuando se pretende adoptar el término de Integración Continua, como parte indispensable de DevOps, donde la propia construcción y validación de la Build a través de todo género de pruebas automáticas es parte inherente del proceso.

Siendo esto todavía más crítico en niveles altos de madurez en donde llegaríamos a aplicar despliegue automatizado o bien, aun, progresivo.

La relevancia que han ido ganando las pruebas ha sido tal que la propia forma de codificar el software asimismo 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 indispensable en el código de calidad.

Y, si bien no lleguemos a usar esta avanzada técnica de desarrollo (que no es nada simple), la meta de poder probar de manera automática nuestro código, ha reforzado prácticas tan esenciales en la programación orientada a objetos como es SOLID.

Pruebas automatizadas vs manuales

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

Como señala su nombre, las primeras dependen de una herramienta de pruebas que implica, en prácticamente todos los casos, un lenguaje o bien subconjunto del lenguaje propio. O sea, si las hago en nUnit será muy complicado pasarlas a MS Test.

Las pruebas manuales precisan de interacción humana. El probador se pone en la piel del rol de usuario que se deba validar, y efectúa todas y cada una aquellas operaciones que tenga definidas en un plan de pruebas, o bien le busca “las cosquillas” al sistema para llegar allá donde ningún “luser” ha llegado previamente…

Como ves, los dos géneros de ejecución de pruebas son complementarios y también esenciales para asegurar un software de calidad.

La automatización es veloz y puede probar muchas alteraciones sutiles en los datos; asimismo puede reiterar de forma fácil las pruebas conforme el software evoluciona; y debido a que es ejecutado por el sistema, se evita la fatiga y los fallos que en ocasiones acompañan a las labores repetitivas.

En cambio, si bien las pruebas manuales en general tardan más en ejecutarse (en tanto que las efectúa una persona), con frecuencia requieren mucho menos tiempo de configuración. Es buena opción para las pruebas que solo deben ejecutarse esporádicamente, o bien en los casos en que el costo/tiempo de la configuración de automatización supere las ventajas.

Un cosmos de géneros de pruebas

Siguiendo los pasos de la dificultad inherente de nuestra industria, las pruebas asimismo padecen de una miríada interminable de tipos, versiones, evoluciones y clases. Mas centrémonos en las más esenciales y también indispensables, conforme cada caso y contexto.

Prueba unitaria: las pruebas unitarias son pruebas automatizadas que comprueban la funcionalidad en el componente, clase, procedimiento o bien nivel de propiedad.

El principal objetivo 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 precisamente como aguardamos.
Cada unidad se prueba separadamente ya antes de integrarlas en los componentes para probar las interfaces entre las unidades.

Las pruebas unitarias deben escribirse ya antes (o bien poquísimo después) de redactar un método; siendo los desarrolladores que crean la clase o bien el procedimiento, quienes diseñan la prueba.

De esta manera, logramos sostener el foco en lo que debe hacer el código, y se transforma en una poderosa herramienta para aplicar KISS, JIT, y sostener el foco en lo que debe hacer en lugar de en el de qué forma, eludiendo introducir dificultad 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, 2 unidades que han sido probadas se combinan en un componente integrado y se prueba la interfaz entre ellas.

Las pruebas de integración – o bien de componentes – identifican inconvenientes que ocurren cuando las unidades se combinan. Los nuevos fallos que brotan seguramente estén relacionados con la interfaz entre las unidades en vez de en las propias unidades; facilitando la labor de hallar y corregir los defectos.

Pruebas de regresión: toda vez que se efectúan cambios en un proyecto, posiblemente el código existente ya no funcione adecuadamente o bien que se presenten fallos no descubiertos anteriormente. Este género de fallo lleva por nombre regresión.

Para advertir estos defectos, todo el proyecto debe someterse a una regresión: una nueva prueba completa de un programa cambiado, en vez de una prueba de solo las unidades cambiadas, para asegurar que no se hayan introducido fallos con las modificaciones.

Como se puede inferir, esta clase de pruebas ha de ser automatizado por el hecho de que puede estar compuesto por decenas o bien miles y miles de pruebas unitarias, de integración o bien más.

Una versión menos costosa, podría ser edificar pruebas que contesten las acciones que provocaron la regresión, y verifiquen que han sido corregidos al no regresar a sucederse los errores; aparte de agregar los test unitarios que aseguren que el código que ha corregido la regresión marcha apropiadamente.

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

En su versión automática son pruebas que se automatizan para ahorrar tiempo de pruebas. Desde los casos de prueba de las pruebas manuales, se automatizan los casos de prueba a fin de que se repitan en las ejecuciones. Esos casos acostumbran a ser los más esenciales (happy flow) de los módulos o bien procesos de negocio vitales de la aplicación. O sea, los procesos que siempre y en todo momento deben marchar y que bajo concepto alguno pueden fallar. La meta de las pruebas funcionales automáticas es revisar que no haya regresiones.

Las pruebas fuerzan a hacer un código desajustado y fomentan la calidad

En el caso de las manuales, las ejecuta un tester tal y como si fuera un usuario, mas siguiendo una serie de pasos establecidos en el plan de pruebas, desarrollado en el análisis de los requisitos para asegurar que hace lo que debe (casos positivos), que no falla (casos negativos) y que es lo que se ha pedido.

El tester efectuará las acciones indicadas en todos y cada paso del caso de prueba verificando que se cumple el resultado aguardado. Si el resultado es diferente, se reportará un defecto con todo detalle: descripción, datos usados, capturas, etcétera, para facilitar la solución.

El mayor inconveniente con el que se encaran las pruebas funcionales para ser automatizadas es su debilidad. Cada prueba testa miles y miles de líneas de código, cientos de integraciones en todos y cada uno de los tiers, y una interfaz de usuario alterable. Llegando a no ser sustentable el conjunto de pruebas con relación a su definición, costo y mantenimiento.

Llevando las aplicaciones a su límite

Ya tenemos probada y desplegada nuestra aplicación. Ahora viene la una parte de operaciones y asimismo se debe probar de manera automática las capacidades y debilidades del software y de la plataforma sobre la que corre (infraestructura y dependencias), llevándola al máximo, para revisar 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 bien una base de datos con solo un puñado de registros, pueden no descubrir inconvenientes que suceden cuando la aplicación se emplea en condiciones reales.

La prueba de agobio empuja los límites funcionales de un sistema. Se efectúa sometiendo el sistema a condiciones extremas, como volúmenes de datos máximos o bien un sinnúmero de usuarios simultáneos.

Asimismo se emplean para, llevado el sistema al colapso o bien humillación, revisar su funcionamiento continuado sobre su límite y, una vez liberado de la carga, valorar su capacidad de resiliencia volviendo a su estado inmejorable de funcionamiento.

Lleva la aplicación al máximo, busca que rompa, y después observa como se reconstruye

Y hoy en día poco a poco más se usan las capacidades de la Cloud tanto para crear un elevado número de usuarios, repartir las solicitudes en el mundo entero, para conseguir los recursos de procesamiento, memoria y almacenaje precisos en operaciones de este calibre.

Prueba de rendimiento: determinan la capacidad de contestación, el desempeño, la fiabilidad y/o la escalabilidad de un sistema bajo una carga de trabajo determinada.

En aplicaciones web, las pruebas de desempeño con frecuencia están de manera estrecha relacionadas con las pruebas de agobio, la medición del retraso y la capacidad de contestación bajo una carga pesada.

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

Si guardamos todos y cada uno de los resultados de las pruebas de desempeño a lo largo de un plazo de tiempo, podemos conocer el