¿Problemas en el momento de unir grandes bloques de código de diversos desarrolladores? ¿El equipo de trabajo no está coordinado y se solapan algunas tareas? ¿Dedicamos el tiempo suficiente a revisar el funcionamiento de nuestro código? ¿O nos enteramos de los errores a través de las quejas de los usuarios? ¿Nos topamos con más dificultades de las que desearíamos cada vez que queremos presentar nuestro software al usuario final? Si alguna de las respuestas a las preguntas anteriores es sí, este artículo será de tu interés.

Para el desarrollo de proyectos de software, en inLab seguimos el ciclo de desarrollo por iteraciones (sprints) o incremental basado en Scrum. Con este método se divide temporalmente, normalmente en periodos de dos o tres semanas, el desarrollo de las funcionalidades requeridas por el cliente pudiendo implementar, comprobar, desplegar y mostrar al cliente cada una de ellas con independencia de las otras beneficiando así a ambas partes.

Gracias a esta división del bloque de funcionalidades, el equipo de desarrollo puede centrarse única y exclusivamente en la misma fase de trabajo, si el cliente cambia los requisitos sólo afecta a la iteración actual y una mala implementación de las funcionalidades no interfiere con el trabajo realizado en anteriores iteraciones.

Este método de desarrollo, se adapta con resultados muy atractivos a algunas de las prácticas adoptadas del extreme programming que utilizamos en inLab como la integración continua. En pocas palabras, éste modelo de trabajo pretende que el equipo de desarrollo integre su código diariamente, que dicho código pase una serie de pruebas predefinidas por los programadores para verificar su correcto funcionamiento y que el despliegue del producto esté disponible en cualquier momento ya sea en un sistema de pre-producción o incluso en uno de producción.

«Integration is primarily about communication. Integration allows developers to tell other developers about the changes they have made. Frequent communication allows people to know quickly as changes develop.»

– Martin Fowler, 1 May 2006, Continuous Integration

En el día a día, antes de empezar a programar, cada desarrollador actualiza su entorno bajando los últimos cambios del repositorio de código compartido, si existe cualquier conflicto debe resolverlo y a partir de aquí puede empezar a diseñar una de las nuevas funcionalidades. Para ello, utilizamos TDD, otra de las prácticas del extreme programming preparando las pruebas que validarán el comportamiento de dichas funcionalidades, implementando la funcionalidad y comprobando que los test definidos anteriormente se ejecutan correctamente. Una vez terminada la funcionalidad, el desarrollador es libre de enviar su aportación al repositorio antes de repetir el mismo proceso para otra funcionalidad. De esta forma, cada día un sólo desarrollador realiza varios commits con pequeños cambios, facilitando la detección de errores y la comprensión de los cambios para otros miembros del equipo. Antes de terminar la jornada el desarrollador tiene que compartir sus cambios, para ello antes tiene que descargarse las aportaciones de sus compañeros, resolver posibles conflictos, verificar que todos los test funcionan y finalmente enviar sus cambios al repositorio.

Es en este flujo de trabajo donde entran en juego los sistemas de integración continua, automatizando y facilitando algunas de estas tareas desde la construcción del proyecto hasta el despliegue del mismo.

Los sistemas de integración continua son entornos que pretenden gestionar estos procesos de forma autónoma liberando así a los programadores de estas labores. Típicamente detectan los cambios en el desarrollo de los proyectos, lanzan la batería de test para controlar que el comportamiento del software es el deseado y despliegan en una máquina de desarrollo todo el código. No obstante, los procesos que acabamos de listar son tan sólo algunos de una extensa colección que nos facilitan algunos software de integración continua.

Las diversas tareas que ejecutan estos sistemas suelen representarse visualmente mediante tarjetas ordenadas que, en caso de funcionar lanzan la siguiente tarea y en caso de fallar pueden llegar a avisar al equipo de desarrollo para que revisen qué ha pasado. Incluso se puede llegar a configurar algunas tarjetas para que su ejecución tenga que ser lanzada manualmente, podría ser el caso de un despliegue del software a un entorno de producción, el cual no siempre nos interesará hacer.

tareas sistemas integración continua

Entre las ventajas que aporta la inclusión de estos sistemas en los proyectos encontramos la detección de errores en cada iteración del código, la disponibilidad del proyecto construido en todo momento y la seguridad de tener la implantación en el entorno de producción ya comprobada.

Hay multitud de herramientas de integración continua: Jenkins, Hudson, Bamboo, Solano, CruiseControl

En inLab utilizamos este tipo de herramientas en diversos proyectos, tomaremos como ejemplo un proyecto de aplicaciones móviles formado por una API, una aplicación móvil y una web de administración gestionados por Jenkins.

ejemplo proyecto

Cada cierto tiempo Jenkins revisa si existe algún cambio en la rama de desarrollo del repositorio y si es así inicia el flujo de trabajo. Este período de tiempo es parametrizable, también se puede lanzar manualmente.

Una vez iniciado el flujo, la primera tarjeta trae la última versión del código, construye el proyecto descargando sus dependencias y preparando el espacio de trabajo para la ejecución de las siguientes tarjetas.

Una vez el proyecto está listo, se inician dos tarjetas en paralelo, son las de ejecución de test unitarios de la API y la de test de la aplicación móvil. Al terminar los test unitarios de la API, una nueva tarjeta se encarga de lanzar los test de integración migrando previamente los últimos cambios producidos en la base de datos.

Ejecutando este tipo de pruebas en un sistema de integración continua como Jenkins conseguimos validar el buen funcionamiento del proyecto en un entorno independiente del nuestro.

Si las tarjetas de test de la API tienen éxito, la siguiente tarjeta desplegará la API a la máquina de pre-producción mientras otra tarjeta ejecuta los test de calidad de la API.

En la tarjeta de calidad hemos configurado varios plugins que nos proporcionan información acerca del software que estamos desarrollando. Gracias a un plugin para Jenkins llamado Clover PHP en la tarjeta de calidad obtenemos métricas de la cantidad de código cubierto por los test. DRY Plugin nos genera una vista donde obtenemos información acerca de la posible duplicación de código en el proyecto. Otro plugin, PMD Warnings nos muestra alertas sobre el mal funcionamiento o uso incorrecto de nuestro código clasificándolas en distintos niveles de prioridad. Por último, JDepend Plugin enseña las dependencias entre clases y posibles ciclos en el diseño del software.

Clover PHP coverage reportFigura 1: Ejemplo de informe de cobertura de código de Clover PHP

 

PMD Warnings warning​Figura 2: Ejemplo de alerta de PMD Warnings

Una vez el despliegue en la máquina de pre-producción esté listo podremos lanzar manualmente la tarjeta de los test de carga. Estos permiten comprobar el correcto funcionamiento del código en una máquina que será lo más parecida posible a la máquina de producción.

Cuando los test de calidad hayan acabado de forma satisfactoria, Jenkins nos permitirá lanzar de forma manual el despliegue de la API en la máquina de producción. Este proceso tiene que ser manual porque no siempre nos interesará reemplazar la API ya existente.

Además, como punto extra, existe una última tarjeta que lanza el despliegue de la web de administración. Este punto es posterior al lanzamiento de la API porque la web es dependiente de ella.

Paralelamente, la compilación de la versión de desarrollo de la aplicación se realiza en un servidor externo y los ficheros generados son recuperados por Jenkins para subirlos a una web de uso interno. Todo este proceso se realiza en una única tarjeta.

Si la tarjeta anterior se ha ejecutado de forma correcta, Jenkins nos permitirá lanzar exactamente el mismo proceso pero generando una versión firmada para producción y preparando los archivos APK y IPA listos para distribuir en Play Store para Android y en App Store para iOS.

En caso de que cualquier tarjeta falle, a través de un plugin hemos configurado Jenkins para que nos notifique vía Slack en un canal concreto. Además, para algunos procesos que requieren cierto tiempo o se tratan de despliegues a producción Jenkins también nos avisará al finalizar.

No debemos olvidar que este tipo de sistemas pretenden automatizar procesos pero no redimen al desarrollador de la programación de pruebas ni de la configuración de los despliegues de entornos de pre-producción y producción.

Si después de leer este artículo estáis pensando en introducir la integración continua en alguno de vuestros proyectos os recomendamos esta lectura.