archivo

Archivo de la etiqueta: pruebas unitarias

El funcionamiento de un producto software se define mediante la integración de sus componentes internos y la integración con componentes externos.

En un ambiente de arquitectura del software donde la modularidad y la delegación de funcionalidades predomina sobre otros paradigmas y en donde los equipos de trabajo pueden estar en diferentes localizaciones geográficas, un gobierno adecuado de integración de componentes resulta fundamental para detectar cualquier anomalía lo antes posible.

CMMI no es ajeno a esta circunstancia y determina en su nivel 3 la necesidad de establecer una estrategia global. Perfectamente podría haber incluido a nivel de proyecto este proceso en el nivel 2, sin embargo la orientación del mismo a la administración o gestión de cómo se realiza al desarrollo y no a la definición de métodos de trabajo, es lo que ha hecho que este proceso al igual que otros pase a engrosar este nivel.

Por tanto, en este proceso se establece una metodología a nivel de organización en cuanto a la integración de componentes. Como en el resto de procesos, la definición de una estrategia global no consiste en la definición de un modelo único sino que permite escenarios en función de la tipología y características del proyecto.

En la descripción de este proceso se encuentra por un lado los pasos a seguir para preparar la integración (determinar la secuencia de integración, adecuar un entorno de integración y decidir cómo y a través de qué se realiza el proceso de integración), para garantizar la compatibilidad de las interfaces (mejor si se detectan problemas de este tipo antes de realizar la integración) y para realizar el propio proceso de integración (condiciones que tienen que cumplir los componentes con carácter previo a la integración, cómo realizar la misma y cómo evaluar el resultado final).

La implantación de este área de proceso no debe resultar complicada para la mayoría de las empresas de desarrollo de software ya que con carácter previo han tenido que gestionar de manera adecuada la gestión de la configuración (esencial para restar complejidad a este proceso) y porque generalmente cuentan con soluciones de integración de componentes y en muchos casos se aplican técnicas de integración continua y en menos, pero afortunadamente cada vez más, pruebas unitarias y técnicas rápidas de verificación estructural y funcional como smoke testing.

La definición de smoke testing es sencilla ya que son pruebas rápidas que se realizan sobre aspectos funcionales del software para comprobar a alto nivel si el mismo tiene el comportamiento esperado. Ahora bien, la forma y el momento de aplicación puede variar.

Lo más normal es que se aplique durante el proceso de construcción como un paso más allá a la integración continua y a las pruebas unitarias y pueden ser automáticas, manuales o mixtas, ya que lo que se pretende comprobar es si determinadas funcionalidades tienen el comportamiento adecuado.

Hay que tener en cuenta que el smoke testing más que descubrir errores concretos lo que pretende es verificar que a nivel funcional no se están produciendo incidencias graves.

En otros contextos se aplica smoke testing con caracter previo a pruebas funcionales más exhaustivas con el objeto verificar si merece la pena ponerse con ellas, es decir, si se detectan errores graves (el sistema no funciona, sea cae a la más mínima operación, no abre una pantalla que permite acceder a una funcionalidad esencial, etc…), ¿para qué perder el tiempo haciendo un testing en mayor profundidad del sistema?

También es frecuente aplicar este tipo de testing con carácter previo a una entrega al cliente, de manera que se realice una última comprobación para verificar que a alto nivel todo está bien (antes como es lógico se debería haber realizado unas pruebas en profundidad del sistema de información a todos los niveles, no solo el funcional).

Bogdan Bereza-Jarocinski es un desarrollador de software y psicólogo polaco especializado en la calidad del software, más concretamente en el campo del testing. No obstante, ha pasado prácticamente por los diferentes perfiles clásicos por los que suele pasar un desarrollador: programador, analista, jefe de proyectos…

Me ha resultado interesante una reflexión que realizó en el año 2000, sobre el proceso de automatización del testing (traducción libre): “Introducirse en la automatización de las pruebas es, a veces, como un romance: tormentoso, emocional, en ocasiones un fracaso y en otras un éxito espectacular”.

Esta cita puede ser válida en muchos campos del desarrollo de software cuando existe un cambio de la manera en que tradicionalmente se han hecho las cosas a otra diferente: nunca es fácil, genera un cierto desgaste, siempre aprendes y el resultado puede ser bueno o nefasto.

La automatización del testing, en todos los niveles, no solo a nivel de la definición de pruebas unitarias o la aplicación de técnicas orientadas a las mismas como TDD, supone un cambio de mentalidad importante en el desarrollo de software, ya que es la consecuencia de entender, por fin, que:

– El software es de naturaleza adaptativa o evolutiva, donde los mismos usuarios van refinando con el uso del producto la idea que tenían inicialmente del mismo y donde los mismos procesos de negocio evolucionan, dando lugar a nuevas versiones de la aplicación.

– Donde la calidad del software es un objetivo esencial a conseguir por cualquier desarrollador y que parte de esa calidad final depende de la capacidad de localizar errores en las etapas más tempranas posibles (para reducir los costes asociados a su corrección) y para reducir el número y criticidad de los mismos que llegan a entornos de producción.

Mutation testing es una técnica de pruebas de caja blanca que surgió en la década de los setenta que se utiliza para realizar una verificación de la calidad de los métodos y procedimientos de testing utilizados.

El motivo principal de la utilización de este tipo de estrategias es la de comprobar si las pruebas definidas son correctas y cubren los requerimientos del sistema que se está verificando. A veces se definen pruebas que efectivamente dan por válidas las salidas correctas de un determinado programa, pero que también dan por válidas otras salidas que deberían ser erróneas, es decir, nos centramos en comprobar que el resultados que esperamos se produce sin pensar en que lo mismo hay otras formas anómalas de producir resultados que además son incorrectos.

¿En qué consiste la técnica? En realizar ligeras modificaciones al código fuente original que deberían provocar unos resultados diferentes al normal y comprobar si la técnica de testing utilizada puede detectar estos cambios. Ejemplos de estos pequeños cambios lo podemos tener modificando el operador en las guardas de sentencias selectivas o iterativas, eliminando sentencias, etc…

Si la técnica de testing no ha detectado el cambio puede ser por dos motivos, por un lado que los cambios en el código fuente no han sido ejecutados (ya sea porque ese fragmento de código no se ejecuta nunca) o porque la estrategia de testing ha fallado. Para intentar que este tipo de situaciones no provoque falsos positivos lo que se hace es incluir mutaciones en zonas cubiertas por pruebas unitarias (y que se ha verificado que el código puede llegar a ejecutarse) o bien realizar mutaciones en diversas zonas del código.

La rigurosidad del testing debe depender del nivel de criticidad del sistema que se va a poner en producción, ya sea en primera instancia o como consecuencia de una evolución del mismo. Esta rigurosidad se tendrá que ver reflejada en todas las etapas de desarrollo del software y tiene como objetivo evitar que lleguen a producción errores que sean críticos para el sistema y que puedan provocar un coste en vidas humanas o unas pérdidas económicas que puedan poner en jaque la subsistencia de una organización.

Esta rigurosidad estará relacionada con el número de casos de prueba a los que se somete el software, desde la misma definición de las pruebas unitarias a las pruebas de carácter funcional, así como a la propia verificación de la bondad de la técnica utilizada.

Hoy en día existen implantaciones software en sistemas empotrados, sistemas de información, etc… que manejan todo tipo de cosas. Como cada vez está más extendida la automatización, cada vez son mayores los sectores donde el software maneja funciones críticas. Incluso funciones que se pueden considerar no críticas, como por ejemplo la web de una organización, cada vez tiene un mayor grado de criticidad tanto en cuanto la forma en que muchos clientes acceden a la misma es través de Internet.

Por tanto, cada día que pasa es más importante que un producto llegue a producción tras superar un umbral de calidad específico definido para el mismo que permita tener confianza en una reducción importante de la probabilidad de que se produzcan errores críticos.

Hacer un buen testing no es sencillo, así como seleccionar el conjunto finito de casos que se va a estudiar (incrementar la cardinalidad de ese conjunto depende de factores económicos y el presupuesto debería estar ajustado a la criticidad del producto), ya que de el mismo depende que posibles errores importantes se puedan colar en producción.

Sobre esto es conveniente recordar una cita muy conocida de Edsger Wybe Dijkstra en la que comenta que: “El testing de los programas puede ser muy efectivo para mostrar la presencia de errores, sin embargo resulta inadecuado para mostrar su ausencia”.

Uno de los aspectos más complicados del testing consiste en definir hasta dónde se va a llegar, es decir, qué tipo de pruebas realizar y con qué intensidad, ya que todo el software no tiene la misma criticidad, los mismos recursos para su desarrollo, los mismos plazos, etc…

Es un error aplicar el mismo nivel de testing a todos los productos, ya que podemos pecar por exceso o por defecto (siempre mejor, como es lógico el exceso) y además hay que tener en cuenta que el testing se debería medir más por su efectividad y por su calidad que por el tiempo dedicado al mismo, si bien, se entiende que un equipo formado, aplicando una serie de metodologías y utilizando una serie de herramientas, mantendrá un nivel de efectividad más o menos regular por unidad de tiempo.

A lo anterior hay que añadir que el testing se debe realizar en todo el proceso de desarrollo y no solo al final y que el mismo equipo de proyecto debe participar en el mismo mediante la aplicación de pruebas unitarias, integración continua, pruebas funcionales, verificaciones continuas con el usuario y obtención e implementación del feedback, etc…, independientemente de que existan una serie de profesionales que sean especialistas en realizar este tipo de trabajo.

El testing es muy importante y no una disciplina secundaria, no es algo que se debiera infravalorar, como en el mundo de la programación hay programadores buenos, malos y regulares, lo mismo pasa con los testers. Un producto que llega a producción con errores graves va a costar dinero, de una u otra forma. En algunos casos estos errores serán críticos y el coste será elevadísimo en otros casos provocará un parón en el servicio que no es poco.

Resulta muy interesante la siguiente cita de Weinberg porque refleja bien a las claras lo crítico que resulta en muchas ocasiones el proceso de testing y lo complicado que resulta establecer sus límites (traducción libre): “En septiembre de 1962, saltó a la luz una noticia que indicaba que un cohete de 18 millones de dolares había sido destruido en pleno vuelo debido a un simple guión que faltaba en un programa. La naturaleza de la programación es así, ya que no existe relación entre el tamaño del error y los problemas que causa. Por tanto, es difícil definir cualquier objetivo en el proceso de testing, sin llegar a la eliminación de todos los errores, algo que resulta imposible”.

Detectar errores al final puede ser demasiado tarde. Si las fallas son funcionales, la arquitectura no es buena, la codificación deja mucho que desear y la culpa es tuya (en muchos casos aunque no lo sea, si el cliente tiene fuerza y no te respeta) el esfuerzo de corregirlo será tan importante que los beneficios del proyectos, si los hay, se diluirán y si hay pérdidas se multiplicarán.

El testing debe acompañar al proyecto desde que se concibe. Los que nos dedicamos a esto tenemos el defecto de que damos demasiadas cosas por supuesto y nos pasan demasiadas cosas por eso. Desde el plan de proyecto, desde el primer acta de reunión, todo debe ser revisado, todo debe ser validado.

¿Es eso ágil? Habrá quien piense que no, pero desde mi punto de vista no hay nada más ágil que no tener que volver a hacer algo de nuevo sin que el usuario te haya dicho que lo cambies. ¿Qué hay que hacer quince veces una pantalla? Pues se hace, si con ello y en cada paso estamos más cerca de que el usuario vea satisfechas sus expectativas. Evidentemente eso cuesta dinero y si no hay presupuesto detrás, el usuario deberá intentar ser más preciso (o renunciar a funcionalidades menos prioritarias).

En el proceso de codificación las pruebas deben hacerse cuanto antes. Si consideras que TDD es demasiado, aplica pruebas unitarias una vez desarrollado el código, si también consideras que eso es demasiado aplica la técnica que prefieras, pero por favor, prueba lo que estés haciendo y no des por supuesto que determinadas partes del código funcionan salvo que estés muy seguro.