Integración continua

Integración Continua

Traducción del articulo "Continuous Integration" de Martin Fowler

Artículo original: https://www.martinfowler.com/articles/continuousIntegration.html

Fecha de la traducción: 30 de Abril de 2018


La integración continua es una práctica de desarrollo de software donde los miembros de un equipo integran su trabajo con frecuencia, por lo general cada persona integra al menos diariamente, lo que lleva a integraciones múltiples por día. Cada integración se verifica mediante una compilación automatizada (incluido el test) para detectar errores de integración lo más rápido posible. Muchos equipos encuentran que este enfoque conduce a problemas de integración significativamente reducidos y permite a un equipo desarrollar software cohesivo más rápidamente. Este artículo es una descripción general rápida de la integración continua que resume la técnica y su uso actual.

Recuerdo vívidamente uno de mis primeros avistamientos de un gran proyecto de software. Estaba haciendo una pasantía de verano en una gran empresa inglesa de electrónica. Mi gerente, parte del grupo de control de calidad, me dio un recorrido por el lugar y entramos en un gran almacén deprimente repleto de box's. Me dijeron que este proyecto había estado en desarrollo durante un par de años y que actualmente se estaba integrando, y se había estado integrando durante varios meses. Mi guía me dijo que nadie sabía realmente cuánto tardaría en terminar de integrarse. De esto aprendí una historia común de proyectos de software: la integración es un proceso largo e impredecible.

Pero este no es el camino. La mayoría de los proyectos realizados por mis colegas en ThoughtWorks, y por muchos otros en todo el mundo, tratan la integración como un no evento. El trabajo individual de cualquier desarrollador está a solo unas pocas horas de tener un estado donde el proyecto este compartido y puede integrarse de nuevo en ese estado en minutos. Cualquier error de integración se encuentra rápidamente y puede corregirse rápidamente.

Este contraste no es el resultado de una herramienta costosa y compleja. La esencia de esto radica en la práctica simple de que todos en el equipo se integren con frecuencia, generalmente a diario, en un repositorio controlado.

Cuando describí esta práctica a las personas, es común que encuentre dos reacciones: "no puede funcionar (aquí)" y "hacerlo no hará mucha diferencia". Lo que la gente descubre cuando lo intentan es que es mucho más fácil de lo que parece y que marca una gran diferencia para el desarrollo. Por lo tanto, la tercera reacción común es "sí, lo hacemos, ¿cómo pudimos vivir sin eso? ".

El término 'Integración Continua' se originó con el proceso de desarrollo Extreme Programming, como una de sus doce prácticas originales. Cuando comencé en

ThoughtWorks, como consultor, alenté en el proyecto con el que estaba trabajando para usar la técnica. Matthew Foemmel convirtió mis vagas exhortaciones en acciones sólidas y vimos que el proyecto iba desde integraciones raras y complejas hasta el no evento que describí. Matthew y yo escribimos nuestra experiencia en la versión original de este documento, que ha sido uno de los más populares en mi sitio web.

Si bien la integración continua es una práctica que no requiere herramientas específicas para implementarla, hemos descubierto que es útil usar un servidor de integración continua. El servidor más conocido es CruiseControl, una herramienta de código abierto originalmente construida por varias personas en ThoughtWorks y ahora mantenida por una amplia comunidad. Desde entonces, han aparecido varios otros servidores de CI, tanto de código abierto como comercial, incluido Cruise de ThoughtWorks Studios.

Crear un feature con integración continua

La manera más fácil para mí de explicar qué es CI es mostrar un rápido ejemplo de cómo funciona con el desarrollo de una característica pequeña. Supongamos que tengo que hacer algo con un software, en realidad no importa cuál es la tarea, por el momento supongo que es pequeño y puede hacerse en unas pocas horas. (Exploraremos tareas más largas y otros problemas más adelante).

Empiezo por tomar una copia del código fuente actual en mi máquina de desarrollo local. Hago esto usando un repositorio de control de versiones comprobando que la copia de trabajo sea desde el branch principal.

El párrafo anterior tendrá sentido para las personas que usan sistemas de control de código fuente, pero será un galimatías para quienes no lo hacen. Así que permítanme explicarlo rápidamente. Un sistema de control de versiones mantiene todo el código fuente de un proyecto en un repositorio. El estado actual del sistema generalmente se conoce como el 'branch principal'. En cualquier momento, un desarrollador puede hacer una copia del branch principal en su propia máquina, esto se denomina 'checking out'. La copia en la máquina del desarrollador se llama 'working copy'. (La mayoría de las veces actualizas tu copia de trabajo al branch principal, en la práctica es lo mismo). Ahora tomo mi copia de trabajo y hago todo lo que tengo que hacer para completar mi tarea. Esto consiste en alterar el código de producción y también agregar o cambiar las pruebas automatizadas. La integración continua supone un alto grado de pruebas que se automatizan en el software: una habilidad que llamo self-testing code. A menudo, estos utilizan una versión de los populares frameworks de prueba XUnit.

Una vez que termino (y generalmente en varios momentos cuando estoy trabajando) llevo a cabo una compilación automatizada en mi máquina de desarrollo. Esto toma el código fuente en mi copia de trabajo, lo compila y lo vincula a un archivo ejecutable, y ejecuta las pruebas automatizadas. Solo si todo se desarrolla y prueba sin errores, la construcción general se considera buena.

Con una buena compilación, puedo pensar en subir mis cambios al repositorio. El giro, por supuesto, es que otras personas pueden, y generalmente lo han hecho, hacer cambios en el branch principal antes de que yo tenga la oportunidad de hacer commit. Así que primero actualizo mi copia de trabajo con sus cambios y reconstruyo. Si sus cambios tienen conflictos con mis cambios, se manifestará como una falla en la compilación o en las pruebas. En este caso, es mi responsabilidad arreglar esto y repetir hasta que pueda crear una copia de trabajo que esté sincronizada correctamente con el branch principal.

Una vez que he hecho mi propia compilación de una copia de trabajo correctamente sincronizada, puedo finalmente realizar commit de mis cambios en el branch principal, que luego actualiza en el repositorio.

Sin embargo, no termina mi trabajo en el commit. En este punto, volvemos a construir, pero esta vez en una máquina de integración basada en el código del branch principal. Solo cuando esta construcción tenga éxito podemos decir que mis cambios han terminado. Siempre hay una posibilidad de que se haya perdido algo en mi máquina y el repositorio no se haya actualizado correctamente. Solo cuando los cambios que subí al repositorio tengan un build exitoso, mi trabajo ha terminado. Este build de integración puede ser ejecutado manualmente por mí o hacerlo automáticamente Cruise.

Si se produce un conflicto entre dos desarrolladores, generalmente se detecta por el segundo desarrollador cuando va a actualizar su copia de trabajo. Si no, el build de integración debería fallar. De cualquier manera, el error se detecta rápidamente. En este punto, la tarea más importante es arreglarlo y hacer que el build funcione correctamente otra vez. En un entorno de Integración continua, nunca debe haber fallas en el build de una integración por mucho tiempo. Un buen equipo debe tener muchos builds correctos por día. Las versiones incorrectas ocurren de vez en cuando, pero deben corregirse rápidamente.

El resultado de hacer esto es que hay una pieza estable de software que funciona correctamente y contiene pocos errores. Todo el mundo desarrolla esa base estable compartida y nunca se aleja tanto de esa base que lleva mucho tiempo integrarse con ella. Se gasta menos tiempo tratando de encontrar errores porque aparecen rápidamente.

Prácticas de integración continua

La historia anterior es una descripción general de CI y cómo funciona en el día a día. Conseguir que todo esto funcione sin problemas es obviamente más que eso. Me centraré ahora en las prácticas clave que conforman un CI efectivo.

Mantené un repositorio único

Los proyectos de software implican muchos archivos que deben organizarse juntos para construir un producto. Hacer un seguimiento de todos ellos es un gran esfuerzo, especialmente cuando hay varias personas involucradas. Entonces, no es sorprendente que a lo largo de los años los equipos de desarrollo de software hayan construido herramientas para gestionar todo esto. Estas herramientas, llamadas herramientas de administración de código fuente, administración de configuración, sistemas de control de versiones, repositorios u otros nombres, son 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 raro, pero me encuentro con proyectos que no usan dichos sistemas y usan alguna combinación desordenada de discos locales y compartidos.

Entonces, como base simple, asegúrese de obtener un sistema de administración de código fuente decente.

El costo no es un problema ya que hay herramientas open source disponibles de buena calidad. El repositorio open source actual es Subversion. (La antigua herramienta CVS todavía se usa ampliamente, y es mucho mejor que nada, pero Subversion es la opción más moderna).

Curiosamente, mientras hablo con los desarrolladores, sé que la mayoría de las herramientas de administración de código fuente comerciales les gusta menos que Subversion. La única herramienta que he escuchado consistentemente que la gente dice que vale la pena pagar es Perforce.

Una vez que obtenga un sistema de administración de código fuente, asegúrese de que sea el lugar conocido para que todos obtengan el código fuente. Nadie debería preguntar "¿dónde está el archivo fool-whiffle?" Todo debería estar en el repositorio.

Aunque muchos equipos usan repositorios, un error común que veo es que no ponen todo en el repositorio. Si las personas usan uno, pondrán el código ahí, pero todo lo que necesita una compilación debe estar ahí, incluidos: scripts de prueba, archivos de properties, esquema de base de datos, scripts de instalación y libraries de terceros. He conocido proyectos que verifican sus compiladores en el repositorio (importante en los primeros días de los compiladores de C ++). La regla básica es que se debe poder en una máquina virgen, hacer un checkout y construir completamente el sistema. Solo debe haber una cantidad mínima de cosas en la máquina virgen, generalmente cosas grandes, complicadas de instalar y estables. Un sistema operativo, un entorno de desarrollo Java o un sistema base de bases de datos son ejemplos típicos.

Se debe poner todo lo necesario para una compilación en el sistema de control, sin embargo, también puede incluir otras cosas con las que las personas generalmente trabajan. Las configuraciones del IDE son buenas para poner ahí porque de esa manera es fácil para las personas compartir las mismas configuraciones.

Una de las características de los sistemas de control de versiones es que les permiten crear múltiples branches para manejar diferentes flujos de desarrollo. Esta es una característica útil, no esencial, pero con frecuencia se usa en exceso y causa problemas. Mantenga el uso de los branches al mínimo. En particular, tienen una línea principal: un solo branch del proyecto actualmente en desarrollo. Casi todo el mundo debería trabajar fuera de este branch principal la mayor parte del tiempo. (Ramas razonables son correcciones de errores de lanzamientos de producción anteriores y experimentos temporales.)

En general, debe almacenar todo lo que necesita para construir cualquier cosa, para los compilados. Algunas personas mantienen archivos ya compilados en el repositorio, pero considero que esto es un problema más profundo, generalmente una incapacidad para recrear builds de manera confiable.

Automatizá el build

Hacer que los fuentes se conviertan en un sistema ejecutable a menudo puede ser un proceso complicado que involucra compilación, mover archivos, cargar esquemas en las bases de datos, etc. Sin embargo, como la mayoría de las tareas en esta parte del desarrollo de software, se puede automatizar.

Pedirle a la gente que escriba comandos extraños o que haga clic en los cuadros de diálogo es una pérdida de tiempo y un caldo de cultivo para cometer errores.

Los entornos automatizados para los builds son una característica común de los sistemas. El mundo de Unix lo ha hecho durante décadas, la comunidad Java ha desarrollado Ant, la comunidad .NET ha tenido Nant y ahora tiene MSBuild. Estar seguro de que se puede compilar e iniciar su sistema utilizando estos scripts con un solo comando.

Un error común es no incluir todo en la compilación automatizada. La compilación debe incluir obtener el esquema de la base de datos del repositorio y activarlo en el entorno de ejecución. Elaboraré mi regla de oro: cualquiera debería poder traer una máquina virgen, verificar las fuentes del repositorio, emitir un solo comando y tener un sistema ejecutable en su máquina.

Los scripts de compilación vienen en varios sabores y, a menudo, son propios de una plataforma o comunidad, pero no tienen que serlo. Aunque la mayoría de nuestros proyectos Java usan Ant, algunos han usado Ruby (el sistema Ruby Rake es una herramienta de script de construcción muy agradable). Obtuvimos mucho valor al automatizar un proyecto de Microsoft COM inicial con Ant.

Una compilación grande a menudo lleva tiempo, no desea hacer todos estos pasos si solo ha realizado un pequeño cambio. Entonces, una buena herramienta de compilación analiza qué se debe cambiar como parte del proceso. La forma más común de hacerlo es verificar las fechas de los archivos de origen y compilar solo si la fecha de origen es posterior. Las dependencias se vuelven complicadas: si un archivo de objeto cambia los que dependen de él también pueden ser necesario reconstruirlos. Los compiladores pueden manejar este tipo de cosas, o pueden no hacerlo.

Dependiendo de lo que necesite, es posible que hagan falta diferentes tipos de cosas para construir. Puede construir un sistema con o sin código de prueba, o con diferentes conjuntos de test's. Algunos componentes se pueden construirse stand-alone. Un script de compilación debería permitirle crear objetivos alternativos para diferentes casos.

Muchos de nosotros usamos IDEs, y la mayoría de los IDEs tienen algún tipo de proceso de administración de compilación dentro de ellos. Sin embargo, estos archivos siempre son propiedad del IDE y, a menudo, frágiles.

Además, necesitan que el IDE funcione. Está bien que los usuarios de IDE configuren sus propios archivos de proyecto y los utilicen para el desarrollo individual. Sin embargo, es esencial tener una compilación maestra que pueda utilizarse en un servidor y ejecutarse desde script's. Entonces, en un proyecto de Java estamos de acuerdo en que los desarrolladores construyan su IDE, pero la compilación maestra utiliza Ant para garantizar que se pueda ejecutar en el servidor de desarrollo.

Hace tu propia autoevaluación (self-testing)

Tradicionalmente, un build significa compilar, vincular y todas las cosas adicionales necesarias para que un programa se ejecute. Se puede ejecutar un programa, pero eso no significa que haga lo correcto.

Los lenguajes modernos de tipos estáticos pueden evitar muchos errores, pero muchos más se escapan.

Una buena forma de detectar errores de manera más rápida y eficiente es incluir pruebas automatizadas en el proceso de compilación. Las pruebas no son perfectas, por supuesto, pero pueden filtrar muchos errores, suficientes para ser útiles. En particular, el aumento de Extreme Programming (XP) y Test Driven Development (TDD) han hecho mucho para popularizar el código de autoevaluación y, como resultado, muchas personas han visto el valor de la técnica.

Los lectores habituales de mi trabajo sabrán que soy un gran admirador tanto de TDD como de XP, sin embargo quiero recalcar que ninguno de estos enfoques es necesario para obtener los beneficios del código de autoevaluación. Ambos enfoques hacen que sea conveniente escribir pruebas antes de escribir el código que las hace aprobar: en este modo, las pruebas tratan tanto de explorar el diseño del sistema como de capturar errores. Esto es algo bueno, pero no es necesario a los fines de la integración continua, donde tenemos el requisito más débil del código de autoevaluación. (Aunque TDD es mi forma preferida de producir código de autoevaluación).

Para el código de autoevaluación, necesita un conjunto de pruebas automatizadas que puedan verificar una gran parte de la base de códigos para detectar errores. Las pruebas deben poder iniciarse desde un comando simple y ser auto-chequeables. El resultado de ejecutar el conjunto de pruebas debe indicar si alguna prueba falló. Para que una compilación sea una autoevaluación, la falla de una prueba debería ocasionar que la compilación falle.

En los últimos años, el aumento de TDD ha popularizado la familia XUnit de herramientas open source que son ideales para este tipo de pruebas. Las herramientas de XUnit han demostrado ser muy valiosas para nosotros en ThoughtWorks y siempre sugiero a las personas que las usen. Estas herramientas, iniciadas por Kent Beck, facilitan la configuración de un entorno totalmente autoevaluado.

Las herramientas de XUnit son ciertamente el punto de partida para hacer que tu código sea self-testing. También debes buscar otras herramientas que se centren en más pruebas de extremo a extremo, hay un amplio rango de éstas por el momento, incluidas FIT, Selenium, Sahi, Watir, FITnesse y muchas otras que no estoy tratando de enumerar de manera exhaustiva aquí.

Por supuesto, no podes contar con pruebas vayan a encontrar todo. Como se ha dicho a menudo: las pruebas no prueban la ausencia de errores. Sin embargo, la perfección no es el único punto en el que se recupera la inversión para un build de self-testing. Las pruebas imperfectas, que se ejecutan con frecuencia, son mucho mejores que las pruebas perfectas que nunca se escriben.

Todos deben realizar commit's diarios en el branch principal

La integración se trata principalmente de comunicación. La integración permite a los desarrolladores informar a otros desarrolladores sobre los cambios que han realizado. La comunicación frecuente permite a las personas conocer rápidamente los cambios.

El requisito previo para que un desarrollador haga commit en el branch principal es que pueda hacer un build correcto su código. Esto, por supuesto, incluye pasar las pruebas de compilación. Al igual que con cualquier ciclo de commit, el desarrollador primero actualiza su copia de trabajo para que coincida con el branch principal, resuelve cualquier conflicto con este branch y luego compila en su máquina local. Si el build pasa, entonces son libres de hacer commit con el branch principal.

Al hacer esto con frecuencia, los desarrolladores descubren rápidamente si hay un conflicto entre dos desarrolladores. La clave para solucionar problemas rápidamente es encontrarlos rápidamente. Con los desarrolladores que hacen commit cada pocas horas, se puede detectar un conflicto unas pocas horas después de que ocurra, en ese momento no ha transcurrido mucho y es fácil de resolver. Los conflictos que no se detectan durante semanas pueden ser muy difíciles de resolver.

El hecho de que compiles cuando actualiza su copia de trabajo significa que detecta conflictos de compilación así como conflictos textuales. Como la compilación es self-testing, también detecta conflictos en la ejecución del código. Los últimos conflictos son errores especialmente difíciles de encontrar si permanecen sin detectarse durante mucho tiempo en el código. Como solo hay unas pocas horas entre commit's, hay muchos lugares donde el problema podría estar oculto. Además, dado que no ha cambiado mucho, puede usar diff-debugging como ayuda a encontrar el error.

Mi regla general es que cada desarrollador debe hacer commit en el repositorio todos los días. En la práctica, a menudo es útil si los desarrolladores hacen commit con más frecuencia que eso. Cuantas más veces commitees, tendrás menos lugares para buscar los errores y más rápido resolverás los conflictos.

Los commit's frecuentes alientan a los desarrolladores a dividir su trabajo en pequeños trozos de unas pocas horas cada uno. Esto ayuda a rastrear el progreso y proporciona una sensación de progreso. A menudo, las personas inicialmente sienten que no pueden hacer algo significativo en solo unas pocas horas, pero descubrimos que el mentoring y la práctica les ayuda a aprender.

Cada commit al branch principal debería compilar en una máquina de integración

Usando commit's diarios, un equipo obtiene compilaciones probadas con frecuencia. Esto debería significar que el branch principal se mantiene en un estado saludable. En la práctica, sin embargo, las cosas salen mal. Una razón es la disciplina, las personas que no hacen una actualización y compilación antes de commitear. Otro es las diferencias de ambiente entre las máquinas de los desarrolladores.

Como resultado, debe asegurarse de que las compilaciones regulares se realicen en una máquina de integración y solo si esta compilación de integración tiene éxito si se considera que la compilación se realizó. Dado que el desarrollador que hace commit es responsable de esto, ese desarrollador necesita monitorear la compilación delbranch principal para que pueda arreglarla si se rompe. Un corolario de esto es que no deberías irte a casa hasta que el build del branch principal haya pasado con las commit's que hayas agregado al final del día.

Hay dos formas principales para asegurar esto: usar una compilación manual o un servidor de integración continua.

El enfoque de compilación manual es el más simple de describir. Esencialmente, es algo similar a el build local que hace un desarrollador antes de hacer commit en el repositorio. El desarrollador va a la máquina de integración, revisa el encabezado del branch principal (que ahora alberga su último commit) y comienza la compilación de integración. Mantiene un ojo en su progreso, y si la construcción tiene éxito, ha terminado con su compromiso. (También vea la descripción de Jim Shore.)

Un servidor de integración continua actúa como un monitor para el repositorio. Cada vez que finaliza un commit contra el repositorio, el servidor verifica automáticamente los fuentes en la máquina de integración, inicia una compilación y notifica al autor del resultado de la compilación. El autor no termina hasta que recibe la notificación, generalmente un correo electrónico.

En ThoughtWorks, somos grandes entusiastas de los servidores de integración continua; de hecho, lideramos el desarrollo original de CruiseControl y CruiseControl.NET, los servidores open source ampliamente utilizados. Desde entonces, también creamos el servidor comercial Cruise CI. Usamos un servidor de CI en casi todos los proyectos que hacemos y estamos muy contentos con los resultados.

No todo el mundo prefiere usar un servidor de 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 aquí deben estar en juego para hacer una integración continua de manera efectiva. Pero igualmente muchos equipos que hacen CI encuentran que un servidor de CI es una herramienta útil.

Muchas organizaciones hacen builds regulares en un horario programado, como todas las noches. Esto no es lo mismo que una integración continua y no es suficiente. El objetivo de la integración continua es encontrar problemas tan pronto como sea posible. Las compilaciones nocturnas significan que los errores permanecen sin detectar durante todo un día antes de que alguien los descubra. Una vez que están en el sistema por tanto tiempo, lleva mucho tiempo encontrarlos y eliminarlos.

Arreglá inmediatamente las compilaciones rotas

Una parte clave de hacer una compilación continua es que si falla la construcción del branch principal, debe corregirse de inmediato. El objetivo de trabajar con CI es que siempre estás desarrollando una base estable conocida. No es malo que se rompa el build del branch principal, aunque si está sucediendo todo el tiempo, sugiero que las personas no están siendo lo suficientemente cuidadosas con la actualización y construcción local antes de un coomit. Sin embargo, cuando se rompe la compilación principal, es importante que se solucione rápidamente.

Una frase que recuerdo que Kent Beck utilizó fue "nadie tiene una tarea de mayor prioridad que arreglar el build". Esto no significa que todos en el equipo tengan que detener lo que están haciendo para arreglar el build, generalmente solo necesitan un par de personas para que las cosas funcionen nuevamente. Significa una priorización consciente de una corrección del build como una tarea urgente y de alta prioridad.

A menudo, la manera más rápida de arreglar la compilación es revertir el último commit desde el branch principal, llevando el sistema a la última compilación conocida. Ciertamente, el equipo no debería intentar realizar ningún debugging en el branch principal roto. A menos que la causa de la rotura sea obvia, solo revertir el branch principal y solucionar el problema en una workstation de desarrollo.

Para ayudar a evitar romper el branch principal, podría considerar usar pending head.

Cuando los equipos introducen IC, a menudo esta es una de las cosas más difíciles de resolver. Al principio, un equipo puede tener dificultades para adoptar el hábito habitual de trabajar compilaciones principales, especialmente si están trabajando en una base de códigos existente. La paciencia y la aplicación constante parece hacer el truco, así que no te desanimes.

Mantené una compilación rápida

El objetivo de la integración continua es proporcionar una respuesta rápida. Nada absorbe la sangre de una actividad de CI más que una compilación que lleva mucho tiempo. Aquí debo admitir cierta diversión de tipo viejo y malhumorado en lo que se considera un build largo. La mayoría de mis colegas consideran que una compilación que demora una hora es totalmente irracional. Recuerdo que los equipos soñaban con obtenerlo tan rápido, y de vez en cuando todavía nos topamos con casos en los que es muy difícil obtener compilaciones a esa velocidad.

Para la mayoría de los proyectos, sin embargo, la guía de XP de un build de diez minutos está perfectamente dentro de lo razonable. La mayoría de nuestros proyectos modernos logran esto. Vale la pena esforzarse para que esto suceda, porque cada minuto que reduces el tiempo del build es un minuto ahorrado para cada desarrollador cada vez que hace commit. Como CI exige compilaciones frecuentes, esto se traduce en mucho tiempo.

Si observas un tiempo de compilación de una hora, llegar a una compilación más rápida puede parecer una perspectiva desalentadora. Incluso puede ser desalentador trabajar en un nuevo proyecto y pensar cómo mantener las cosas rápido. Para las aplicaciones empresariales, al menos, hemos encontrado que el cuello de botella habitual es el test, en particular las pruebas que involucran servicios externos, como una base de datos.

Probablemente, el paso más importante es comenzar a trabajar en la configuración de una deployment pipeline. La idea detrás de deployment pipeline (también conocida como build pipeline (canalización de compilación) o staged build (compilación por etapas)) es que de hecho hay varias compilaciones hechas en secuencia. El commit en el branch principal desencadena la primera compilación, lo que llamo commit build. El commit build es la compilación que se necesita cuando alguien hace commit en el branch principal. El commit build es el que se debe hacer rápidamente, como resultado se necesitarán varios atajos que reducirán la capacidad de detectar errores. El truco es equilibrar las necesidades de búsqueda de errores y velocidad para que un buen commit build sea lo suficientemente estable como para que otras personas puedan trabajar.

Una vez que el commit build es bueno, otras personas pueden trabajar en el código con confianza. Sin embargo, hay otras pruebas más lentas que se pueden comenzar a hacer. Máquinas adicionales pueden ejecutar rutinas de test que demoran más.

Un ejemplo simple de esto es un deployment pipeline en dos etapas. La primera etapa haría la compilación y ejecuta tests que son pruebas de unidades más localizadas con la base de datos completamente apagada. Dichas pruebas pueden ejecutarse muy rápido, manteniéndose dentro de la guía de diez minutos. Sin embargo, no se encontrarán errores que impliquen interacciones a mayor escala, particularmente aquellos que involucran la base de datos real. La compilación de la segunda etapa ejecuta un conjunto diferente de pruebas que llegan a la base de datos real e implican un comportamiento más integral. Esta suite puede tomar un par de horas para ejecutarse.

En este escenario, las personas usan la primera etapa como compilación del commit y la usan como su ciclo de CI principal. La compilación de la segunda etapa se ejecuta cuando puede, tomando el ejecutable de la última compilación buena para realizar más pruebas. Si esta compilación secundaria falla, es posible que no tenga la misma calidad de "detener todo", pero el objetivo del equipo es corregir esos errores lo más rápido posible, manteniendo la compilación del commit ejecutándose. Como ocurre en el ejemplo, las compilaciones posteriores a menudo son pruebas puras, ya que en la actualidad suelen ser las pruebas las que causan la lentitud.

Si la compilación secundaria detecta un error, eso es una señal de que la compilación del commit podría tener que hacerse con otro test. En la medida de lo posible, querrás asegurarte de que cualquier falla en una etapa posterior conduzca a nuevas pruebas en la compilación de commit que habría atrapado el error, por lo que el error permanece fijo en la compilación del commit. De esta forma, las pruebas del commit se fortalecen cada vez que algo pasa de largo. Hay casos en los que no hay forma de crear una prueba rápida que expone el error, por lo que se puede decidir probar solo esa condición en la compilación secundaria. La mayoría de las veces, afortunadamente, puede agregar pruebas adecuadas a la compilación del commit.

Este ejemplo es de un two-stage pipeline, pero el principio básico se puede extender a cualquier cantidad de etapas posteriores. Los builds posteriores a la compilación del commit también se pueden realizar en paralelo, por lo que si tiene dos horas de pruebas secundarias, puede mejorar la capacidad de respuesta al tener dos máquinas que ejecutan la mitad de las pruebas cada una. Mediante el uso de compilaciones secundarias paralelas como esta, puede introducir todo tipo de pruebas automáticas adicionales, incluidas las pruebas de rendimiento, en el proceso de compilación habitual.

Test en un ambiente Clon de producción

La finalidad del testing es eliminar, bajo condiciones controladas, cualquier problema que el sistema tenga en producción. Una parte importante de esto es el entorno en el que se ejecutará el sistema de producción. Si prueba en un entorno diferente, cada diferencia resulta en un riesgo de que lo que sucede bajo prueba no ocurra en la producción.

Como resultado, se desea configurar su entorno de testing para que sea lo más similar posible al entorno de producción. Utilice el mismo software de base de datos, con las mismas versiones, use la misma versión de sistema operativo. Coloque todas las bibliotecas adecuadas que se encuentran en el entorno de producción, en el entorno de prueba, incluso si el sistema no las usa. Use las mismas direcciones IP y puertos, ejecútelo en el mismo hardware.

Bueno, en realidad hay límites. Si está escribiendo software de escritorio, no es factible probar en un clon de todos los escritorios posibles con todo el software de terceros que ejecutan distintas personas. De manera similar, algunos entornos de producción pueden ser prohibitivamente costosos de duplicar (aunque a menudo he encontrado economías falsas al no duplicar entornos moderadamente costosos). A pesar de estos límites, su objetivo debe ser duplicar el entorno de producción tanto como pueda, y comprender los riesgos que está aceptando por cada diferencia entre prueba y producción.

Si tiene una configuración bastante simple sin muchas comunicaciones incómodas, es posible que pueda ejecutar su compilación del commit en un entorno imitado. Sin embargo, a menudo es necesario utilizar TestDouble porque los sistemas responden lenta o intermitentemente. Como resultado, es común tener un entorno muy artificial para las pruebas de velocidad, y usar un clon de producción para las pruebas secundarias.

He notado un creciente interés en usar la virtualización para facilitar la creación de entornos de prueba. Las máquinas virtualizadas se pueden guardar con todos los elementos necesarios integrados en la virtualización. Entonces es relativamente sencillo instalar las últimas pruebas de compilación y ejecución. Además, esto puede permitirle ejecutar múltiples pruebas en una máquina, o simular múltiples máquinas en una red en una sola máquina. A medida que la penalización de rendimiento de la virtualización disminuye, esta opción tiene cada vez más sentido.

Facilitá el acceso de cualquier persona al último ejecutable

Una de las partes más difíciles del desarrollo de software es asegurarse de construir el software adecuado. Descubrimos que es muy difícil especificar lo que desea por adelantado y ser correcto; a las personas les resulta mucho más fácil ver algo que no está del todo bien y decir cómo debe cambiarse. Los procesos de desarrollo ágiles esperan y aprovechan explícitamente esta parte del comportamiento humano.

Para ayudar a que esto funcione, cualquier persona involucrada en un proyecto de software debería poder obtener el último ejecutable y poder ejecutarlo: para demostraciones, pruebas exploratorias o simplemente para ver qué cambió esta semana.

Hacer esto es bastante sencillo: asegúrese de que haya un lugar conocido donde las personas puedan encontrar el último ejecutable. Puede ser útil poner varios ejecutables en dicha tienda. Para lo último, debe colocar el ejecutable más reciente para pasar las pruebas del commit; dicho ejecutable debería ser bastante estable, siempre que el paquete del commit sea razonablemente sólido.

Si está siguiendo un proceso con iteraciones bien definidas, generalmente es conveniente poner también las compilaciones finales de las iteraciones ahí. Las demostraciones, en particular, necesitan un software cuyas funciones sean familiares, por lo que generalmente vale la pena sacrificar lo último por algo que la persona encargada de la demo sabe cómo operar.

Todos pueden ver lo que está pasando

La integración continua tiene que ver con la comunicación, por lo que debe asegurarse de que todos puedan ver fácilmente el estado del sistema y los cambios que se le han realizado.

Una de las cosas más importantes para comunicar es el estado del build principal. Si está utilizando Cruise, hay un sitio web incorporado que le mostrará si hay un build en progreso y cuál fue el estado de la último build del branch principal. A muchos equipos les gusta hacer que esto sea aún más evidente al conectar una pantalla continua al sistema de compilación: las luces que brillan en verde cuando la compilación funciona, o las rojas si fallan son populares. Un toque particularmente común son las lámparas de lava rojas y verdes; no solo indican el estado de la compilación, sino también cuánto tiempo ha estado en ese estado. Las burbujas en una lámpara roja indican que la compilación se ha roto por mucho tiempo. Cada equipo toma sus propias decisiones con estos sensores de build: es bueno ser travieso con tu elección (recientemente vi a alguien experimentando con un conejo danzante).

Si está utilizando un proceso manual de CI, esta visibilidad sigue siendo esencial. El monitor de la máquina de compilación física puede mostrar el estado de la compilación principal. A menudo tienes un token de compilación para poner sobre el escritorio de quien está haciendo la build (de nuevo, algo tonto como un pollo de goma es una buena opción). A menudo a las personas les gusta hacer un simple ruido en las buenas compilaciones, como tocar la campana.

Las páginas web de los servidores de CI pueden llevar más información que esta, por supuesto. Cruise proporciona una indicación no solo de quién está compilando, sino qué cambios hicieron. Cruise también proporciona un historial de cambios, lo que permite a los miembros del equipo tener una buena idea de la actividad reciente en el proyecto. Conozco a los líderes del equipo a los que les gusta usar esto para hacerse una idea de lo que la gente ha estado haciendo y tener una idea de los cambios en el sistema.

Otra ventaja de utilizar un sitio web es que aquellos que no comparten la ubicación pueden tener una idea del estado del proyecto. En general, prefiero que todos trabajen activamente en un proyecto juntos, pero a menudo hay personas periféricas a quienes les gusta vigilar las cosas. También es útil para que los grupos agreguen información de compilación de múltiples proyectos, proporcionando un estado simple y automatizado de diferentes proyectos.

Las pantallas de buena información no son solo aquellas en las pantallas de una computadora. Una de mis presentaciones favoritas fue para un proyecto que estaba entrando en CI. Tenía una larga historia de incapacidad para compilar estructuras estables. Pusimos un calendario en la pared que mostraba un año completo con un pequeño cuadrado para cada día. Todos los días, el grupo de control de calidad ponía un sticker verde el día si habían recibido una compilación estable que pasara las pruebas de commit, de lo contrario, un cuadrado rojo. Con el tiempo, el calendario reveló el estado del proceso de compilación que mostraba una mejora constante hasta que los cuadrados verdes eran tan comunes que el calendario desapareció, se cumplió su propósito.

Automatizar el deploy

Para realizar la Integración Continua necesita múltiples entornos, uno para ejecutar pruebas de commit, uno o más para ejecutar pruebas secundarias. Como mueve los archivos ejecutables entre estos entornos varias veces al día, querrás hacer esto automáticamente. Por lo tanto, es importante tener scripts que le permitan deployar la aplicación en cualquier entorno fácilmente.

Una consecuencia natural de esto es que también debe tener scripts que le permitan deployar en producción con facilidad. Es posible que no esté implementando en producción todos los días (aunque me he encontrado con proyectos que sí lo hacen), pero el deploy automático ayuda a acelerar el proceso y reducir los errores. También es una opción económica, ya que solo utiliza las mismas capacidades que se usa para implementar entornos de prueba.

Si deploya en producción una capacidad automatizada adicional que debe considerar es el rollback automatizado. De vez en cuando, suceden cosas malas, y si las malolientes sustancias marrones golpean el metal que gira, es bueno poder volver rápidamente al último buen estado conocido. La posibilidad de revertir automáticamente también reduce la tensión de la implementación, lo que anima a las personas a deployar con mayor frecuencia y, por lo tanto, les proporciona nuevas funciones rápidamente a los usuarios. (La comunidad de Ruby on Rails desarrolló una herramienta llamada Capistrano que es un buen ejemplo de una herramienta que hace este tipo de cosas).

En entornos de clúster, he visto despliegues sucesivos en los que el nuevo software se deploya en un nodo a la vez, reemplazando gradualmente la aplicación en el transcurso de unas pocas horas.

Una variación particularmente interesante de esto que he encontrado con la aplicación web pública es la idea de implementar una versión de prueba para un subconjunto de usuarios. Luego, el equipo ve cómo se usa la compilación de prueba antes de decidir si implementarla en la población total de usuarios. Esto le permite probar nuevas funciones e interfaces de usuario antes de comprometerse con una elección final. El deploy automatizado, vinculada a una buena disciplina de CI, es esencial para hacer que esto funcione.

Beneficios de la integración continua

En general, creo que el mayor y más amplio beneficio de la integración continua es la reducción del riesgo. Mi mente todavía flota en el primer proyecto de software que mencioné en mi primer párrafo. Allí estaban al final (esperaban) de un proyecto largo, pero sin una idea real de cuánto tiempo pasaría antes de que terminaran.

El problema con la integración diferida es que es muy difícil predecir cuánto tiempo llevará hacerlo, y peor es muy difícil ver qué tan lejos está del proceso. El resultado es que te estás metiendo en un punto ciego completo justo en una de las partes más tensas de un proyecto, incluso si eres uno de los pocos casos en los que aún no llegas tarde.

La integración continua completa este problema. No hay una integración larga, eliminas por completo el punto ciego. En todo momento, sabes dónde estás, qué funciona, qué no, los errores pendientes que tiene en su sistema.

Errores: estas son las cosas desagradables que destruyen la confianza y arruinan los horarios y la reputación. Los errores en el software deployado hacen que los usuarios se enojen contigo. Los errores en el trabajo en curso se interponen en su camino, dificultando que el resto del software funcione correctamente.

Continuous Integrations no elimina los errores, pero los hace mucho más fáciles de encontrar y eliminar. En este sentido, es bastante parecido al código de autoevaluación. Si introduce un error y lo detecta rápidamente, es mucho más fácil deshacerse de él. Como solo has cambiado un poco del sistema, no tienes que mirar muy lejos. Dado que ese pedazo del sistema es la parte con la que acabas de trabajar, lo tenes fresco en la memoria, lo que hace que sea más fácil encontrar el error. También podes usar el diff debugging, comparando la versión actual del sistema con una versión anterior que no tenía el error.

Los errores también son acumulativos. Cuantos más errores tenga, más difícil será eliminarlos. Esto se debe en parte a que se producen interacciones de errores, donde las fallas se muestran como resultado de múltiples fallas, lo que hace que cada falla sea más difícil de encontrar. También es psicológico: las personas tienen menos energía para encontrar y deshacerse de los errores cuando hay muchos de ellos, un fenómeno que los programadores pragmáticos llaman Broken Windows syndrome.

Como resultado, los proyectos con Integración Continua tienden a tener dramáticamente menos errores, tanto en producción como en proceso. Sin embargo, debo enfatizar que el grado de este beneficio está directamente relacionado con lo bueno que es el conjunto de tests. Debería descubrir que no es demasiado difícil construir un conjunto de pruebas que marque una diferencia notable. Sin embargo, por lo general lleva un tiempo antes de que un equipo llegue al nivel bajo de errores que tienen el potencial de alcanzar. Llegar allí significa trabajar constantemente y mejorar sus pruebas.

Si tiene integración continua, elimina una de las mayores barreras para el deploy frecuente. La implementación frecuente es valiosa porque les permite a sus usuarios obtener nuevas funciones más rápidamente, dar una retroalimentación más rápida sobre esas características y, en general, colaborar más en el ciclo de desarrollo. Esto ayuda a romper las barreras entre los clientes y el desarrollo, barreras que creo que son las mayores barreras para el desarrollo exitoso de software.

Introduciendo la Integración Continua

Entonces, ¿Te gustaría probar la Integración Continua? ¿Dónde comienzas? El conjunto completo de prácticas que describí anteriormente le brinda todos los beneficios, pero no necesita comenzar con todos.

Aquí no hay una receta fija, mucho depende de la naturaleza de tu configuración y tu equipo. Pero aquí hay algunas cosas que hemos aprendido para hacer que las cosas funcionen.

Uno de los primeros pasos es automatizar la compilación. Obtenga todo lo que necesita en el control de código fuente para que pueda hacer build de todo el sistema con un solo comando. Para muchos proyectos, esta no es una tarea menor, sin embargo, es esencial para que funcione cualquier otra cosa. Inicialmente, solo puede compilar de vez en cuando a pedido, o simplemente hacer una compilación nocturna automatizada. Si bien estos no son una integración continua, un build nocturno automático es un buen paso en el camino.

Presente algunas pruebas automatizadas en su compilación. Intente identificar las áreas principales donde las cosas van mal y obtenga pruebas automatizadas para exponer esas fallas. Particularmente en un proyecto existente es difícil conseguir un buen conjunto de pruebas que funcionen rápidamente; toma tiempo construir pruebas. Sin embargo, tienes que empezar en algún lado: todos esos clichés sobre el calendario de construcción de Roma se aplican.

Intenta acelerar la compilación de commit. La integración continua en una construcción de pocas horas es mejor que nada, pero bajar a ese número mágico de diez minutos es mucho mejor. Esto generalmente requiere una cirugía bastante seria en su código base para hacer a medida que rompe las dependencias en partes lentas del sistema.

Si está comenzando un nuevo proyecto, comience con Integración continua desde el principio. Esté atento a los tiempos de compilación y tome medidas tan pronto como comience a ir más lento que la regla de diez minutos. Si actúas rápido, harás las reestructuraciones necesarias antes de que la base de códigos se vuelva tan grande que se convierta en un gran dolor.

Sobre todo, obtener ayuda. Encuentre a alguien que haya hecho Integración Continua antes para ayudarlo. Al igual que cualquier técnica nueva, es difícil presentarla cuando no se sabe cuál es el resultado final. Puede costar dinero conseguir un mentor, pero también pagará en tiempo perdido y productividad si no lo hace. (Descargo de responsabilidad / anuncio: sí, en ThoughtWorks realizamos algunas consultorías en esta área. Después de todo, hemos cometido la mayoría de los errores que hay que hacer).

Pensamientos finales

En los años transcurridos desde que Matt y yo escribimos el documento original en este sitio, la integración continua se ha convertido en una técnica principal para el desarrollo de software. Casi ningún proyecto de ThoughtWorks se queda sin él, y vemos a otros usando CI en todo el mundo. Casi nunca escuché cosas negativas sobre el enfoque, a diferencia de algunas de las prácticas de Programación extrema más controvertidas.

Si no está utilizando Integración Continua, le recomiendo encarecidamente que lo intente. Si es así, tal vez haya algunas ideas en este artículo que pueden ayudarlo a hacerlo de manera más efectiva. Hemos aprendido mucho sobre Integración Continua en los últimos años, espero que aún haya más para aprender y mejorar.

Otras lecturas

Un ensayo como este solo puede abarcar tanto terreno, pero este es un tema importante, así que he creado una guide page en mi sitio web para indicarle más información.

Para explorar la Integración Continua con más detalle, sugiero echar un vistazo al libro de Paul Duvall titulado apropiadamente sobre el tema (que ganó un premio Jolt, más de lo que he logrado). Para obtener más información sobre el proceso más amplio de entrega continua, eche un vistazo al libro de Jez Humble y Dave Farley, que también me ganó a un premio Jolt.

También puede encontrar más información sobre integración continua en el sitio de ThoughtWorks.