Integración Continua

Nota de Javier D'Ovidio: Si bien el siguiente artículo ya tiene un par de años y menciona herramientas que en su momento fueron muy útiles pero hoy en día han sido reemplazadas por tecnología más moderna, creo que explica muy bien algunos conceptos y prácticas básicas de la integración continua.

Recent Posts

alt

Nota de Javier D'Ovidio:

Si bien el siguiente artículo ya tiene un par de años y menciona herramientas que en su momento fueron muy útiles pero hoy en día han sido reemplazadas por tecnología más moderna, creo que explica muy bien algunos conceptos y prácticas básicas de la integración continua.

Integración Continua

Por Martin Fowler 1° de mayo de 2006. Artículo original aquí

Integración continua es una práctica de desarrollo de software en la que los miembros de un equipo integran su trabajo frecuentemente. Generalmente cada persona integra material al menos diariamente, dando lugar a múltiples integraciones por día. Cada integración es verificada por un build automatizado (incluyendo pruebas) para detectar errores de integración lo más rápido posible. Muchos equipos estiman que este enfoque conduce a reducir significativamente los problemas de integración, y que permite al equipo desarrollar software cohesivo más rápidamente. Este artículo es un panorama de la Integración continua resumiendo la técnica y su uso actual.

Contenidos

Recuerdo muy bien una de mis primeras observaciones de un gran proyecto de software. Estaba haciendo una pasantía de verano en una gran compañía inglesa de electrónica. Mi manager, parte del grupo QA, me hizo un tour por el lugar y entramos en un galpón enorme y deprimente lleno de cubos. Me habían dicho que ese proyecto había sido desarrollado por un par de años y estaba siendo integrado en ese momento desde hacía varios meses; mi guía me dijo que nadie sabía realmente cuánto tiempo llevaría terminar la integración. De esto he aprendido una lección acerca de proyectos de software: la integración es un proceso largo e impredecible.

Pero este no tiene que ser el camino. La mayoría de los proyectos hechos por mis colegas en ToughtWorks y por tantos otros alrededor del mundo, tratan a la integración como un no evento. El trabajo de cada desarrollador está a sólo unas horas de distancia de un estado de proyecto compartido y puede reintegrarse en ese estado en minutos. Cualquier error de integración se encuentra y se puede reparar rápidamente.

Este contraste no es el resultado de una herramienta cara y compleja; la esencia de esto está en la simple práctica de todos los que integran de manera frecuente el equipo, generalmente a diario, en un repositorio de código fuente controlado.

El Artículo original sobre integración continua describe nuestra experiencia sobre cómo Matt ayudó a armar la integración continua en un proyecto de ThoughtWorks en 2000.

Cuando le describo esta práctica a la gente, encuentro dos reacciones: “no puedo trabajar aca” y “hacerlo no hará mucha diferencia”. Lo que la gente descubre al probarlo, es que es mucho más fácil de lo que parece, y que hace una gran diferencia en el desarrollo. Así, la tercera reacción común es "Sí, lo hacemos, ¿Cómo podría vivir sin esto?".

El término “Integración continua” se origina en el proceso de desarrollo de la programación extrema, como una de las doce prácticas originales. Cuando comencé en ThoughWorks como consultor, alenté a utilizar esta técnica en el proyecto en el que estaba trabajando. Matthew Foemmel transformó mi vaga exhortación en sólida acción, y vimos al proyecto transformarse de integraciones raras y complejas al no-evento que describí. Matthew y yo escribimos nuestra experiencia en la versión original de este artículo, uno de los artículos más populares en mi sitio web.

Si bien la integración continua es una práctica que no requiere herramientas particulares para deployar, hemos descubierto que esto es útil el utilizar un servidor de integración continua. El servidor más conocido de este tipo es CruiseControl, una herramienta open source construida originalmente por varias personas en ThoughtWork, y ahora mantenida por una gran comunidad. Desde entonces, aparecieron otros servidores CI, tanto open source como comerciales, incluyendo Cruise de los estudios ThoughtWork.

Construir una característica con Integración continua

La forma más fácil para explicar qué es la integración continua y cómo funciona, es mostrar un ejemplo de cómo funciona en el desarrollo de una pequeña característica. Vamos a asumir que le tengo que hacer algo a una pieza de software, no es importante cuál es la tarea, por el momento asumiré que es pequeña y se puede hacer en un par de horas. Luego exploraremos tareas más largas y otras cuestiones.

Comienzo haciendo una copia de la fuente integrada actual en mi máquina de desarrollo local; lo hago utilizando un sistema de manejo de código fuente, chequeando la copia de trabajo desde el mainline.

El párrafo anterior tendrá sentido para la gente que utiliza sistemas de manejo de código fuente, pero no lo tendrá para nada/será chino para los que no; así que permítanme explicarlo para ellos. Un sistema de manejo de código fuente mantiene todo el código fuente de un proyecto dentro de un repositorio. Al estado actual del sistema usualmente se lo llama 'mainline' (línea principal). En cualquier momento un desarrollador puede hacer una copia controlada del mainline en su propia máquina, esto se llama ‘hacer checkout’; la copia en la máquina del desarrollador se llama ‘copia de trabajo’. De hecho, la mayoría de las veces se actualiza la copia de trabajo en el mainline; en la práctica, es lo mismo.

Ahora tomo la copia de trabajo y hago todo lo que necesito hacer para terminar mi tarea: alterar el código de producción y también agregar o cambiar las pruebas automatizadas. La integración continua asume un alto grado de pruebas que están automatizadas dentro del software: Una instalación que llamo [código auto-testeante]. A menudo utilizan una versión de los populares frameworks de prueba de XUnit.

Una vez que terminé, y muchas veces cuando estoy trabajando, realizo un build automatizado en mi máquina de desarrollo. Esto toma el código fuente de la copia de trabajo, lo compila y lo linkea en un archivo ejecutable, y ejecuta las pruebas automatizadas. Se considera bueno el build general sólo si todas los builds y pruebas no tienen errores.

Con un buen build, puedo pensar en commitear los cambios en el repositorio. El punto es, por supuesto, que otras personas pueden y generalmente tienen que hacer cambios en el mainline antes que yo tenga la oportunidad de commitear. Entonces, primero actualizo en mi copia de trabajo sus cambios y re-builds; si sus cambios no combinan con los míos, esto se verá como una falla en la compilación o en las pruebas. En este caso es mi responsabilidad arreglarlo y repetir el proceso hasta que pueda hacer un build de una copia de trabajo que se sincronice correctamente con el mainline.

Una vez que tengo mi propio build de la copia de trabajo correctamente sincronizada, finalmente puedo commitear los cambios en el mainline, lo que luego actualizará el repositorio.
Sin embargo, el commit no es el fin de mi trabajo. En este punto hacemos un build nuevamente, pero esta vez en una máquina de integración basada en el código del mainline. Sólo cuando este build se realice con éxito podremos decir que los cambios están hechos. Siempre existe la posibilidad de olvidarme de algo en mi máquina y que el repositorio no haya sido actualizado correctamente. Sólo cuando los cambios commiteados realicen un build exitoso en la integración, mi trabajo está hecho. Esta integración de build la puedo ejecutar yo manualmente, o Cruise de manera automática.

Si no hay coincidencia entre dos desarrolladores, generalmente se descubre cuando el segundo desarrollador que quiere commitear, hace un build de su copia de trabajo actualizada; si no, el build de integración falla. De cualquier manera el error se detecta rápidamente. En este punto, la tarea más importante es arreglarlo, y hacer funcionar correctamente al build nuevamente. En un entorno de integración continua nunca debes conservar por mucho tiempo una falla en un build de integración; un buen equipo debe generar/tener muchos builds correctos por día. Los builds incorrectos ocurren de vez en cuando, pero deberían arreglarse rápidamente.

El resultado de esto es una pieza de software estable que funciona correctamente y contiene algunos bugs. Todos desarrollan a partir de esa base estable compartida y nunca se alejan mucho de ella para no necesitar mucho tiempo para reintegrarse. Se ocupa menos tiempo intentando encontrar bugs porque aparecen rápidamente.

Prácticas de integración continua

La historia anterior es el resumen de integración continua (CI) y cómo funciona en el día a día. Conseguir que todo esto funcione sin problemas es obviamente algo más que eso. Ahora me centraré en las prácticas clave que hacer eficaz al CI.

Mantén un solo repositorio fuente

Los proyectos de software involucran muchos archivos que necesitan orquestarse todos juntos para hacer un build de un producto. Rastrearlos es un esfuerzo enorme, particularmente cuando hay mucha gente involucrada. Con lo cual, no es una sorpresa que con el paso del tiempo los equipos de desarrollo de software hayan construido herramientas que manejen estas cuestiones. Estas herramientas llamadas herramientas de manejo de código fuente, manejo de configuración, sistemas de control de versiones, repositorios, etc., son una parte integral de la mayoría de los proyectos de desarrollo. Lo triste y sorprendente es que no son parte de todos los proyectos. Es extraño, pero yo lo ejecuto en proyectos que no utilizan este tipo de sistemas sino una especie de combinación desastrosa de discos locales y compartidos.

Entonces, como base, asegúrate de tener un sistema decente de manejo de código fuente. El costo no es un problema, hay herramientas open source disponibles de buena calidad. El repositorio open source preferido actualmente es Subversion; el antiguo CVS también se sigue utilizando mucho y es mucho mejor que nada, pero Subversion es la opción más moderna. Curiosamente, cuando hablo con desarrolladores que conozco, las herramientas más comerciales de manejo de código fuente gustan menos que Subversion. La única herramienta que he oído constantemente que dicen vale la pena pagar es Perforce.

Una vez que tengas un sistema de manejo de código fuente, asegúrate que todos sepan que ese es el lugar para obtener código fuente. Nadie debe preguntarte dónde está el archivo foo-whiffle; todo debe estar en el repositorio.

Aunque muchos equipos utilizan repositorios, el típico error que veo es que no ponen todo dentro de él. Si la gente utiliza uno, colocan código en él, pero todo lo que necesitas para hacer un build tiene que estar allí, incluyendo: scripts de prueba, archivos de propiedades, el esquema de base de datos, scripts de instalación y bibliotecas de terceros. Sé de proyectos que chequean sus compiladores dentro del repositorio, cosa importante en los comienzos de los compiladores flacky C++. La regla de oro es que debes poder bajar el proyecto en una máquina virgen, hacer el checkout, y poder hacer un build completo del sistema. Debe haber sólo pocas cosas en la máquina virgen; generalmente cosas que son grandes, complicadas de instalar y estables. Los típicos ejemplos son: Un sistema operativo, un entorno de desarrollo Java, o un sistema de base de datos.

Debes colocar todo lo requerido para hacer un build en el sistema de control de fuente, sin embargo, también puedes colocar otras cosas con las que generalmente la gente trabaja allí. Es bueno colocar configuraciones IDE ya que facilita a la gente poder compartir la misma instalación IDE.

Una de las características de los sistemas de control de versiones es que te permiten crear múltiples branches para manejar canales de desarrollo. Es una característica útil, mejor dicho, esencial; pero frecuentemente utilizada en exceso y eso le trae problemas a la gente. Utiliza las branches lo menos posible. Particularmente, ten un mainline: solo una branch de proyecto en desarrollo. Casi todos deben trabajar a partir de este mainline la mayoría de las veces. Las branches razonables son correcciones de bugs de versiones de producción anteriores y de experimentos temporales.

En general, debes almacenar en el control de código fuente todo lo que necesitas para hacer un build, pero nada a lo que ya le hayas hecho un build. Algunas personas conservan los productos del proceso de build en el control de código fuente, tómalo como una amenaza: un indicador de un problema profundo, por lo general la imposibilidad de volver a crear builds confiables.

Automatiza el build

Convertir las fuentes en sistemas de ejecución puede ser un proceso complicado que involucra compilación, mover archivos, cargar esquemas dentro de las bases de datos y demás. Sin embargo, como la mayoría de las tareas en esta parte del desarrollo de software, se puede automatizar, por ende, debe automatizarse. Pregúntale a la gente si tipear en comandos extraños o cliquear dentro de cuadros de diálogo no es una pérdida de tiempo y un criadero de errores.

Los entornos automatizados para builds son una herramienta común de los sistemas. El mundo Unix los ha hecho por décadas; la comunidad Java desarrolló Ant; la comunidad .NET tuvo Nant y ahora tiene MSBiuld. Asegúrate que puedes construir y poner en marcha tu sistema utilizando esos scripts con un solo comando.

Un error típico es el no incluir todo en el build automatizado. El build debe poder obtener del repositorio el esquema de la base de datos y ponerlo en marcha en el entorno de ejecución. Crearé mi primera regla de oro: cualquier persona debe ser capaz de traer una máquina virgen, hacer un checkout de las fuentes del repositorio, emitir un solo comando, y tener un sistema ejecutable en su máquina.

Los build scrpits vienen en diferentes formas y a menudo son particulares para una plataforma o una comunidad, pero no tienen que serlo. Aunque la mayoría de nuestros proyectos Java utilizan Ant, algunos han utilizado Ruby; El sistema Ruby Rake es una buena herramienta de build script. Obtuvimos mucho valor con la automatización en un primer proyecto de Microsoft COM con Ant.

Hacer un gran build en los script a menudo lleva tiempo, y no querrás hacer todos esos pasos sólo para hacer un pequeño cambio; entonces, una buena herramienta para hacer un build analiza lo que necesita cambiar como parte del proceso. La forma más común de hacer esto es chequear las fechas de la fuente y archivos de objetos y sólo compilar si la fecha de la fuente es posterior. Las dependencias se vuelven tramposas: si un archivo de objeto cambia lo que depende de él, puede que también necesites volver a rehacer un build. Los compiladores pueden manejar o no este tipo de cosas.

Dependiendo de lo que necesites, puede que tengas que hacer un build en diferentes cosas. Puedes hacer un build en un sistema con o sin código de prueba, o con conjuntos de pruebas diferentes. Algunos componentes se pueden construir de forma autónoma (stand-alone). Un build script debe permitir hacer un build en destinos alternativos para casos diferentes.

Muchos de ustedes utilizan IDEs, y la mayoría de los IDEs tienen algún tipo de proceso de manejo de build dentro de ellos. Sin embargo, estos archivos siempre son propiedad del IDE y a menudo son frágiles. Además, necesitan el IDE para funcionar. Está bien para los usuarios de IDE configurar sus propios archivos de proyecto y utilizarlos para desarrollos individuales. Sin embargo, es esencial tener un build maestro que sea utilizable en un servidor y ejecutable desde otros scripts. Para los usuarios Java, está bien que tengamos un build de desarrolladores en su IDE, pero el build maestro utiliza Ant para asegurarse que puede ejecutarse en el servidor de desarrollo.

Haz un build auto-testeante

Tradicionalmente un build significa complilar, linkear y todo el resto de cosas que se necesitan para ejecutar un programa. Un programa puede ejecutarse, pero eso no significa que lo haga de forma correcta. Los lenguajes modernos tipeados estáticamente pueden atrapar muchos bugs, pero muchos otros duermen en esa red.

Una buena forma de atrapar un bugs de manera más rápida y eficiente es incluir puebas automatizadas en el proceso de build. Las puebas no son perfectas, por supuesto, pero pueden atrapar muchos bugs; suficientes para ser útil. En particular, el crecimiento de Extreme programming (XP) y Test Driven Development (TDD) han popularizado el código auto- testeante y como resultado, mucha gente ha descubierto el valor de esta técnica.

Los lectores frecuentes de mi trabajo sabrán que soy un gran fan de ambos; sin embargo, quiero remarcar que no necesariamente estos enfoques conseguirán los beneficios del código auto-testeante. Ambos enfoques dicen algo importante acerca de escribir pruebas antes de escribir el código que las haga pasar; de esta forma, las pruebas son para explorar el diseño del sistema como los enfoques son para atrapar bugs. Esto es bueno, pero no necesariamente a los fines de la integración continua, donde tenemos el requerimiento más débil de código de auto-testeante. (Aunque TDD es mi forma preferida de producir código auto-testeante.)

Para código auto-testeante, necesitas una serie de pruebas automatizadas que puedan chequear una gran parte de la base de código para los bugs. Las pruebas necesitan poder dispararse desde un comando simple y auto chequearse. El resultado de la ejecución del conjunto de pruebas debe indicar si alguna ha fallado. Para un auto-testeo al que se le hará un build, la falla de la prueba debe causar la falla del build.

En los últimos años, el crecimiento de TDD ha popularizado a la familia de herramientas open source XUnit que son ideales para este tipo de pruebas. Las herramientas XUnits han demostrado ser muy valiosas para nosotros en ThoughtWorks y siempre le sugiero a la gente que las utilice. Estas herramientas, promovidas por Kent beck, te facilitan configurar un entorno auto-testeante completo; de hecho, son el punto de partida para hacer a tu código auto-testeante. También debes investigar otras herramientas que se enfoquen en pruebas de principio a fin; existe un abanico de ellas al momento, incluídas FIT, Selenium, Sahi, Watir, FITnesse y muchas otras, pero no quiero hacer aquí una lista exhaustiva de ellas.

Por supuesto que no puedes confiar en que las pruebas encontrarán todo. Como se suele decir: las pruebas no prueban la ausencia de bugs. Sin embargo, la perfección no es el único punto en el que se amortiza un build auto-testeante. Pruebas imperfectas ejecutadas con frecuencia, son mucho mejores que las perfectas que nunca se escribieron.

Todos commitean al mainline todos los días

La integración es, ante todo, comunicación. La integración permite a los desarrolladores contarle a los otros los cambios que ha realizado. La comunicación frecuente permite que la gente sepa más rápido los cambios que se desarrollan.

El único prerrequisito para un desarrollador que commitea al mainline es que puedan hacer un build correcto del código. Esto, por supuesto, incluye pasar la prueba de build. Como en cualquier ciclo de commit, el desarrollador primero actualiza su copia de trabajo para coincidir con el mainline, resuelve cualquier conflicto que tenga con el mainline y luego hace un build en la máquina local. Si pasa el build, entonces es libre de commitear en el mainline.

Al hacer esto frecuentemente, los desarrolladores descubren rápidamente si existe algún conflicto entre dos desarrolladores. La clave para solucionar problemas de forma rápida es encontrarlo rápidamente. Con desarrolladores que commitean cada un par de horas, el conflicto se puede detectar dentro de las primeras horas que esto ocurre; a esa altura no ha pasado mucho y es fácil de resolver. Los conflictos que permanecen sin ser detectados por semanas pueden ser difíciles de resolver.

El hecho de que hagas un build cuando actualizas tu copia de trabajo significa que has detectado conflictos en la compilación tanto como conflictos textuales. Como el build es auto-testeante, también detectas conflictos en la ejecución del código. Los conflictos posteriores son bugs particularmente difíciles de encontrar si están alojados hace mucho tiempo en el código. Como sólo hay un par de horas de cambios entre commits, no hay muchos lugares donde pueda estar escondido el problema. Además, como no ha cambiado mucho puedes utilizar diff-debugging para ayudarte a encontrar el bug.

Mi regla de oro general es que cada desarrollador debe commitear al repositorio todos los días. En la práctica es útil si los desarrolladores commitean incluso más frecuentemente. Mientras más frecuentes son tus commits, menos lugares tienes para buscar errores, y más rápido los reparas.

Los commits frecuentes fomentan a los desarrolladores a romper su trabajo en trozos pequeños de un par de horas cada uno. Esto ayuda a seguir el progreso y te da un sentido de avance. A menudo, al principio la gente siente que no puede hacer algo importante en un par de horas, pero hemos descubierto que esa forma de seguimiento y práctica los ayuda a aprender.

Cada commit debe construir el mainline en una máquina de integración

Utilizando commits diarios, el equipo obtiene builds probados frecuentemente. Esto debería significar que el mainline permanece en estado saludable. En la práctica, sin embargo, las cosas todavía salen mal. Una razón es la disciplina, gente que no actualiza y hace un build antes de commitear; otra son las diferencias de entorno entre las máquinas de los desarrolladores.

Como resultado debes asegurarte que los builds normales se realicen en una máquina de integración y sólo si este build de integración se realiza con éxito se debe considerar hacer el commit. Como el desarrollador que commitea es responsable por esto, necesita monitorear el build de mainline para poder repararlo en caso que se rompa. El resultado de esto es que no debes irte a casa hasta que el build de mainline haya pasado con todos los commits que hayas agregado a última hora.

Hay dos maneras principales que he visto para asegurase de esto: utilizar un build manual o un servidor de integración continua.

El enfoque del build manual es el más simple para describir.Esencialmentee es algo similar a un build local que un desarrollador hace en el repositorio antes que el commit. El desarrollador va a la máquina de integración, hace un checkout del head de mainline (que ahora contiene su último commit) y comienza el build de integración. Presta atención al progreso y si se completa con éxito ya ha realizado su commit. También puedes ver la descripción de Jim Shore.

Un servidor de integración continua actúa como un monitor del repositorio. Cada vez que finaliza un commit contra el repositorio, el servidor automáticamente realiza un checkout de las fuentes en la máquina de integración, inicia un build, y notifica resultado del build al desarrollador; éste no termina hasta que obtiene la notificación, generalmente vía e-mail.

En ThoughtWorks, somos grandes fans de los servidores de integración continua. De hecho, lideramos el desarrollo original de CruiseControl y CruiseControl.NET, el servidor open source de CI más utilizado; por ello, también construimos el servidor CI comercial Cruise. Utilizamos el servidor CI en casi todos los proyectos que hicimos y ha sido con resultados muy satisfactorios.

No todos prefieren utilizar un servidor CI. Jim Shore dio una [descripción bien argumentada] de por qué prefiere el enfoque manual; estoy de acuerdo con él en que CI es mucho más que instalar un software. Todas las prácticas tienen que estar en juego para hacer efectiva la integración continua, pero al igual que muchos equipos que utilizan CI, piensan que CI es una herramienta útil.

Muchas organizaciones hacen builds regulares en un cronograma programado, como por ejemplo, todas las noches. Esto no es igual que un build continuo y no es suficiente para ser una integración continua. El punto de la integración continua es encontrar problemas tan pronto como sea posible. Un build nocturno significa que los bugs estuvieron alojados durante todo el día anterior sin ser detectados. Una vez que están en el sistema por tanto tiempo, lleva mucho tiempo encontrarlos y borrarlos.

La clave de hacer un build continuo es que si el build de mainline falla, necesita que lo arreglen en el momento. El punto de trabajar con CI es que siempre estás desarrollando una base estable conocida. No es malo que se rompa el build de mainline, aunque si sucede todo el tiempo da a entender que la gente no está siendo lo suficientemente cuidadosa al actualizar y construir localmente antes de commitear. Cuando un build de mainline se rompe es importante que sea arreglado rápidamente. Para ayudarte a evitar romper el mainline considera utilizar un head pendiente (pending head).

Cuando los equipos comienzan a utilizar CI suele ser de las cosas más difíciles. Al comienzo, un equipo puede luchar para obtener el hábito de trabajar con builds de mainline, sobre todo si se está trabajando en una base de código existente. La paciencia y la constante aplicación parecen ser el truco, así que no te desanimes.

Mantén rápido al build

El objetivo de la integración continua es proveer un feedback rápido. Nada molesta más en una actividad de CI que un build que tome mucho tiempo. Debo admitir un cierto regocijo de viejo cascarrabias respecto de qué se considera un proceso de build largo. La mayoría de mis colegas estiman que un build toma como mucho, una hora; recuerdo a equipos soñando con hacerlo así de rápido, y ocasionalmente seguimos ejecutándolo en casos donde es muy difícil llevar un build a esa velocidad.

Sin embargo para la mayoría de los proyectos, la guía de XP para hacer un build en 10 minutos es perfectamente razonable; la mayoría de nuestros proyectos más modernos lo logran. Vale la pena esforzarse para lograrlo porque cada minuto que reduces el tiempo de build es un minuto salvado para cada desarrollador cada vez que commitee. Como la CI demanda commits frecuentes esto significa una gran cantidad de tiempo.

Si apuntas a pasar de un tiempo de construcción de una hora, a un build más rápido puede parecer una perspectiva desalentadora; incluso puede ser desalentador trabajar en un nuevo proyecto y pensar en cómo mantener las cosas funcionando rápidamente. Para aplicaciones de empresa, al menos hemos encontrado que el cuello de botella habitual es la prueba; en particular las pruebas que involucran a los servicios externos, como una base de datos.

Probablemente el paso crucial es comenzar a trabajar en la configuración de un pipeline de deployment. La idea de fondo de un pipeline de deployment (conocido como pipeline de build o build en etapas) es que se hagan en consecuencia múltiples builds. El commit al mainline activa el primer build, que llamo el build de commit. El build de commit es el build que se necesita cuando alguien commitea al mainline. El build de commit es el que tiene que hacerse rápidamente, y como consecuencia, tendrá una serie de accesos directos que reducirán la capacidad de detectar errores. El truco es balancear la necesidad de encontrar bugs y la velocidad, así un buen build de commit es lo suficientemente estable para que otras personas trabajen en él.

Jez Humble y Dave Farley profundizaron estas ideas en el tópico de Delivery continuo, con más detalle en el concepto de proyectos de deployment. Su libro Continuous Delivery ganó el premio “Jolt excellence” en 2011.

Una vez que el build de commit esté bien, el resto de la gente podrá trabajar en el código con confianza. Sin embargo existen otras pruebas más lentas que puedes comenzar a utilizar. Las máquinas adicionales pueden ejecutar otras rutinas de prueba en el build que tome más tiempo.

Un ejemplo simple de esto son los deployment de dos etapas. La primera etapa compilaría y ejecutaría las pruebas que sean de unidades localizadas con la base de datos completamente apagada. Esa prueba se puede ejecutar rápidamente, manteniéndose dentro de la pauta de los diez minutos. Sin embargo, todo bug involucrado en interacciones a gran escala, particularmente los involucrados en base de datos reales, no podrán ser encontrados. La segunda etapa ejecuta una serie de pruebas diferentes, que sí dan con la verdadera base de datos e involucra más comportamiento de principio a fin. Esta serie puede tardar un par de horas en ejecutarse.

En este escenario, la gente utiliza la primera etapa como el build de commit y la utiliza como su ciclo CI principal. La segunda etapa de build se ejecuta cuando puede, tomando el archivo ejecutable del último commit bueno para pruebas posteriores. Si el build secundario falla, puede que no tenga la misma calidad de ‘frenar todo’, pero el objetivo del equipo es arreglar los bugs tan rápido como se pueda, mientras mantienen el build de commit ejecutándose. Como en este ejemplo, los builds posteriores suelen ser pruebas puras desde que últimamente son sus pruebas las que causan la lentitud.

Si el build secundario detecta un bug, es una señal de que el build de commit podría hacerlo con otro test. En la medida de lo posible asegúrarte de que cualquier falla en la última etapa conduzca a nuevas pruebas en el build de commit que haya capturado el bug, para que el bug se conserve arreglado en el build de commit. Esta es la forma en que las pruebas de commit se fortalecen cada vez que algo las traspasa. Hay casos donde no hay forma de hacerle un build a una prueba de ejecución rápida que exponga el bug, así que puedes decidirte por hacer una prueba para esa condición sólo en el build secundario. La mayoría de las veces, por suerte, puedes agregar pruebas adecuadas al build de commit.

Este ejemplo es un pipeline de dos etapas, pero el principio básico puede extenderse a todos las etapas posteriores. Los builds luego del build de commit también pueden hacerse en paralelo, así que si tienes dos horas de pruebas secundarias puedes mejorar la capacidad de respuesta si tienes dos máquinas que ejecuten la mitad de la prueba cada una. Utilizando builds secundarios paralelos como estos, puedes introducir todo tipo de pruebas automatizadas nuevas, incluyendo test de performance en el proceso normal de build.

Prueba en un clon del entorno de producción

Lo importante de las pruebas es limpiar, bajo condiciones controladas, cualquier problema que el sistema tenga durante la producción. Una buena parte de esto es el entorno dentro del cual el sistema de producción se ejecutará. Si pruebas en un entorno diferente, cada diferencia resulta en el riesgo de pensar que lo que sucede bajo prueba no va a suceder en la producción.

Como resultado, quieres configurar el entorno de prueba para ser lo más parecido a un imitador del entorno de producción como sea posible: Utiliza el mismo software de base de datos con las mismas versiones y la misma versión de sistema operativo; coloca todas las bibliotecas que estén en el entorno de producción dentro del entorno de prueba, incluso si el sistema no las utiliza; utiliza las mismas direcciones IP y los mismos puertos, ejecútalos en el mismo hardware.

Bueno, en la realidad, tenemos límites. Si estás escribiendo software de computadora de escritorio, no es posible probar en un clon de cada una de las posibles computadoras con todo el software de terceros que diferentes personas estén ejecutando. Lo mismo con algunos entornos de producción que pueden ser prohibitivamente costosos de duplicar (aunque a menudo me he encontrado con economías falsas por no duplicar entornos moderadamente caros). Más allá de estos límites, tu objetivo debe seguir siendo duplicar el entorno de producción tanto como sea posible, y entender los riesgos que aceptas para cada diferencia entre la prueba y la producción.

Si tienes una instalación simple sin mucha comunicación complicada, quizás puedas ejecutar el build de commit en un entorno simulado. Sin embargo, a menudo necesitarás utilizar dobles de prueba ya que los sistemas responden de manera lenta o intermitente. Como resultado, es normal obtener un entorno muy artificial para las pruebas de velocidad de commit, y utilizar un clon de producción para pruebas secundarias.

He notado un mayor interés por utilizar la virtualización para facilitar la unión de los entornos de prueba. Las máquinas virtuales pueden salvarse con todos los elementos necesarios backapeados en la virtualización. Es entonces relativamente sencillo de instalar el build más reciente y ejecutar pruebas. Además, esto te permite ejecutar múltiples pruebas en una máquina, o simular múltiples máquinas en red en una sola máquina. Como está disminuyendo la penalización de la virtualización, esta opinión tiene mucho sentido.

Facilita que cualquiera pueda obtener el último archivo ejecutable

Una de las partes más difíciles del desarrollo de software es asegurarse de estar haciendo un build del software correcto. Para nosotros es muy difícil especificar por adelantado lo que quieres y estar en los cierto. La gente piensa que es mucho más fácil detectar algo que no está del todo bien y decir cómo necesita cambiar. Los procesos de desarrollo ágiles esperan explícitamente y se aprovechan de este comportamiento humano.

Para ayudar a que funcione, cualquier persona involucrada en un proyecto de software debería poder obtener el archivo ejecutable más reciente y poder ejecutarlo: para demostraciones, pruebas, o para ver qué ha cambiado en la semana.

Hacer esto es bastante sencillo: asegúrate que haya un lugar conocido donde la gente pueda encontrar el último archivo ejecutable. Puede ser útil colocar varios archivos ejecutables en esta tienda. Para la última, debes colocar el archivo ejecutable más reciente para pasar las pruebas de commit, ese archivo ejecutable debe ser bastante estable para proveer la serie de commits que sea razonablemente fuerte.

Si sigues el proceso con iteraciones bien definidas, generalmente es apropiado también colocar el final de los builds de iteración allí. Particularmente las demostraciones necesitan un software con características conocidas, con lo cual generalmente vale la pena sacrificar el más reciente por uno que el demostrador conozca cómo operarlo.

Todos pueden ver lo que está sucediendo

La integración continua es comunicación, así que tienes que asegurarte de que todos puedan ver fácilmente el estado del sistema y los cambios hechos en él.

Una de las cosas más importantes para comunicar es el estado del build de mainline. Si estás utilizando Cruise hay una web que mostrará si hay un build en curso y cuál era el estado del último build de mainline. A muchos equipos les gusta hacer esto aún más evidente mediante la conexión de una pantalla continua para el sistema de build, luces verdes si el build funciona o roja si las fallas son populares. Un toque particular son las lámparas de lava rojas y verdes. No sólo que estos indican el estado del build, sino también el tiempo que ha estado en ese estado. Las burbujas en la lámpara verde indican que el build está roto por mucho tiempo.

Cada equipo toma sus propias decisiones para esos sensores de build. Es bueno jugar con las elecciones; hace poco vi a alguien experimentando con un conejo danzarín.

Si estás utilizando un proceso de CI manual, la visibilidad sigue siendo esencial. El monitor de la máquina física de build puede mostrar el estatus del build de mainline. A menudo tienes un símbolo de build para colocar en el escritorio de cualquiera que esté haciendo el build en ese momento. Nuevamente, algo tonto como un pollo de goma es una buena opción. A menudo a la gente le gusta hacer un ruido simple en buenos builds, como hacer sonar una campana.

Las páginas web de los servidores CI contienen más información que esta, por supuesto. Cuirse provee una indicación no sólo de quiénes estén haciendo un build, sino también de qué cambios hicieron. Cruise también provee un historial de cambios que permite a los miembros del equipo tener una idea de la actividad reciente en el proyecto. Conozco equipos que guían a los que utilizan esto para entender lo que la gente está haciendo y mantener la lógica en los cambios del sistema.

Otra ventaja de utilizar un sitio web es que aquellos que no están en el mismo lugar pueden saber el estado del proyecto. En general, prefiero tener a todos trabajando activamente en un proyecto en el mismo lugar, pero a veces hay gente periférica a la que le gusta vigilar las cosas. También es útil para los grupos agregar información de build de muchos proyectos, proveer un estatus simple y automatizado de proyectos diferentes.

Los displays de buena información no son sólo los de la pantalla de la computadora. Uno de mis displays favoritos fue el de un proyecto que hice en CI. Estuvo inhabilitado para hacer builds estables por mucho tiempo. Colocamos un calendario en la pared que mostraba en pequeños cuadrados un año completo, un cuadrado para cada día. Cada día el grupo QA ponía una etiqueta verde en el día si habían recibido un build estable que hubiese pasado las pruebas de commit, de lo contrario, un cuadrado rojo. Con el tiempo el calendario reveló el estado del proceso de build, mostrando una mejora constante hasta que los cuadraditos verdes fueron tan comunes que el calendario desapareció. Cumplió su propósito.

Automatiza el deployment

Para hacer integración continua necesitas múltiples entornos: uno para ejecutar pruebas de commit, uno o más para ejecutar pruebas secundarias. Como mueves archivos ejecutables entre estos entornos muchas veces al día, vas a querer hacerlo de forma automática. Así que es importante tener scripts que te permitan deployar fácilmente la aplicación en cualquier entorno.

Una consecuencia de esto es que también debes tener scripts que te permitan deployar en la producción con la misma facilidad. No puedes deployar en una producción cada día (aunque he ejecutado proyectos que sí lo hacen), pero el deployment automático ayuda tanto a acelerar el proceso como a reducir errores. También es una opción económica ya que sólo utiliza las mismas capacidades que tú utilizas para deployar en los entornos de prueba.

Muchas personas se preocupan por cómo lidiar con bases de datos con versiones frecuentes. Pramod Sadalage y yo escribimos este artículo explicando cómo manejar esto con refactoreo automatizado y migración de bases de datos.

Si deployas en producción de forma automátizada, debes considerar que es un rollback automático. Suceden cosas malas de vez en cuando; y si algo huele mal, es bueno ser capaz de volver rápidamente al último estado correcto conocido. Poder ser capaz de revertir automáticamente, también reduce mucha de la tensión del deployment, dando valor a la gente para deployar más frecuentemente y en consecuencia obtener rápidamente características nuevas de los usuarios. La comunidad “Ruby on Rails” desarrolló una herramienta llamada Capristano que es un buen ejemplo de herramienta que hace este tipo de cosas.

En entornos clusterizados he visto rolling deployments donde el software nuevo es deployado de a un nodo por vez, remplazando gradualmente la aplicación en el transcurso de un par de horas.
Una variación particularmente interesante de esto es que me he encontrado con que la aplicación web pública es la idea de deployar un build de ensayo a un subconjunto de usuarios. Entonces el equipo ve cómo el build de ensayo se usa antes de decidir si deployarlo para toda la población de usuarios. Esto te permite probar nuevas características e interfaces de usuarios antes de commitear una opción final. Es esencial hacer funcionar el deployment automatizado relacionado con la buena disciplina de CI.

Beneficios de la integración continua

En general, creo que la mejor y más grande ventaja de la integración continua es la reducción del riesgo. Todavía pienso en aquel primero proyecto de software que mencioné en el primero párrafo. Allí estaban al final (eso esperaban) de un proyecto a largo plazo, pero sin tener ni idea real de cuánto tiempo pasaría antes de que estuviera completo.

El problema con la integración diferida es que es muy difícil de predecir cuánto tiempo tardará en completarse, y lo que es peor, es muy difícil de ver dónde estás durante el proceso. Entonces te colocas en un punto ciego en uno de los momentos más tensos del proyecto; incluso estando en un caso raro donde todavía no estás retrasado.

La integración continua resuelve completamente este problema. No hay integración larga y eliminas por completo el punto ciego, siempre sabes en qué punto estás, qué funciona y qué no, los bugs pendientes en tu sistema.

Los bugs, esas cosas horrendas que destruyen la confianza y desordenan cronogramas y reputaciones. Los bugs en un software deployado hace que los usuarios se enojen contigo; los bugs en el proceso de trabajo se meten en tu camino, dificultando mantener el resto del software funcionando correctamente.

La integración continua no se deshace de los bugs, pero sí facilita mucho encontrarlos y eliminarlos. En este aspecto, es preferible a un código auto-testeante. Si introduces un bug y lo detectas rápidamente es mucho más fácil deshacerse de él. Como sólo has cambiado una pequeña parte del sistema, no tienes que buscar más. Como esa parte del sistema es la parte donde trabajabas, está fresco en tu memoria (nuevamente: haciendo más fácil de encontrar el bug. También puedes utilizar diff debugging) comparando la versión actual del sistema con una anterior que no tenía el bug.

Los bugs también son acumulativos; cuantos más bugs tienes, más difícil es remover cada uno de ellos. Esto es en parte porque generas interacción entre los bugs, dónde las fallas aparecen como resultado de múltiples errores, haciendo muy difícil el poder encontrarlas. También es psicológico: la gente pone menos energía en encontrar y deshacerse de los bugs cuando son muchos; un fenómeno que los programadores más pragmáticos llaman el síndrome de las ventanas rotas.

En consecuencia, los proyectos con integración continua tienden a tener muchos menos bugs tanto en la producción como en el proceso. Sin embargo, debo hacer hincapié en que el grado de este beneficio es directamente proporcional a cuán buena sea la serie de pruebas. Debes saber que no es muy difícil hacer un build en una serie de pruebas que marquen la diferencia. De todos modos, generalmente toma un tiempo que el equipo realmente alcance el bajo nivel de bugs que potencialmente puede alcanzar. Obtenerlo es el resultado del trabajo constante y las mejoras en las pruebas.

Si tienes integración continua, ésta eliminará una de las barreras más grandes del deployment frecuente, el cual es valioso porque permite a los usuarios obtener nuevas características de forma más rápida, hacer un feedback más rápido de ellas, y generalmente volverse más colaborativos en el ciclo de desarrollo. Esto ayuda a romper las barreras entre clientes y desarrollo, barreras que creo son las más grandes para un desarrollo de software exitoso.

Introduce la integración continua

Entonces, tienes ganas de probar la integración continua, por dónde empezar? En conjunto completo de prácticas que he esbozado antes te dan todos los beneficios, pero no es necesario comenzar con todas ellas.

No hay una receta para esto, depende mucho de la naturaleza de tu instalación y de tu equipo; pero aquí hay algunas cosas que hemos aprendido para que funcione.

Uno de los primeros pasos es automatizar el build, llevar todo lo necesario a un control de fuente para que puedas construir todo el sistema con un solo comando. Esto no es una tarea menor para muchos proyectos, pero es esencial para que funcione cualquier otra cosa. Al comienzo puede que sólo hagas builds cuando lo necesites ocasionalmente, o sólo hagas un build automatizado a la noche. Si bien no es una integración continua, un build automatizado a la noche es un buen paso en el proceso.

Introduce pruebas automáticas en tu build. Intenta identificar las áreas más grandes donde las cosas no funcionen y utiliza pruebas automatizadas para descubrir esas fallas. En un proyecto existente, es particularmente difícil de hacer que una buena serie de pruebas funcionen de forma rápida; toma tiempo crear estas pruebas, pero por algún lado tienes que comenzar, y todos esos clichés sobre el cronograma aplicado en la construcción de Roma.

Intenta acelerar el build de commit. La integración continua en un build de un par de horas es mejor que nada, pero bajarlo a esos mágicos diez minutos es mucho mejor. Generalmente requiere de un poco de cirugía en el código base cuando rompes las dependencias en las partes más lentas del sistema.

Si estás comenzando un proyecto nuevo, comienza con la integración continua desde el principio. Vigila los tiempos de build y toma medidas en cuanto empieces a ir más lento que la regla de diez minutos. Actuando rápidamente harás las reestructuraciones necesarias antes de que el código base se vuelva tan grande que se transforme en una molestia mayor.

Además, pide ayuda; busca a alguien que ya haya hecho integración continua para que te ayude. Como toda nueva técnica, es difícil comenzar a utilizarla cuando no sabes cómo será el resultado final. Quizás tener un mentor cueste dinero, pero también pagarás la pérdida de tiempo y productividad si no lo haces. (Aviso: Sí, en ThoughtWorks damos asesoramiento en esta área. Después de todo, nosotros ya cometimos todos los errores por cometer).

Conclusiones

Durante estos años, desde que Matt y yo escribimos el artículo original, la integración continua se ha vuelto una técnica habitual para el desarrollo de software. Casi ningún proyecto de ThoughtWorks se realiza sin CI, y vemos que otros la utilizan en todo el mundo. Apenas he oído cosas negativas sobre el enfoque - a diferencia de algunas de las prácticas más controvertidas en la programación extrema.

Si no estás interesado en utilizar integración continua, te recomiendo que lo pruebes, Y si lo estás, quizás aquí hay algunas ideas que puedan ayudarte a hacerlo de manera más efectiva. Hemos aprendido mucho acerca de integración continua en los últimos años, espero que haya algo más que aprender y mejorar.