Tipo de Pruebas para Desarrollo de Software

Tema: Explicación sobre los diferentes tipos de pruebas que se pueden hacer en el desarrollo de software.
Categoría: Explicación concreta / Calidad - Integración Continua
Tecnologías / Componentes: XUnit, Sonar, PMD, Findbugs, Thucydides, Checkstyle, Cobertura


Contenidos

1 Introducción
2 Pruebas Unitarias
3 Pruebas de Aceptación de Usuario
4 Pruebas de Regresión
5 Pruebas Funcionales
6 Pruebas de Integración
7 Pruebas No Funcionales
8 Pruebas de Stress
9 Pruebas de Calidad de Código
9.1 Cobertura:
9.2 Análisis de Líneas de Código:
9.3 Complejidad:
9.4 Diseño de Clases:
9.5 Violaciones de Calidad:
9.6 Sonar:
10 Resumen

En este artículo daré una explicación concreta del tipo de pruebas que podemos utilizar dentro del mismo concepto de Integración Contínua, aunque abarcaré un artículo específico para detallar cada uno.

Introducción

Dentro del proceso de Integración Contínua podemos ir agregando actividades automatizadas que prueben el funcionamiento, calidad, tiempo de respuesta de un sistema en desarrollo:
  • Pruebas Unitarias,
  • Pruebas de Aceptación de Usuario
  • Pruebas de Regresión
  • Pruebas Funcionales
  • Pruebas de Integración.
  • Pruebas de Estress
  • Pruebas de Calidad de Código
    • Cobertura
    • Análisis en Líneas de Código
    • Complejidad
    • Diseño de clases
    • Violaciones generales de desarrollo
    • Sonar

Pruebas Unitarias


Las pruebas unitarias tienen ese nombre debido a que se prueba la funcionalidad de cada método o función; únicamente contemplando la lógica que debe realizar y excluyendo la convivencia con otras clases o sistemas. Esto conlleva a que si este método utiliza funciones de otros métodos tenemos que ingeniárnosla para que esta lógica no afecte nuestro método que estamos probando.
Por ejemplo, supongamos que tenemos una Asistente de un doctor con la siguiente funcionalidad:
  • Recibir el seguro social de un paciente.
  • Después utiliza a un Bodeguero para que éste busque su respectivo expediente.
  • Una vez que ya tiene el expediente tiene que buscar una sección en específico y extraer el nombre de una medicina.
  • Ese nombre entregárselo al paciente.
Esa lógica que realiza la Asistente es la que tenemos que probar unitarimente pero se tiene que contemplar que existe una dependencia entre el proceso de obtención del nombre de la medicina con el dato ingresado (seguro social) y del contenido del expediente (el cual fue entregado por un tercero).
Por lo tanto, para realizar las respectivas pruebas tendremos que controlar el dato ingresado y también el contenido del expediente.  De esta manera si nosotros sabemos y podemos controlar estas variables podremos saber con certeza el valor de salida (en este caso el nombre de la medicina)   
Así que, lo que tenemos que hacer es desarrollar un Bodeguero para pruebas el cual regresará siempre el mismo expediente que nosotros conocemos. Este tipo de Bodeguero para pruebas también se le conoce como mock,  stub, dummy, etcétera.  En otro artículo escribiré las diferencias.
Por lo pronto, y regresando al tema hay que notar que se deben desarrollar dos implementaciones del mismo Bodeguero. Uno que realiza la logica real que se usará en producción y otro que servirá para controlar los datos que regresa utilizado únicamente en pruebas.
Por ende,  nosotros al programar la lógica de la Asistente tenemos que programar sus respectivas pruebas unitarias indicándole que utilize la implementación de Bodeguero de pruebas; y al mismo tiempo tendremos que desarrollar otra implementación para pruebas de Asistente para que otras pruebas puedan utilizarla también. 
El objetivo principal de las pruebas unitarias es asegurar que el código que hemos programado funciona realmente como lo esperamos; no precisamente refleja lo que el cliente nos pidió (aunque debería de ser), para eso existen otro tipo de pruebas como las de Aceptación de Usuario. Por lo tanto, las Pruebas Unitarias están destinadas para el aseguramiento del desarrollador y no para el usuario final.
Existen varios frameworks para poder realizar estas Pruebas Unitarias, tales como la familia XUnit: Junit para java CppUnit para C, PhpUnit  y Nunit para .Net. Existe otra familia de frameworks igualmente famosa como TestNG.

Pruebas de Aceptación de Usuario


Las Pruebas de Aceptación (acceptance tests) son principalmente para usuarios no técnicos como aprobadores, clientes, usuarios de sistema etc.
Se pueden hacer este tipo de pruebas montadas sobre Xunit pero también existen frameworks especializados como EasyB para groovy, JBehave y Cucumber para Java, etc. Éstos están basados en las metodologías de Desarrollo Basado en Pruebas (TDD Test Driven development ) y Desarrollo Basado en Comportamiento (BDD Behavior Driven Development) que es un subconjunto de TDD.
 Para poder generar este tipo de pruebas y poder montarlos sobre Xunit o en cualquiera de los otros frameworks podemos utilizar herramientas como como Webdriver ,Selenium y en lo particular prefieroThucydides
Existen tres maneras de hacer pruebas en web.
1. Mediante grabación:  Con una herramienta empieza a grabar la pantalla y uno comienza a abrir la aplicación web,  hacer clicks, llenar campos etc, y la herramienta grabará todos los inputs que se escribió y recuerda la posición de cada campo o elemento. De esta manera, para ejecutar las pruebas posteriormente, prácticamente, se le pone "play" y la herramienta comenzará a repetir lo grabado.
El mayor problema de este tipo de pruebas es que si la página cambia de look& feel o de posición los elementos, la grabación ya no servirá porque las coordenadas de los campos han cambiado de posición.  Sin embargo es un método válido, muy usado y fácil de implementar. 
2. Otro método es por Scripts: En el cual, uno levanta la herramienta, por lo regular como plugin de Firefox, y comenzamos a utilizar la aplicación.  La herramienta en lugar de grabar las coordenadas obtendrá los objetos del DOM del navegador y comenzará a guardar instrucciones en Javascript. Para ejecutar posteriormente las pruebas, habrá que inicializar el plugin en el navegador y éste comenzará a ejecutar los comandos de javascript.
Este método es muy bueno y confiable sin embargo no tiene la facilidad de ejecutar las pruebas de manera automatizada y obtener reportes de las pruebas.
Selenium y Webdriver utilizan este método.
3. La tercera manera se le llama Page Objects: El cual es prácticamente una capa encima del método Por Scripts pero el desarrollo de las pruebas se programan en Java ( o groovy)  y la herramienta, al ejecutar las pruebas, creará las respectivas instrucciones de javascript y ejecutará Selenium, por ejemplo. Pero estará al control de las pruebas y podrá generar reportes de resultados.
La ventaja de este método es la automatización de las pruebas y la generación de reportes enfocado a Usuarios Finales, Clientes y demás personal no técnico. Además de poder hacer la abstracción entre información de negocio (para clientes de alto nivel), bajando a cada paso de pruebas (para usuarios de sistema) y separando la implementación la capa WEB de las páginas (información muy técnica)
Thucydides es un gran ejemplo de un framework que utiliza esta manera.
El objetivo de las Pruebas de Aceptación es mostrar al usuario no técnico el avance del desarrollo.
Existe una variación de este tipo de pruebas las cuales son las Pruebas de Aceptación Automatizadas, las cuales, la única diferencia con las no automatizadas es que, las automatizadas siempre deben de tener los mismos inputs y esperar siempre los mismos resultados; por lo tanto, para realizar este tipo de pruebas automatizadas se deberán utilizar Clases Mock o Stubs para la interacción con otros componentes como base de datos, mensajería, etc. y tener datos controlados como en el caso de las Pruebas Unitarias. De esta manera, garantizaremos que nuestras Pruebas de Aceptación de Usuario Automatizadas podrán ser ejecutadas mil veces y las mil veces tendrán el mismo resultado.

Pruebas de Regresión

Existen también las Pruebas de Regresión las cuales pueden ser Pruebas de Aceptacion o Pruebas Funcionales pero se realizan sobre una aplicación ya terminada para tener una base de su funcionalidad básica.
Este tipo de pruebas se realizan principalmente sobre una aplicación que no se tiene documentación o ningún tipo de pruebas, por lo que estas pruebas se deben realizar antes de hacerle una modificación a la aplicación.  Por lo tanto, solo hay que hacer pruebas de las funcionamiento básico y no exhaustivo preferentemente.

Pruebas Funcionales

Las Pruebas Funcionales son pruebas similares a la de aceptación con la diferencia que sí son técnicas y por lo tanto deberán incluir cada uno de los requerimientos funcionales. En general, las Pruebas de Aceptación incluyen Pruebas Funcionales y No Funcionales.
Para este tipo de pruebas es necesario que se realizen también bajo ambientes controlados para poder manipular los datos de entrada y salida. Por ejemplo, en una base de datos que deberá popularse con datos ya conocidos antes de iniciar. Esto servirá para que las pruebas siempre funcionen y no marquen error cada vez que se ejecutan debido a que la base ya cambió de estado

Pruebas de Integración

Las Pruebas de Integración son similares a las funcionales o las de aceptación, pero sobre repositorios reales, datos reales y con la interacción real con otros sistemas o componentes.
Principalmente sirven para asegurar que la implementación en alguna ambiente en particular (UAT o Producción ) ha sido exitosa. Aquí el término exitoso implica que la aplicación corre al 100% por lo tanto, todos los artefactos han sido instalados y configurados correctamente y que también su convivencia con otros componentes como base de datos, mensajeria entre otros es correcto. Estas pruebas automatizadas ayudan mucho en tareas repetidas y aburridas cada vez que se hacen implementaciones.

Pruebas No Funcionales

Este tipo de pruebas están destinadas a probar que los requerimientos no funcionales han sido satisfechos completamente. Lo que en general se intenta probar son funcionalidades en el comportamiento bajo estréss, alta demanda, respuesta bajo los límites de tiempo establecidos por el cliente, ciertos criterios de look&feel, etc.

Pruebas de Stress

Las Pruebas de Stress son una particularidad de las Pruebas No Funcionales y sirven para verificar el comportamiento de una aplicación bajo una demanda excesiva.
El objetivo es poder generar una gran cantidad de peticiones a la aplicación y verificar su comportamiento, y de esta manera poder garantizar el número máximo de peticiones bajo las cuales la aplicación, servidor, interacción con otros aplicativos, etc, es normal.
Para poder realizar este tipo de pruebas hay que contemplar muchas variables y se vuelve un tanto complejo su implementación y análisis.
Por ejemplo:
  • Deben realizarse sobre un ambiente lo más parecido al de Producción. De esta manera, los resultados serán congruentes con los experimentados en producción.
  • En caso que no se tenga un ambiente similar a Producción, el análisis se vuelve complejo; esto es porque es dificil poder extrapolar los resultados en este ambiente y llevarlos a un análisis productivos, ya que por lo general los sistemas no se comportan de una manera lineal para poder predecir.
  • Por otro lado, se debe contemplar varios clientes que realizen las pruebas y los clientes deberán ejecutarse en diferentes computadoras. Esto debido a que tenemos que considerar la carga de CPU que se genera en cada computadora y no sobrecargarlas, sino las peticiones que generen irán disminuyendo.
  • Si se realizan estas pruebas distribuidamente, se tendrá que considerar la recolección de los resultados de cada una de las computadoras y analizar el conglomerado de datos.
En general, para poder realizar este tipo de pruebas deberá tenerse un plan bien definido de la arquitectura de servidores / computadoras que las realizarán, un método de recolección y análisis centralizado.
JMeter de Apache es excelente para ésto.

Pruebas de Calidad de Código

Este tipo de pruebas sirven para garantizar que la calidad del código es realmente óptima y que la probabilidad de tener errores o bugs en la codificación es mínima (nunca dejarán de existir los bugs pero al menos podemos hacer lo pertinente para disminuir la probabilidad).
Existen varios tipos de análisis de calidad y para cada uno existen diferentes herramientas, por lo que a continuación explicaré generalmente qué tipo de análisis se pueden realizar y con qué herramienta.

Cobertura:

Este análisis nos indica el porcentaje que nuestro código desarrollado ha sido probado por las pruebas unitarias. La idea principal es que entre más código probado menor el riesgo de que aparezcan comportamientos indeseados. Cobertura es una herramienta muy usada para este tipo.

Análisis de Líneas de Código: 

Este tipo de análisis nos indica la pulcritud del código y se puede dividir en varios:
  • Código repetido: Nos indica el porcentaje de código que se encuentra repetido. Esto es, evita que tengamos la misma funcionalidad repetida en varios lugares en el proyecto y que al encontrar un bug y corregirlo nos falte corregirlo en los demás bloques. También nos ayuda a tener un diseño más estructurado y con mayor cohesión.PMD y Simian son buenas herramientas para ésto.
  • Código Documentado: Nos ayuda en poder conocer qué porcentaje de nuestro código está documentado para que al generar el JavaDoc sea los más real posible.
  • Código comentado: Nos dice el porcentaje del código que se encuentra comentado. Aunque en la práxis este código documentado no afecta, ya que la máquina virtual lo ignora, sí mete ruido y suciedad en el código al debuggearlo. Si en teoría comentamos un código porque actualmente no se va hacer uso de el pero en un futuro es probable y no tegamos que recodificarlo, es un hecho que la mayoría de las veces NUNCA descomentamos el código para usarlo, por lo que casi siempre resulta basura. Además, contando que se usa una versionador como Git o Subversion, es más facil remitirnos a la versión donde el código sí existe, tomarlo y luego copiarlo que andarlo arrastrando siempre comentado e inservible.

Complejidad: 

Este dato de complejidad nos indica que tan complicado es el código (es la implementación ciclomática de McCabe). Por lo regular la complejidad aumenta cuando el código tiene muchas sentencias IF-ELSE, Loops, Switch, etc. La teoría bajo este análisis es que entre menos complejo es un código, más sencillo es poder entenderle, por lo que será mas probable que haga lo que nosotros queríamos que hiciera.
Con un buen "refactoring" este indicativo puede ser controlado bastante eficaz.

Diseño de Clases:

Este análisis lo que intenta demostrarnos es la relación que existe entre las clases en diferentes paquetes. La agrupación de clases en paquetes sirve para diferenciar la funcionalidad entre clases. Por ejemplo, se tiene una clase de pojos, otra de controladores, otra de interfaces otra de implementación, otra de constantes y utilierías, etc. Por lo que este análisis nos muestra los paquetes desde los más abstractos a los más concretos, por lo que la mayoría de las relaciones las tienen las clases más concretas. Así que si en nuestro diseño existe algún error, este análisis nos mostrará las relaciones indebidas de una clase concreta que hace uso de una abstracta.

Violaciones de Calidad:

Existen varias reglas ya definidas y conocidas las cualas al analizar el código y su funcionalidad pueden caer en este tipo de reglas. Estas pueden ser desde meramente funcionales, estéticas, estándares  y hasta críticas con bugs potenciales. Herramientas como Checkstyle, Findbugs o PMD
Por ejemplo: Existe la regla Magic Number, el cual nos indica que estamos haciendo una comparación con un número establecido y la teoría dice que ningún número debe ser definido ya que al analizar el código nos costará trabajo enteder el por qué de ese número; así que es más facil usar una constante con un nombre explícito que mencione el significado de ese número. Al poder disminuir éstas violaciones lo más posible, estaremos garantizando que el código cumple con estándares o convenciones, tiene el menor riesgo de bugs probables y en general buenas prácticas de desarrollo.

Sonar:

Sonar es una herramienta que tiene integrado todas estas pruebas de control de calidad por lo que dentro utiliza PMD, Findbugs, Checkstyle, Cobertura y demás herramientas en un lugar centralizado. La ventaja de esta herramienta es que podemos utilizarla para poder realizar estos análisis en un lugar centralizado con reportes generalizados, detallados y con la posiblidad de ir adentrando (drilldown). Existe un plugin para maven, jenkins, eclipse, etc. para poder hacer todos los análisis bajo un mismo criterio que en Sonar asignemos.

Resumen

En resumen, podemos hacer uso de todos o alguno de este tipo de pruebas. Entre más pruebas abarquemos, mayor garantía estaremos dando a nuestros clientes de entregar un producto de alta calidad, código limpio, reportes de avances, status de implementación de requerimientos funcionales y no funcionales, etc.
Estas pruebas pueden implementarse bajo un Contenedor de Integración Contínua como Jenkins para que se realicen automáticamente y escalonadamente.
En caso que aún no tengas en tus proyectos algunas de estas pruebas, no te asustes, puedes comenzar a implementar y estandarizar de una en una. ¡Recuerda que es un proceso de madurez!
Post original en blog del autor

Opciones de visualización de comentarios

Seleccione la forma que prefiera para mostrar los comentarios y haga clic en «Guardar las opciones» para activar los cambios.
Imagen de paranoid_android

Buen Post

Hola, es un buen post.

Para algún gurú de QA.

¿Existe alguna herramienta que te ayude a copiar parte de los datos de producción a otros ambientes ofuscando datos confidenciales?

Supongamos que en un ambiente de desarrollo no se tiene el número de casos suficiente para poder construir un reporte.
Un usuario por ejemplo dice quiero la campaña fulana, propietario, modelo y color. Estos datos no vienen en una sola tabla y los nombres de los campos en la base de datos no te dan muchas pistas. Así que tendrías que explorar los datos en las tablas para ver en donde se están guardando ese tipo de valores.

En el ejemplo en lugar de darte el nombre real te daría un nombre ficticio.
En lugar de copiar un millon de registros copia cienmil.