Integración continua

Yeray Darias Camacho

Independientemente de si el equipo de desarrollo sigue una metodología clásica en cascada o algún tipo de metodología ágil hay un momento decisivo que determina el éxito del proyecto. Este momento es el despliegue de la aplicación en los sistemas del cliente, lo que conocemos como sistema de producción.

Generalmente este suele ser un momento muy tenso porque es muy raro que todo funcione a la primera. Si se sigue una metodología en la que predomine el desarrollo incremental, donde las funcionalidades se van entregando poco a poco, se minimiza ligeramente el impacto, siempre y cuando al final de cada iteración se haya desplegado en el sistema de producción real. Pero generalmente sigue siendo un momento incómodo, se suelen producir errores porque las máquinas de desarrollo tienen configuraciones diferentes a las máquina de producción, el rendimiento no es tan bueno porque la base de datos de producción tiene una cantidad mucho mayor de información, o cualquier otro detalle que no se tuvo en cuenta durante el desarrollo.

Para resolver este problema aparece una nueva «filosofía» o práctica denominada integración continua. Es un modo de desarrollar un poco diferente al habitual, y que requiere de una serie de buenas prácticas y la aceptación de las mismas por el equipo de desarrollo. Se ha de convertir en un hábito que se realice de forma automática, casi sin darnos cuenta.

1. La integración continua desde el punto de vista del desarrollador

La siguiente descripción de un día de trabajo, en un equipo que realiza integración continua, ayudará a ilustrar el proceso y a comprender los elementos necesarios para llevarla a cabo.

Al principio del día lo normal es seleccionar la siguiente tarea más importante a realizar. En base a la reunión de planificación [6] y a la reunión diaria [7] siempre existe una lista de tareas priorizadas a disposición del equipo de desarrollo, por lo que es muy sencillo saber qué es lo siguiente que debemos hacer. Seleccionamos la tarea en la que debemos trabajar y volvemos a nuestra mesa de trabajo.

El primer paso será actualizar el código fuente del que disponemos con la versión más nueva que exista en el repositorio central. De igual manera, si fuese la primera vez que vamos a trabajar en un proyecto, no tenemos más que descargar una copia limpia del repositorio de código y empezar a hacer nuestro trabajo. A lo largo del día implementaremos la nueva funcionalidad, que debería ser bastante pequeña como para terminarla en una jornada de trabajo y debería incluir una serie de tests que verifiquen que posee el comportamiento deseado. Se puede leer más sobre los tests en otros capítulos del libro, ahora mismo no ahondaremos en el tema porque existen libros enteros sobre TDD y tests unitarios.

Cuando la funcionalidad esté terminada, antes de subir ningún cambio al repositorio, actualizaremos el código fuente con los cambios de nuestros compañeros y nos aseguraremos de que la aplicación sigue construyéndose correctamente y que los tests del proyecto están en verde, es decir que pasan todos sin ningún problema. Si por el contrario aparece algún error lo arreglaremos inmediatamente. Nunca, bajo ningún concepto, se debe subir código al repositorio sin revisar que los tests pasan correctamente y la aplicación se puede construir sin incidencias. Además, es recomendable acceder a la aplicación y revisar rápidamente que todo sigue funcionando adecuadamente, ya que por lo general los tests unitarios no tienen una cobertura del 100%, puesto que ciertos detalles de infraestructura son más sencillos de probar a mano.

Una vez hemos realizado todos los pasos anteriores podemos guardar nuestros cambios en el repositorio de código central, lo que permite que el resto del equipo se actualice y los tengan disponibles en cuestión de segundos.

Aunque el proceso de integración continua comenzó desde el momento en el que empezamos a trabajar en la nueva funcionalidad, el servidor de integración continua comienza a trabajar cuando subimos nuestros cambios al repositorio de código. Dicho sistema se descargará una versión nueva del código fuente con todos los cambios realizados (los nuestros y los de nuestros compañeros), ejecutará los tests y tras hacer la construcción del proyecto lo desplegará en una «réplica» de la máquina de producción. Todo de forma totalmente automatizada. El propio servidor de integración continua podría pasar algunas pruebas extras, como por ejemplo análisis estático de código, análisis de cobertura, o cualquier otro detalle que sería muy tedioso pasar en el proceso de desarrollo porque requiere demasiado tiempo.

También podría haber ocurrido un error. En ese caso el servidor de integración continua nos avisaría con algún mensaje en la consola del mismo o simplemente con el envío de un correo electrónico, que es la opción más común. En ese caso tendremos que inspeccionar cuál ha sido el error y resolverlo para subir una nueva versión corregida. Como el error se genera a los pocos minutos, y no han pasado días ni meses, es muy fácil encontrar dónde está el problema.

2. Ventajas de la integración continua

La experiencia demuestra que reduce de manera increíble el número de errores en el producto final. Esta es probablemente la mayor ventaja que aporta, porque un producto final con pocos o incluso ningún error es a fin de cuentas el objetivo que todos deseamos como desarrolladores.

Pero no hay que olvidarse de la otra gran ventaja que aporta esta práctica, la transparencia del proceso. Como todos trabajamos utilizando el mismo repositorio de código y la información del servidor de integración es pública, para todo el equipo e incluso a veces para el cliente, se conoce con precisión el estado real del proyecto. No hay que esperar durante meses para saber cómo van las cosas y las reuniones de seguimiento pueden ser cortas y precisas.

Como ventaja añadida cabe destacar la posibilidad de disponer de un servidor de demostración en el que siempre se posee la última versión de la aplicación. De esta manera los clientes pueden probar los últimos cambios día a día y ofrecer información al equipo de desarrollo sin tener que esperar meses.

3. Requisitos de la integración continua

Ahora ya disponemos de una visión más concreta de lo que es la integración continua, pero nos vendrá bien repasar cuáles son los elementos indispensables para hacerlo correctamente.

3.1. Repositorio de código

El repositorio de código es fundamental la herramienta que permite que el equipo trabaje de forma totalmente sincronizada, pero a la vez sencilla. Cada uno trabaja en su parte y puede actualizar los cambios sin necesidad de que otros desarrolladores tengan que esperar por estos y viceversa.

Desgraciadamente todavía hay muchas empresas que aún no poseen una herramienta de este tipo. Para aquellos que aún no utilicen un repositorio de código cabe destacar que actualmente existen una gran cantidad de soluciones gratuitas de calidad, como pueden ser Subversion, Git o Mercurial por mencionar algunas. Esta es una pieza fundamental y será prácticamente imposible realizar integración continua si no se dispone de ella.

Este tipo de herramientas marcan un antes y un después en el trabajo cotidiano de un equipo de desarrollo de software. Pero además es el pivote fundamental de la integración continua, que no es posible sin un repositorio de código.

3.2. Sistema de construcción automatizado

Muchos equipos utilizan complejos entornos de desarrollo para implementar y compilar los proyectos. Esto en sí mismo no es malo, pero debemos poder construir el proyecto en cualquier máquina sin necesidad de dichos entornos. No todas las máquinas en las que vayamos a instalar nuestro proyecto tendrán el entorno de desarrollo con el que trabajamos, incluso en algunos casos estos pueden tener licencias relativamente caras, por lo que no sería una opción viable.

Hay muchas soluciones de construcción diferentes en función de los lenguajes de programación, en Java están Ant o Maven, en Ruby está Rake, solo por mencionar algunas. La condición es que debe ser una herramienta muy sencilla que se pueda ejecutar en cualquier máquina y que consiga automatizar no solo el proceso de compilación, sino la ejecución de los tests de nuestra aplicación e incluso el despliegue en el servidor de aplicaciones llegado el caso.

3.3. Commits diarios

No todos los requisitos se basan en tener un determinado tipo de herramienta, algunos se basan en saber cómo utilizar una determinada herramienta. Una vez que tenemos un control de versiones hay que recordar que el almacenamiento de los cambios debe ser diario, idealmente un desarrollador debe hacer incluso varias subidas al repositorio de código al día.

Si tenemos un repositorio de código, pero cada desarrollador sube sus cambios cada dos semanas seguimos en el mismo problema de antes, la integración del proyecto se retrasa durante todo ese tiempo, perdiendo toda opción de obtener información inmediatamente. Además, retrasar la integración tanto tiempo genera más conflictos entre los ficheros editados por los desarrolladores. Debido a que con cada actualización del repositorio se hace una integración del proyecto completo, cada mínimo cambio que no desestabilice el proyecto debe ser subido para que se compruebe que todo sigue funcionando correctamente.

3.4. Pruebas unitarias

Hemos hablado todo el tiempo de comprobar que el proyecto funciona correctamente y al mismo tiempo de automatizar todo el proceso lo mejor posible. Para poder comprobar que el proyecto funciona de forma automática necesitamos pruebas unitarias (muchas veces incluso pruebas de integración).

Existen muchas maneras diferentes de incluir tests en el proyecto, personalmente creo que TDD es la manera más adecuada, pero si el equipo no tiene experiencia y crea las pruebas después de haber realizado la implementación tampoco es un método inválido. La premisa fundamental es que toda nueva funcionalidad debe tener una batería de pruebas que verifique que su comportamiento es correcto.

3.5. Servidor de integración

Esta es la pieza más polémica, mucha gente cree que no es absolutamente necesaria para hacer integración continua correctamente, por ejemplo, algunos equipos hacen la integración de forma manual con cada subida al repositorio de código. En mi opinión es un paso tan sencillo y barato de automatizar que no merece la pena ahorrárselo. Montar algún servidor de integración, como por ejemplo Jenkins o Cruise Control, es gratuito y tan sencillo como desplegar un fichero en un servidor. Por contra las ventajas son grandísimas. Para empezar el proceso está totalmente automatizado, lo que evita el error humano. Y por otro lado reduce la cantidad de trabajo, ya que tenemos una solución prefabricada sin necesidad de tener que crear nosotros mismos complejas soluciones caseras.

4. Un paso más allá

Con los pasos descritos hasta el momento ya tendríamos un proceso bastante completo y que a buen seguro mejorará enormemente la calidad de nuestro producto. Pero podemos ir un poco más allá y utilizar el servidor de integración para que haga ciertas tareas relativamente complejas por nosotros.

Por ejemplo, podríamos configurar el servidor para que haga análisis estático de código, de forma que pueda buscar en todo el código bloques sospechosos, bloques duplicados o referencias que no se utilizan, entre otras cosas. Existen gran cantidad de opciones como pueden ser FindBugs o PDM. A simple vista puede parecer algo irrelevante, pero hay que recordar que la complejidad es lo que más ralentiza el proceso de desarrollo, por lo que un código con menor número de líneas y referencias inútiles será más sencillo de leer y entender.

También podríamos incluir tareas que permitan desplegar la aplicación en un servidor, de forma que tendríamos siempre un servidor de demostración con la última versión de nuestro proyecto. Esto es realmente útil cuando tenemos un cliente comprometido, dispuesto a probar todos los nuevos cambios y a dar información al equipo.

Otra operación que podemos automatizar, y que el servidor de integración podría hacer por nosotros, es la realización de pruebas de sistema sobre la aplicación. Imaginemos que estamos desarrollando una aplicación web, podríamos crear tests con alguna herramienta de grabación de la navegación, como por ejemplo Selenium, y lanzarlos con cada construcción que haga el servidor. Es un tipo de prueba que requiere mucho tiempo y no sería viable que se lancen con cada compilación del desarrollador, pero para el servidor de integración no habría ningún problema. Este es solo un ejemplo más de la cantidad de cosas que puede hacer un servidor de integración continua por nosotros, y que nos ayudará a mantener un producto estable y testeado de manera totalmente automática.

Para acabar me gustaría utilizar algunos comentarios escuchados por Martin Fowler cuando habla de integración continua con otros desarrolladores. La primera reacción suele ser algo como «eso no puede funcionar (aquí)» o «hacer eso no cambiará mucho las cosas», pero muchos equipos se dan cuenta de que es más fácil de implementar de lo que parece y pasado un tiempo su reacción cambia a «¿cómo puedes vivir sin eso?». Ahora es tu elección si decides probarlo o no, pero antes de hacerlo piensa en lo poco que tienes que perder y lo mucho que puedes ganar.



[6] Reunión de aproximadamente una hora en la que se decide cuáles serán las tareas a incluir en la siguiente versión de la aplicación.

[7] Breve reunión de seguimiento, diaria, que realiza todo el equipo, donde expone en qué se trabajó el día anterior, en qué se trabajará hoy y si existen impedimentos para llevar a cabo alguna de las tareas en ejecución.