zurück zum Artikel

Continuous Integration in Zeiten agiler Programmierung

Know-how

Continuous Integration ist ein bewährtes Mittel zur Optimierung der Softwareentwicklung. Der nachfolgende Artikel erläutert ihre Grundlagen, benennt Stolpersteine in der Praxis sowie die daraus resultierenden Best Practices und gibt abschließend einen Ausblick auf aktuelle Trends.

Im Team entwickelte Softwareprojekte sind mit wachsender Größe und Komplexität zunehmend schwieriger zu verwalten und zu bauen. Durch das ständige Zusammenspiel vieler Entwickler schleichen sich früher oder später zwangsläufig sogenannte Integrationsfehler ein, die sich, je nachdem wann sie bemerkt werden, nur schwer oder gar nicht mehr beseitigen lassen.

Dieses Problem ist letztlich so alt wie die Softwareentwicklung selbst und dementsprechend gibt es schon seit Langem Ideen und Wege, damit umzugehen. Bekannt [1] sind sie unter so klingenden Namen wie "Daily Build" und "Smoke Test". Es war jedoch Martin Fowlers Artikel "Continuous Integration [2]" (CI) aus dem Jahre 2000, der das Thema unter selbigem Namen bekannt machte. Seitdem sind über zehn Jahre vergangen, und neben den ursprünglichen Konzepten existieren mittlerweile gebrauchsfertige CI-Server diverser Hersteller. Ihre Verwendung in der Softwareentwicklung gehört heute zum guten Ton.

Erst die Arbeit, dann das Vergnügen

Um ein CI-System vernünftig betreiben zu können, müssen einige technische und organisatorische Voraussetzungen erfüllt sein, die nachfolgend kurz erläutert seien.

Eine zentrale technische Vorbedingung für Projekte, die mit CI arbeiten sollen, ist die Verwaltung des Quellcodes und sonstiger zugehöriger Dateien mit einem Version Control System (VCS). Dabei ist es aus CI-Sicht gleichgültig, ob es sich um ein zentralisiertes System wie Subversion oder um ein verteiltes System wie Git handelt.

Da der Betrieb des CI-Servers ohne menschliche Interaktion ablaufen soll, ist es notwendig, dass das Bauen der entsprechenden Projekte vollständig automatisiert ist (Stichwort Build-Automatisierung). Die genaue Bedeutung von "Bauen" variiert je nach Projekt, üblicherweise verbergen sich dahinter zumindest ein Kompilieren des Sourcecodes und ein Durchführen automatisierter Tests.

Damit sich der Betrieb eines CI-Servers letztlich auf Dauer lohnt, sollten die zuvor erwähnten automatisierten Tests in ausreichender Menge vorhanden sein (Test-Automatisierung). Die Ergebnisse eines CI-Servers, der wenige oder nur triviale Tests ausführt, sind in der Regel wenig aussagekräftig.

Während der Arbeit an einem Projekt nimmt das verantwortliche Entwicklerteam Quellcodeänderungen vor und übergibt diese mit einem sogenannten Commit an das VCS. Die Commits sind häufig durchzuführen, da sonst die Wahrscheinlichkeit stark zunimmt, dass sich die lokalen Änderungen der Entwickler auf dem CI-System nicht mehr integrieren lassen.

Da das CI-System ein Teil der Entwicklungskultur der jeweiligen Teams werden soll, ist es notwendig, dass alle Teammitglieder leichten Zugriff darauf haben und über die jeweiligen Resultate ihrer Arbeit und die ihrer Kollegen informiert werden (Sichtbarkeit, zum Beispiel durch Versand entsprechender E-Mails.

Das Ganze ist mehr als die Summe seiner Teile

Ein CI-System besteht neben dem eigentlichen CI-Server noch aus weiteren Komponenten. Die Abbildung 1 zeigt schematisch die wesentlichen Bestandteile eines typischen CI-Systems. Ausgangspunkt ist ein Projekt, das mit CI arbeiten soll und dessen Quellcode mit einem VCS verwaltet wird. Das verantwortliche Entwicklerteam arbeitet an dem Projekt und übergibt die durchgeführten Änderungen am Quellcode via Commit an das VCS. Als Erstes ist einmalig im CI-Server bekannt zu machen, wo sich das Projekt innerhalb des VCS befindet, üblicherweise durch Angabe einer VCS-spezifischen URL und wie sich der CI-Server gegenüber dem VCS authentifiziert. Ähnlich einem normalen Entwickler erzeugt der CI-Server bei sich nun eine lokale Arbeitskopie des betroffenen Projekts.

Bestandteile eines CI-Systems (Abb. 1)

Ab sofort richtet der CI-Server in regelmäßigen Zeitabständen Anfragen an das VCS, ob sich im betroffenen Projekt seit dem letzten Mal Änderungen ergeben haben. Falls ja, wird im CI-Server ein neuer sogenannter CI-Build angestoßen. Der erste Schritt innerhalb eines solchen CI-Builds ist grundsätzlich, dass der CI-Server seine lokale Arbeitskopie vom VCS durch ein sogenanntes Update aktualisiert. Anschließend wird im nächsten Schritt das Projekt mit diesen aktuellen Dateien "gebaut", das bedeutet üblicherweise Kompilieren und Ausführen der automatisierten Tests.

Die Kompilierung und Testausführung führt nicht der CI-Server durch, sondern ein Build-Tool wie Maven oder Make. Das setzt voraus, dass das Entwicklerteam im Vorfeld den Bauvorgang vollständig automatisiert hat und die Build-Tool-spezifischen Dateien wie Mavens POMs (Project Object Model) oder Makefiles ebenfalls im VCS verwaltet werden. Je nachdem wie der Aufruf des Build-Tools endet, klassifiziert der CI-Server den gesamten CI-Build abschließend. Das geschieht prinzipiell nach folgendem Schema:

  1. erfolgreich: Sowohl das Kompilieren als auch das automatisierte Testen haben keine Fehler ergeben.
  2. Warnung: Das Kompilieren und Testen war zwar grundsätzlich erfolgreich, es gab jedoch kleinere Probleme, wie einzelne fehlgeschlagene Tests oder Warnungen des Compilers.
  3. Fehler: Es gab derart kritische oder viele Fehler, dass der CI-Build als gescheitert anzusehen ist, zum Beispiel ein Abbruch des Compilers oder das Überschreiten einer bestimmten Anzahl fehlgeschlagener Tests.

Durch den Zugriff auf das VCS kann der CI-Server ermitteln, welche Mitglieder des Entwicklerteams diejenigen Änderungen vorgenommen haben, die den letzten CI-Build ausgelöst haben. Diese Entwickler lassen sich abschließend gezielt zum Beispiel per E-Mail über das Build-Ergebnis informieren und erhalten somit Auskunft darüber, ob ihre Änderungen zu Problemen geführt haben.

Üblicherweise werden die Ergebnisse aller Builds sowie die zugehörigen Daten auf dem CI-Server als Berichte gespeichert und sind über entsprechende Webseiten abrufbar, sodass als Benachrichtigung nur die zugehörige URL und das Gesamtergebnis zu verschicken sind. An der Stelle sei noch erwähnt, dass das beschriebene Szenario den "typischen" CI-Ablauf beschreibt. Anpassungen und Erweiterungen des Grundgedankens sind durchaus möglich, so zum Beispiel:

CI-Server

Wer die Wahl hat ...

Da die Ideen von Continuous Integration seit Langem bekannt und etabliert sind, hat sich mittlerweile eine große Landschaft verfügbarer CI-Server [3] gebildet. Doch woran macht sich die Auswahl eines konkreten CI-Servers fest? Das kann in der Praxis von mehreren Kriterien abhängen:

  1. Kompatibilität mit vorhandener Infrastruktur: Der eigentliche CI-Server ist nur Teil einer größeren Entwicklungsinfrastruktur. Insbesondere sollte zum Beispiel die Interoperabilität mit einem vorhanden VCS oder einem etablierten Build-Tool gewährleistet sein.
  2. Optimierung für bestimmte Szenarien: Manche CI-Server sind speziell auf bestimmte Programmiersprachen, Frameworks oder integrierte Entwicklungsumgebungen (IDEs) hin optimiert, sodass das unter Umständen ein wichtiges Entscheidungsmerkmal ist.
  3. Funktionsumfang und Erweiterbarkeit: Ein weiteres wichtiges Kriterium ist sicherlich, welche Features ein CI-Server jenseits der weiter oben geschilderten "Grundfunktionen" bietet. Eng verknüpft damit ist die Frage, ob sich ein CI-Server von dritter Seite funktional erweitern lässt, etwa durch eine Plug-in-API.
  4. Proprietär oder Open Source: Auf dem CI-Server-Markt existieren sowohl proprietäre als auch Open-Source-Produkte. Auch das kann Einfluss auf die Entscheidungsfindung haben, zum Beispiel hinsichtlich Fragen rund um Lizenzkosten und Support oder ob großer Wert auf das Vorhandensein einer aktiven Community gelegt wird, die Plug-ins und Mailinglisten bietet.

Angesichts der Menge der verfügbaren CI-Server und der bei der Entscheidung zu berücksichtigenden Aspekte ist klar, dass sich keine "one size fits all"-Empfehlung geben lässt. Als Beispiel sei trotzdem Jenkins genannt, ein kostenfreier CI-Server, der sich derzeit großer Beliebtheit erfreut. Jenkins ist ein in Java programmierter Open-Source-CI-Server, der als Fork des CI-Servers Hudson hervorgegangen ist. Für die Hintergründe der Abspaltung und die daraus resultierenden Spannungen sei an dieser Stelle lediglich auf den Wikipedia-Beitrag [4] zum Thema verwiesen.

Jenkins/Hudson war zwar ursprünglich primär für Java-Projekte gedacht, die gut integrierte Plug-in-Schnittstelle hat jedoch früh auch viele Entwickler anderer Programmiersprachen angezogen. Das hat zu zahlreichen Erweiterungen geführt, sodass derzeit viele gängige Build-Tools und Sprachen, auch aus dem Nicht-Java Umfeld, unterstützt werden. Insbesondere erfordert die Installation mittlerweile auch keine Java-Kenntnisse mehr, da fertig verwendbare Installer für die meisten Betriebssysteme verfügbar sind. Weitere Informationen, Dokumentation und Downloads finden sich auf der Jenkins-Website.

Metriken und Trends

Heute nimmt der CI-Server nicht mehr nur die zuvor geschilderte Rolle des CI-Servers als reiner "Rechenknecht" ein, der lediglich Änderungen an der Code-Basis durch Kompilierung verifiziert. Vielmehr wird seine zentrale Rolle genutzt, um ein umfangreiches Qualitätsmanagement des gesamten Entwicklungsprojekts zu erreichen.

Das liegt auch nahe: Der CI-Server steuert mehr oder weniger den gesamten Build-Prozess und ist die zentrale Integrationsinstanz, die unabhängig von den einzelnen Entwicklern und der Projektsituation arbeitet und so als Quality Gate für das gesamte Projekt fungiert. In der Rolle ist er in der Lage, Aussagen zum Umfang und zur Qualität der Software zu treffen, den Entwicklungsstatus aufzuzeigen, zu historisieren und so über die Zeit zu betrachten. Der damit erlangte "Blick in das Entwicklungsprojekt" steht allen Projektbeteiligten zur Verfügung, die dadurch in der Lage sind, den Status selbst zu bewerten, frühzeitig auf Probleme zu reagieren und eine offene Kommunikation mit allen Beteiligten zu führen.

Um verlässliche Aussagen über den Zustand des Softwareprojekts und die Qualität des Programmcodes treffen zu können, muss der CI-Server Messgrößen wie den Umfang der Codebasis und die Stabilität des Entwicklungsprozesses ermitteln. Das können moderne CI-Server nur teilweise selbst, weswegen ein Entwickler hierfür normalerweise auf weitere Tools zurückgreifen muss.

Die gewonnenen Metriken lassen sich im nächsten Schritt nun beinahe beliebig dazu verwenden, Mindestqualitätsanforderungen zu überprüfen und bei Verletzungen entsprechend zu reagieren. Welche Anforderungen das sind, in welcher Phase des Build-Prozesses sie angewandt werden und in welchem Maße Verletzungen zu unmittelbaren Konsequenzen wie einem Scheitern des Builds führen, ist natürlich vor dem Hintergrund des konkreten Projekts zu entscheiden.

Beispiele für in der Praxis oft verwendete Messgrößen und die damit einhergehenden Fragestellungen sind:

Die Liste lässt sich deutlich erweitern und detaillieren und damit eine umfangreiche und individuelle Aussage über die Qualität des Softwareprojektes treffen.

Bei Trends handelt es sich hingegen um Momentaufnahmen. Oft spannender ist die Betrachtung über die Zeit, also ein Aufzeigen von Trends. Auch hierbei kommt dem CI-Server eine zentrale Rolle zu, denn mit den erfassten Messgrößen bietet er die Basis für eine Historisierung der Daten und damit beispielsweise der Beantwortung der folgenden Fragen: Steigt die Testabdeckung der Unit-Tests wie geplant? Welche Tests sind besonders instabil? Hat eine Änderung am Code zu einer ungewollten Änderung des Performanceverhaltens oder der benötigten Ressourcen geführt? Ändert sich die Stabilität der Hauptentwicklungslinie (Trunk) unverhältnismäßig, zum Beispiel durch die Vergrößerung des Projektteams?

Metriktrends am Beispiel der Sonar-Code-Quality-Plattform (Abb. 2)

Innerhalb eines komplexeren Szenarios unterscheiden sich die einzelnen Builds nicht nur in der Frage, ob sie erfolgreich waren oder nicht. Gerade die gemessenen Qualitätsmerkmale können unterschiedlich sein, und so ist es oft wünschenswert, einzelne Builds aufgrund bestimmter Eigenschaften automatisch oder manuell zu
kennzeichnen. CI-Server bieten dazu unterschiedliche Funktionen, beispielsweise das Anlegen eines Tags im VCS oder eine sogenannte "Promotion" des entsprechenden Builds. Letztere ermöglicht es etwa, gezielt einzelne Builds manuellen Tests oder weiterführenden Build-Schritten zuzuführen.

Die üblichen Verdächtigen

In der Theorie ist Continuous Integration die ideale Basis für die Entwicklung im Team und der CI-Server dabei das steuernde, überwachende Element des gesamten Entwicklungsprozesses. In der Praxis stellt sich dieses Ideal aber nicht immer ein: Der Build-Prozess dauert zu lange, die Tests sind zu instabil, der CI-Server meldet mehrmals täglich fehlgeschlagene Builds und wird sowieso als Fremdkörper wahrgenommen und gemessene Metriken interessieren eigentlich niemanden.

Gerade in unerfahrenen Teams oder in Projekten, in denen Qualität nicht oberstes Prinzip ist, stößt man auf solche oder ähnliche Situationen. Dem ist schnell und konsequent zu begegnen, indem das Vertrauen in den CI-Prozess, aber auch das Verantwortungsbewusstsein der Projektbeteiligten gestärkt wird. Wie das geht, zeigen mittlerweile etablierte Best Practices und Patterns (z. B. Paul M. Duvall: "Continuous Integration: Patterns and Anti-patterns [5]"). Die Kerngedanken lassen sich wie folgt zusammenfassen.

Optimierung der Tests: Muss der Entwickler zu lange auf Feedback nach dem Check-in seine Codeänderung warten, verliert er nicht nur den aktuellen Fokus, sondern betrachtet den CI-Prozess als unproduktiv und überflüssig. Zu lang laufende Tests sind dabei meist die Ursache für dieses Problem. Dem lässt sich auf unterschiedliche Art begegnen: Erstens sollten die Tests passend strukturiert und partitioniert sein, sodass anlassbezogen beispielsweise nur schnelle Unit-Tests nach einem Commit für ein unmittelbares Entwicklerfeedback ausgeführt werden. Durch gutes Mocking externer Abhängigkeiten, aber auch fortgeschrittene Techniken wie Testpriorisierung und -selektion (z. B. Björn Feustel, Steffen Schluff: "Testest Du noch oder entwickelst Du schon (wieder)?" (PDF [6])) lässt sich zweitens die mittlere Ausführungszeit der Tests deutlich reduzieren. Schließlich ist es heute oftmals nicht nur der einfachste, sondern auch ein kostengünstiger Weg, die Testlaufzeiten zu optimieren, dem CI-Server leistungsstärkere Hardware zur Verfügung zu stellen oder die Tests verteilt über mehrere Serverinstanzen auszuführen.

Stabilisierung der Entwicklung: Schlägt der Build zu oft fehl oder wird er durch instabile Tests unberechenbar, schwindet das Vertrauen in den CI-Prozess und "rote" Builds werden nicht mehr als das wahrgenommen, was sie sind: große Produktivitätsstopper. Lösungen sind etwa

Trends & Fazit

Ausblick: Continuous Delivery, Cloud, ALM

Continuous Integration ist ohne Zweifel zu einem zentralen Thema der Softwareentwicklung geworden. Doch die Entwicklung macht hier nicht Halt, und es entstehen immer wieder interessante Ideen und neue Konzepte, die bestehende Anforderungen oder neue Bereiche angehen. Am Schluss des Artikels soll deswegen ein kleiner, zugegebenermaßen subjektiver Blick in die Zukunft stehen und einige dieser Themen kurz vorgestellt werden.

Continuous Delivery: Es lässt sich wohl sagen, dass der Begriff "Continuous Delivery" in der letzten Zeit der neue Begriff im Bereich Build-Management geworden ist. Lässt man jedoch etwas Marketingsternenstaub beiseite, verbergen sich dahinter größtenteils bekannte Konzepte, denen man in der Praxis bereits begegnen konnte, als der Begriff noch nicht in aller Munde war. Continuous Delivery schafft es jedoch, diese Konzepte in seiner Gesamtheit zu verdeutlichen und auf ein Ziel zu konzentrieren, das sich gut mit dem ersten Prinzip des Agilen Manifests [8] deckt: "Our highest priority is to satisfy the customer through early and continuous delivery of valuable software".

Kern eines Continuous-Delivery-Prozesses ist die Deployment Pipeline, die aus mehreren Stages besteht, die jeweils einem eigenen Build-Prozess entsprechen. Jede Änderung am Programmcode erzeugt eine neue Instanz der Deployment Pipeline und startet die erste Stage, die Commit Stage. Typischerweise produziert diese ein oder mehrere Artefakte, die die Software einschließlich der Änderung umfassen. Wurde sie erfolgreich beendet, werden anschließend sequenziell nacheinander alle weiteren Stages mit diesen Artefakten durchlaufen, und zwar so lange, bis entweder eine Stage fehlschlägt oder das Ende der Deployment Pipeline erreicht ist. In der letzten Stage wird dabei normalerweise ein Deployment der Änderung in die Produktion vorgenommen. Jede erfolgreich durchlaufene Stage erhöht somit die Gewissheit, dass die Änderung intakt ist und tatsächlich zu einer neuen, funktionierenden Version der Software führen wird.

Eine der Grundvoraussetzung eines solchen Continuous-Delivery-Ansatzes ist eine weitgehende Automatisierung des gesamten Prozesses. Sei es das Kompilieren der Software, das Ausführen von Tests, das Verteilen von Konfigurationsänderungen auf die verschiedenen Umgebungen oder das Deployment -- diese Aspekte gilt es zu automatisieren. Basis dafür ist gewöhnlich ein CI-Server, dessen Kernfunktionen meist relativ problemlos eine einzelne Stage abbilden können. Erst die Stage-übergreifende Abstraktion der Deployment Pipeline und damit verbundene Funktionen, zum Beispiel das Weiterreichen ("Sharing") von Artefakten, weichen von den klassischen CI-Funktionen ab und stellen somit neue Anforderungen an den CI-Server. Diese bedienen aber oftmals bereits zeitgemäße Produkte am Markt oder lassen sich spätestens durch eigene Erweiterungen des Build-Prozesses oder der Build-Skripte realisieren.

Continuous Delivery, wie sie sich Jez Humble und David Farley vorstellen (Abb. 3)

Cloud und Virtualisierung: Wollte man vor einigen Jahren dem CI-Prozess mehr Ressourcen zur Verfügung stellen, ließ sich das nur dadurch bewerkstelligen, dass man dem CI-Server leistungsstärkere Hardware (vertikale Skalierung) gab. Heute ist die Situation eine andere: Moderne CI-Server bieten von Hause aus Unterstützung für die vertikale Skalierung des Build-Prozesses. Angefangen von sogenannten Build
Agents, die den gesamten Build auf mehrere Rechner verteilen, über das automatische Starten zusätzlicher (virtueller) Server nach Bedarf oder das automatische Auslagern des Builds zu externen Infrastrukturanbietern wie Amazon und deren EC2-Angebot (Elastic Computing Cloud). Je nach Situation und Last lassen sich so relativ problemlos neue Ressourcen durch den CI-Server akquirieren.

Application Lifecycle Management (ALM) betrachtet die Entwicklung und das Management einer Software über deren gesamten Lebenszyklus, angefangen bei der Anforderungsfindung über die Entwicklung bis hin zur Wartung und Betrieb. In letzter Zeit taucht der Begriff immer öfter abseits der umfassenden und nicht selten teuren Komplettlösungen großer Hersteller auf, wobei der Schwerpunkt speziell auf der Entwicklung der Software liegt. Schlagwörter sind dann die Integration der oftmals heterogenen Tool-Landschaft, Optimierung des Entwicklungsflusses und kontextbezogenes Arbeiten. Im Bereich der CI-Server lässt sich hier im Allgemeinen eine immer bessere Integration in andere Tools des Entwicklungsprozesses, im Speziellen insbesondere in die Entwicklungsumgebung erkennen.

Fazit

Mehr Agile ALM?

Dieser Artikel ist auch im derzeit aktuellen iX-Developer-Sonderheft erschienen, das zum Thema agilem ALM weitere Beiträge enthält. So gibt es Artikel zum Einfluss von Open Source auf die ALM-Branche, zu verteilten Versionskontrollen, angesagten Build-Management-Werkzeugen, Continuous Delivery, zur Qualitätsicherung und zu Sicherheitstrends und rechtlichen Aspekten in agiler Softwareentwicklung.

Zusammenfassend lässt sich sagen, dass Continuous Integration mittlerweile ein in der Praxis erprobtes Mittel zur Optimierung der Softwareentwicklung ist. Dank einer großen Palette fertiger Werkzeuge sowie eines Erfahrungsschatzes von Best Practices gibt es keinen Grund, Softwareentwicklung ohne einen
Continuous-Integration-Prozess zu betreiben. Aktuelle Entwicklungen wie Continuous Delivery und ALM zeigen, dass das Thema aber auch in Zukunft spannend bleibt. (ane [10])

Björn Feustel
ist als Trainer, Entwickler und Berater beim IT-Dienstleister OIO tätig. Seine Schwerpunkte liegen im Bereich Test- und Build-Management, Softwarearchitekturen sowie verteilten Systemen.

Steffen Schluff
ist als Leiter der Softwarefactory bei OIO tätig. Seine Schwerpunkte liegen in den Bereichen Enterprise Java, XML und Open-Source-Tooling.

Literatur

URL dieses Artikels:
http://www.heise.de/-1427092

Links in diesem Artikel:
[1] http://www.stevemcconnell.com/ieeesoftware/bp04.htm
[2] http://www.martinfowler.com/articles/continuousIntegration.html
[3] http://en.wikipedia.org/wiki/Continuous_integration#Software
[4] http://en.wikipedia.org/wiki/Jenkins_%28software%29#Hudson
[5] http://refcardz.dzone.com/refcardz/continuous-integration
[6] http://www.oio.de/m/konf/oop2010/test-optimierung-oop2010.pdf
[7] http://www.jetbrains.com/devnet/academy/concepts/pretested_commit.html
[8] http://agilemanifesto.org/principles.html
[9] http://www.heise-shop.de/heise-zeitschriften-verlag/ix-developer-01-2012-programmieren-heute_pid_17704675.html
[10] mailto:ane@heise.de