Continuous Integration in Zeiten agiler Programmierung

Know-how  –  17 Kommentare

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 sind sie unter so klingenden Namen wie "Daily Build" und "Smoke Test". Es war jedoch Martin Fowlers Artikel "Continuous Integration" (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.

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.

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:

  • Builds nicht nach jeder Änderung, sondern nur zu bestimmten Zeiten anzustoßen, dafür dann aber umfangreichere und somit länger laufende Tests auszuführen (Nightly Build).
  • bei modular aufgebauten Projekten nach jedem CI-Build eines Teilmoduls alle im Abhängigkeitsbaum nachgelagerten Teilmodule zu bauen, auch wenn diese selbst keine Änderungen aufweisen.
  • mehrere vernetzte CI-Server zu betreiben, um aufwendige Tests zu parallelisieren oder um Tests auf verschiedenen Betriebssystemen durchführen zu können.