archivo

Archivo de la etiqueta: pruebas unitarias

Sistemas altamente acoplados, con clases y métodos kilométricos, con código que resulta complicado de entender son ejemplos en donde realmente se programa con miedo una vez que se comprueba que tocarlo supone implicaciones hasta en las partes más insospechadas de la aplicación.

Si el sistema además es crítico el miedo pasa a convertirse en pánico.

Tal vez las palabras miedo o pánico sean demasiado exageradas (y no es nada positivo trabajar de esa manera) pero por lo menos el programador debe sentir respeto y estar alerta cuando realiza modificaciones en un sistema de este tipo. El exceso de confianza no es bueno y más en un caso como este.

Hay que evaluar si el cambio que se va a hacer en el sistema es algo puntual o si se sabe que van a existir una serie de cambios a corto y medio plazo, así como la magnitud de los mismos y la magnitud del sistema. El esfuerzo que supone realizar estos cambios (mucho mayor que en un sistema con una deuda técnica ajustada a sus características), el esfuerzo en testing y el riesgo que tiene liberar nuevas versiones de estos sistemas debe ser analizado antes de afrontar la mejor estrategia para el cambio.

En condiciones como esta, cambios que sean fruto de un capricho no deben ser contemplados y cambios muy relevantes que supongan una modificación sustancial del sistema podrían dar lugar a que se tome la decisión de desarrollar una nueva aplicación.

El programador por su parte puede tomar sus precauciones: testing unitario, automatización de determinado testing de mayor nivel, etc…

Por otro lado, el paso a producción de nuevas versiones de sistemas de estas características no debe tratarse a la ligera y esperar cruzando los dedos a que no pase nada.

El testing debe ser realizado en profundidad y desde el punto de vista funcional debería ser la combinación de casos de prueba y testing exploratorio.

Hay muchos desarrolladores que aplican TDD en sus desarrollos o, al menos, desarrollan pruebas unitarias para el software que están implementando.

Sobre ambos aspectos TDD (las pruebas unitarias se construyen antes) o las pruebas unitarias en general hay defensores y detractores y como suele suceder en tantos aspectos, no hay una verdad absoluta, sino que la aplicación o no de estas estrategias dependen del contexto en que se realiza el desarrollo, del tipo de sistema y del equipo de programadores.

Pese a que hablo de contexto incluso en situaciones favorables existen reticencias por muchos desarrolladores que lo consideran una pérdida de tiempo o una carga que llevar encima desde que se implementan, sin embargo quienes lo utilizan y lo consideran como una actividad más dentro de la programación lo suelen aplicar prácticamente en cualquier contexto.

No voy a defender a unos o a otros, cada cual tiene su criterio. Desde mi punto de vista la posibilidad de realizar testing unitario y su automatización supuso en su día una revolución en el desarrollo de software y en el mantenimiento de sistemas porque todo lo que sea establecer controles que permitan construir sistemas más robustos y entregar productos con menos errores debería ser bienvenido

Martin Fowler como buen defensor de estas prácticas realizó la siguiente reflexión sobre los frameworks de pruebas unitarias: “Nunca en el campo de desarrollo de software tantos le debieron tanto a tan pocas líneas de código”.

La resistencia vista desde distintas perspectivas, como por ejemplo el tiempo de más que tarda en pasarse a producción un producto o todas aquellas dificultades u obstáculos extras en el proceso de desarrollo, que puede ir desde la deuda técnica hasta la falta de colaboración por parte de los usuarios, dificulta el establecimiento de ciclos cortos en un enfoque iterativo e incremental.

Esto al final es como la pescadilla que se muerde la cola, ya que cuanto más largo sea el ciclo, mayor número de funcionalidades se incorporarán o se modificarán y alimentarán, por ejemplo un paso a producción más largo.

No entro a valorar en término cuantitativo qué es un ciclo corto o qué es un ciclo largo porque opiniones hay para todos los gustos, como también las hay para determinar si todas las iteraciones dentro de un proyecto deben tener la misma duración o no.

Si se quieren ciclos cortos y que estos sean efectivos (que sean resolutivos de cara a las expectativas del usuario) hay que disminuir la resistencia y eso no es una cuestión simple, sobre todo si se parte de la base de que se intenta aplicar este enfoque de desarrollo en proyectos que se encuentran en una etapa avanzada o que se encuentran en fase de mantenimiento.

Disminuir la resistencia debe ser siempre un objetivo a tener presente, sobre todo condiciona de manera grave el proceso de desarrollo. Si es importante en todos los casos, lo es todavía más cuando se pretende liberar nuevas versiones del producto cada poco tiempo.

Disminuir la resistencia implicará actuaciones no funcionales (y eso será necesario tenerlo en cuenta y explicarlo muy bien al usuario), como por ejemplo automatizar en lo posible la instalación de la aplicación en los diferentes entornos, la automatización en la ejecución de las pruebas partiendo desde las pruebas unitarias y subiendo cuantos niveles sean precisos, la realización de pruebas manuales en aquellos casos donde no llegue o no sea rentable automatización, la refactorización, etc…

También obligará a realizar diversas tareas de gestión para intentar agilizar los procesos y las actividades de las diferentes personas y roles que participan en el proceso de desarrollo y de implantación y aceptación del sistema.

La programación defensiva es el resultado de la aplicación de una serie de técnicas que tienen como objetivo minimizar el número de errores que puede presentar un sistema de información, así como presentar una arquitectura y código que además de servir de soporte al objetivo anterior permita realizar un mantenimiento del sistema de la forma más limpia posible y minimizando o eliminando posibles efectos colaterales.

Aplicar esta técnica requiere incrementar notablemente el número de controles y comprobaciones que se realizan desde el código ya que se querrán dar respuesta al mayor número de casuísticas posibles, así como ser muy metódico en la aplicación de pruebas unitarias, pruebas de integración y pruebas de sistema desde etapas muy tempranas del desarrollo.

Todo lo anterior sin olvidarnos de las correspondientes pruebas de seguridad y pruebas de rendimiento.

Este testing será realizado tanto por el equipo de proyecto como por uno o varios equipos externos los cuales inclusos pueden estar especializados en un área concreta de testing.

De igual forma se requerirá que las especificaciones estén lo más cerradas y validadas posibles por el cliente o por el usuario, en este caso, el margen de error y la aproximación mediante la combinación feedback e iteraciones es mucho menor, por lo menos a nivel del producto en producción, donde deberá entrar con plenas garantías de acabado y funcionamiento.

También, el uso de componentes externos o de la delegación de competencias en terceros sistemas requerirá tener de los mismos unas garantías similares a la del sistema que se está desarrollando (toda cadena se rompe por el eslabón más débil).

En sistemas considerados críticos, donde se ponga en juego la vida o la integridad de las personas, así como aquellos que pueden afectar de manera sensible al funcionamiento de una organización y a su economía, la aplicación de este tipo de estrategias cobra especial sentido.

Es importante tener en cuenta que no solo es cuestión de técnicas, también es cuestión de un cambio de actitud cuando nos enfrentamos a este tipo de desarrollos, por eso es importante que un porcentaje elevado de personas que participan en estos proyectos tengan experiencia en los mismos, así como que ocupen todos los puestos críticos del proyecto.

Se han realizado estudios que indican que más del 50% de la corrección de errores introducen errores adicionales en el código.

Por este motivo resulta fundamental la aplicación de buenas prácticas en la codificación, la utilización de una buena arquitectura y framework, la aplicación de pruebas unitarias y de integración continua y la realización de nuevas pruebas una vez que se haya integrado un componente corregido.

Por esto es tan importante que el proceso de desarrollo no sea algo improvisado sino algo reglado, donde cada miembro del equipo de proyecto conozca qué es lo que tiene que hacer y qué técnicas tiene que utilizar para programar.

Barry Boehm es una buena referencia a la hora de considerar como adecuados determinados indicadores obtenidos en sus investigaciones y proyectos, no en vano siempre ha sido considerado como una de las referencias en el campo de la ingeniería del software, metodologías de desarrollo, estimación de costes y obtención y determinación de métricas relacionadas con el proceso de desarrollo.

Independientemente del indicador que cita Boehm y que indicaré al final de artículo, la propia experiencia nos dice que el coste de corrección de un error se dispara conforme nos alejamos del momento en que se ha producido. Esto es así porque un error arrastra tras de sí otros trabajos que correctos o no, se verán alterados por la corrección del mismo. Por ejemplo, una mala especificación de un requisito que llega a producción tendrá repercusión en el producto final, pero también su corrección requerirá un esfuerzo considerable por los diferentes artefactos que se tienen que modificar o incluso rehacer.

La aparición de los modelos de testing en V o de testing en W son una respuesta posible ante esos problemas, como también lo son la aplicación de otras técnicas en el proceso de construcción, como por ejemplo pruebas unitarias, integración continua, etc…

No solo afectó esta circunstancia a la aparición de modelos o técnicas de testing, sino que las metodologías de una u otra manera quedan impregnadas por la necesidad de detectar y corregir cuanto antes los errores (de hecho el propio testing en V no es más que una variante del ciclo de vida clásico o en cascada) como por ejemplo podemos ver en RUP o por la propia dinámica de desarrollo siguiendo metodologías ágiles.

Sobre la detección tardía de errores y sus implicaciones en los costes de un proyecto, Barry Boehm comenta lo siguiente (en relación a errores en etapas muy tempranas en el desarrollo, como lo son la obtención de especificaciones o requisitos): “Corrige los errores de especificación cuanto antes. Corregirlos después, costará: un 500% más en la etapa de diseño, un 1000% en la etapa de codificación, un 2000% en la etapa de testing unitario y un 20000% más en la entrega”.

Se trata de una técnica de testing de caja negra, en la que se definen diferentes hitos a ser probados, que pueden ir desde elementos de carácter unitario hasta elementos cuya funcionalidad es proporcionada por varias capas de la arquitectura de la aplicación, como por ejemplo, los campos de un formulario y a los que se les proporciona un juego de entrada de datos aleatorio que puede tener acotado o no el patrón de generación de los mismos.

En función del nivel de acotación (o de inteligencia) del juego de entrada de datos aleatorio generado, se distinguen diferentes tipos de Monkey testing: Dumb Monkey Testing, Brilliant Monkey Testing y Smart Monkey Testing.