Einstieg in Continuous Integration

Erfahre, wie du in fünf Schritten die Continuous Integration und automatisiertes Testen einführen kannst.

Sten Pittet Sten Pittet

Continuous Integration (CI) ist eine bewährte Agile- und DevOps-Methode, bei der Entwickler ihren Code frühzeitig und mehrmals in den Haupt-Branch oder ins Code-Repository integrieren. Das Ziel ist es, der "Integrationshölle" zu entgehen, die entsteht, wenn die Arbeit aller Entwickler erst am Ende eines Projekts oder eines Sprints gemergt wird. Da das bei Deployment bei dieser Methode automatisiert wird, können Teams die Geschäftsanforderungen besser erfüllen, die Codequalität verbessern und die Sicherheit erhöhen.

Einer der Hauptvorteile der Einführung der CI ist deine Zeitersparnis während des Entwicklungszyklus durch frühzeitiges Erkennen und Beheben von Konflikten. Es ist auch eine gute Möglichkeit, den Zeitaufwand für die Behebung von Fehlern und Regressionen zu reduzieren, indem du mehr Wert auf eine gute Testsuite legst. Schließlich hilft es dir dabei, ein besseres gemeinsames Verständnis für die Codebasis und die Funktionen zu erfahren, die du für deine Kunden entwickelst.

Dein erster Schritt zur Continuous Integration: Das Einrichten von automatisiertem Testen.

Einstieg in automatische Tests

Erläuterung der unterschiedlichen Arten von Tests

Um in den Genuss aller Vorteile von CI zu kommen, musst du deine Tests so automatisieren, dass sie für alle Änderungen am Haupt-Repository ausgeführt werden können. Führe unbedingt für jeden Branch des Repositorys Tests durch, statt dich nur auf den Haupt-Branch zu konzentrieren. Auf diese Weise findest du Probleme schon früh und kannst die Störungen für dein Team minimieren.

Es gibt viele Arten von Tests, aber du musst nicht alles auf einmal erledigen, wenn du gerade erst anfängst. Du kannst mit Unit-Tests klein anfangen und im Laufe der Zeit deine Abdeckung erweitern.

  • Unit-Tests haben einen geringen Umfang und überprüfen normalerweise das Verhalten einzelner Methoden oder Funktionen.
  • Mit Integrationstests wird sichergestellt, dass mehrere Komponenten zusammen problemlos funktionieren. Dies kann verschiedene Klassen sowie Tests der Integration mit anderen Services beinhalten.
  • Akzeptanztests ähneln den Integrationstests, konzentrieren sich jedoch auf die Geschäftsfälle und nicht auf die Komponenten selbst.
  • UI-Tests stellen sicher, dass die Anwendung aus Benutzersicht korrekt funktioniert.

Nicht alle Tests sind gleichwertig. Welche Kompromisse du jeweils eingehen musst, verdeutlicht die von Mike Cohn entwickelte Testpyramide.

Test-Dreieck

Unit-Tests sind schnell und kostengünstig zu implementieren, da sie hauptsächlich kleine Codeabschnitte überprüfen. Auf der anderen Seite sind UI-Tests komplex in der Implementierung und langsam in der Ausführung, da sie oft eine vollständige Umgebung sowie mehrere Dienste benötigen, um das Verhalten von Browsern oder mobilen Geräten nachzuahmen. Deshalb solltest du die Anzahl an komplexen UI-Tests begrenzen und dich auf gute Unit-Tests an der Basis verlassen, um einen schnellen Build zu haben und den Entwicklern so schnell wie möglich Feedback zu geben.

Automatisches Ausführen deiner Tests

Um die Continuous Integration einzuführen, musst du deine Tests bei jeder Änderung durchführen, die zurück zum Haupt-Branch gepusht wird. Dafür brauchst du einen Dienst, der dein Repository überwachen und neue Pushes zur Codebasis aufspüren kann. Es gibt viele Lösungen, sowohl lokal als auch in der Cloud, aus denen du auswählen kannst. Du solltest Folgendes beim Auswählen deines Servers berücksichtigen:

  • Wo wird dein Code gehostet? Kann der CI-Dienst auf deine Codebasis zugreifen? Hast du eine bestimmte Einschränkung, wo der Code leben kann?
  • Welches Betriebssystem und welche Ressourcen benötigst du für deine Anwendung? Wird deine Anwendungsumgebung unterstützt? Kannst du die richtigen Abhängigkeiten installieren, um deine Software zu erstellen und zu testen?
  • Wie viele Ressourcen benötigst du für deine Tests? Bei manchen Cloud-Anwendungen könnte es Einschränkungen bezüglich der von dir nutzbaren Ressourcen geben. Wenn deine Software sehr viele Ressourcen verbraucht, solltest du deinen CI-Server hinter einer Firewall hosten.
  • Wie viele Entwickler gibt es in deinem Team? Wenn dein Team mit der CI arbeitet, werden täglich viele Änderungen zurück in den Haupt-Repository gepusht. Damit die Entwickler schnelles Feedback erhalten, musst du die Wartezeit für Builds reduzieren. Außerdem solltest du einen Dienst oder Server verwenden, der dir die nötige Parallelität bietet.

Früher musstest du in der Regel einen separaten CI-Server wie Bamboo oder Jenkins installieren. Inzwischen gibt es in der Cloud Lösungen, die wesentlich einfacher zu implementieren sind. Wenn dein Code beispielsweise in Bitbucket Cloud gehostet wird, kannst du das Pipelines-Feature im Repository nutzen, um bei jedem Push Tests durchzuführen, ohne dafür einen separaten Server oder Build-Agenten konfigurieren und Obergrenzen für die Gleichzeitigkeit beachten zu müssen.

image: node:4.6.0 pipelines:   default:     - step:         script:           - npm install           - npm test

Beispiel einer Konfiguration zum Testen eines JavaScript-Repositorys mit Bitbucket Pipelines

Ermitteln nicht getesteter Codebestandteile anhand der Codeabdeckung

Sobald du mit dem automatisierten Testen beginnst, ist es gut, wenn du ein Testabdeckungstool verbindest. Es kann dir eine Vorstellung davon geben, wie viel deiner Codebasis von deiner Testsuite abgedeckt wird.

Eine Abdeckung von mehr als 80 % ist durchaus erstrebenswert, aber sie ersetzt keine hochwertige Testsuite. Mit einem Tool für die Codeabdeckung findest du nicht getesteten Code zwar leichter, entscheidend ist letztlich jedoch die Qualität der Tests.

Wenn du gerade erst damit anfängst, musst du nicht sofort 100 % Abdeckung deiner Codebasis erreichen. Nutze stattdessen ein Testabdeckungstool, um wichtige Teile deiner Anwendung herauszufinden, die über noch keine Tests verfügen, und beginne dort.

Refactoring ist eine Gelegenheit, Tests hinzuzufügen

Wenn du gerade wesentliche Änderungen an deiner Anwendung vornehmen möchtest, solltest du damit beginnen, Akzeptanztests für die Funktionen zu schreiben, die davon betroffen sein könnten. So erhältst du ein Sicherheitsnetz und stellst sicher, dass das ursprüngliche Verhalten nicht beeinträchtigt wurde, nachdem du Code überarbeitet oder neue Funktionen hinzugefügt hast.

Einführung von Continuous Integration

Auch wenn die Automatisierung deiner Tests für die CI sehr wichtig ist, reicht sie alleine nicht aus. Eventuell musst du deine Teamkultur anpassen, damit die Entwickler nicht tagelang an einer Funktion arbeiten, ohne ihre Änderungen zurück zum Haupt-Branch zu mergen. Und du solltest "grüne Builds" einführen.

Integriere frühzeitig und oft

Egal, ob du die Trunk-basierte Entwicklung oder Feature-Branches verwendest – es ist wichtig, dass die Entwickler ihre Änderungen so früh wie möglich ins Haupt-Repository integrieren. Wenn du den Code zu lange in einem Branch oder in der Workstation der Entwickler lässt, dann gehst du das Risiko ein, zu viele Konflikte auf einmal zu haben, wenn du zurück zum Haupt-Branch mergst.

Indem du frühzeitig integrierst, verringerst du den Umfang der Änderungen. Dadurch wird es einfacher, Konflikte zu verstehen, wenn sie auftreten. Der andere Vorteil ist, dass das Teilen von Wissen unter Entwicklern erleichtert wird, da sie Änderungen erhalten, mit denen sie besser umgehen können.

Wenn du einige Änderungen vornimmst, die sich auf eine vorhandene Funktion auswirken können, dann kannst du Feature Flags verwenden, um deine Änderungen in der Produktion zu deaktivieren, bis du deine Arbeit zu Ende bringst.

Sorge dafür, dass der Build immer in Ordnung ist

Wenn ein Entwickler den Build für den Haupt-Branch kaputt macht, ist die Reparatur dieses Builds von höchster Priorität. Je mehr Änderungen in den Build gelangen, während er kaputt ist, desto schwieriger wird es für dich werden, zu verstehen, was ihn kaputt gemacht hat – und außerdem besteht das Risiko, dass du noch mehr Fehler hinzufügst.

Es lohnt sich, Zeit in deine Testsuite zu investieren, um sicherzustellen, dass sie schnell fehlschlagen kann und dem Entwickler/der Entwicklerin, der/die die Änderungen gepusht hat, so schnell wie möglich Feedback gibt. Du kannst deine Tests so aufteilen, dass die schnellen Tests (z. B. Unit-Tests) vor den Tests mit langer Ausführungszeit laufen. Wenn es immer sehr lange dauert, bis deine Testsuite fehlschlägt, verschwendest du viel Zeit der Entwickler, denn sie müssen den Kontext wechseln, um zu ihrer vorherigen Arbeit zurückzukehren und das Problem zu beheben.

Vergiss nicht, Benachrichtigungen einzurichten, sodass Entwickler eine Warnmeldung erhalten, wenn der Build kaputt geht. Du kannst auch einen Schritt weiter gehen und den Status deiner Haupt-Branches für alle auf einem Dashboard anzeigen.

Schreibe Tests als Teil deiner Storys

Letztendlich solltest du sicherstellen, dass jede Funktion, die entwickelt wird, über automatisierte Tests verfügt. Es sieht vielleicht so aus, als würdest du die Entwicklung verlangsamen, aber in Wahrheit wird dies die Zeit, die dein Team mit dem Beheben von Regressionen und Bugs in jeder Iteration verbringt, erheblich reduzieren. Du kannst auch ganz zuversichtlich Änderungen an deiner Codebasis vornehmen, denn deine Testsuite kann schnell sicherstellen, dass alle zuvor entwickelten Funktionen wie erwartet funktionieren.

Damit du gute Tests schreiben kannst, solltest du dich vergewissern, dass Entwickler frühzeitig an der Definition der User Storys beteiligt sind. Dadurch wird das gemeinsame Verständnis der Geschäftsanforderungen verbessert und die Beziehung zu Produktmanagern erleichtert. Du kannst sogar schon Tests schreiben, bevor der Code, der die Tests bestehen soll, implementiert wird.

Schreibe Tests wenn du Fehler behebst

Egal, ob du eine vorhandene Codebasis hast oder gerade erst anfängst – es steht fest, dass Bugs Teil deines Releases sein werden Stell sicher, dass du Tests hinzufügst, wenn du die Fehler behebst, um zu verhindern, dass sie erneut auftauchen.

Die CI ermöglicht es deinen QS-Entwicklern, die Qualität zu skalieren

Mit der Einführung der CI sowie der Automatisierung ändern sich auch die Aufgaben der QS-Entwickler. Sie brauchen keine trivialen Funktionen deiner Anwendung mehr testen und können sich jetzt der Bereitstellung von Tools widmen, die Entwickler unterstützen sowie ihnen beim Anwenden der richtigen Teststrategien helfen.

Sobald du mit der Continuous Integration beginnst, können sich deine QS-Entwickler darauf konzentrieren, das Testen mit besseren Tools und Datensätzen zu erleichtern und die Entwickler dabei zu unterstützen, besseren Code zu schreiben. Exploratives Testen wird keinen großen Teil ihres Aufgabenbereichs mehr darstellen, aber für so manch komplexe Anwendungsfälle wird es noch gebraucht werden.

Continuous Integration in 5 Schritten

Jetzt solltest du die Konzepte hinter der Continuous Integration ziemlich genau kennen – wir können sie in folgenden Punkten zusammenfassen:

  1. Beginne mit dem Schreiben von Tests für die erfolgskritischen Bestandteile deiner Codebasis.
  2. Besorg dir einen CI-Dienst, um diese Tests automatisch bei jedem Push in das Haupt-Repository auszuführen.
  3. Stell sicher, dass dein Team seine Änderungen jeden Tag integriert.
  4. Repariere den Build sofort, wenn er kaputt ist.
  5. Schreibe Tests für jede neue zu implementierende Story.

Es sieht vielleicht einfach aus, aber du brauchst wahres Engagement seitens deines Teams, um Erfolg zu haben. Zu Beginn solltest du deine Releases etwas verlangsamen, außerdem sollten dich die Produktinhaber unterstützen und die Entwickler nicht zum Ausliefern von Funktionen ohne Tests drängen.

Wir empfehlen dir, mit einfachen Tests klein anzufangen, um dich an die neue Routine zu gewöhnen, bevor du dich an die Implementierung einer komplexeren Testsuite machst, die möglicherweise schwer zu verwalten ist.