Tierney - stock.adobe.com

Software optimal warten: Tests und Automatisierung

Um gut wartbare Software zu entwickeln ist es wichtig, dass Zeit und Budget vorhanden ist. Das gilt auch für Softwaretests und die Automatisierung der CI-/CD-Pipeline.

Eine Software ist nach dem Ende des Entwicklungsprojekts nicht fertig. In den meisten Fällen werden auch nach der Produktivstellung Fehler gefunden, die behoben werden müssen und es werden Änderungen (Change Requests) umgesetzt, die das Verhalten der Applikation verändern.

Zusätzlich wird es notwendig sein, eine Anwendung regelmäßig zu modernisieren. Zum Beispiel müssen aus Sicherheitsgründen Abhängigkeiten zu Drittanbieterbibliotheken aktualisiert werden. Dazu wird die Software nach dem Entwicklungsprojekt meist von einem dedizierten Wartungsteam betreut und weiterentwickelt.

Gute Wartbarkeit ist die Voraussetzung, damit das Wartungsteam in der Lage ist, die Qualität der Software zu erhalten. Die Wartbarkeit muss dazu als nichtfunktionale Anforderung in allen Phasen des Softwarelebenszyklus beachtet werden.

Im ersten Teil dieser zweiteiligen Artikelserie wurden die Themen Dokumentation und Architekturentscheidungen beleuchtet. In diesem Teil soll der Blick auf Tests, Codequalität und die Automatisierung von Prozessen gelenkt werden.

Tests

Gut wartbare Software minimiert den Aufwand für Anpassungen am Code. Bei Fehlerbehebung und für die Implementierung neuer Funktionen muss der Quellcode geändert werden. Aber Codeanpassungen können Seiteneffekte haben. Ein Wartungsteam besteht nicht selten aus wenigen Entwicklern und hat begrenzt Zeit und Budget zur Verfügung. Trotzdem soll es die Fachlichkeit und Technik vollständig verstehen und die bestehende Qualität beibehalten. Um dem Team die dazu notwendige Sicherheit zu geben, ist eine hohe Testabdeckung durch die unterschiedlichen Testarten erforderlich.

Komponententests

Komponententests (Unit-Tests) prüfen das Verhalten der kleinsten Teile, der Komponenten, aus denen die Applikation zusammengesetzt ist. Sie werden vom Entwickler vor oder während der Implementierung der Funktionalität mit erstellt und sind dann regelmäßig, am besten automatisiert, durchzuführen.

Unit-Tests sollten eine hohe Testabdeckung von mindestens 80 Prozent erreichen, wobei die 100 Prozent durchaus anzustreben sind. Durch entsprechende Konfiguration der verwendeten Werkzeuge kann zum Beispiel automatisch generierter Code (Lombok) ignoriert werden. Der Testcode sollte dabei von gleicher Qualität wie der Anwendungscode sein. Ein perfekter Komponententest ist gut lesbar und verständlich, erzielt eine hohe Abdeckung (Code-Coverage) und prüft dabei das Verhalten der Komponente in allen Aspekten.

Integrations- und Schnittstellentests

Integrationstests prüfen die Zusammenarbeit mehrerer einzelner Komponenten, aus denen die Applikation besteht. Schnittstellentests stellen hingegen die Korrektheit der dabei ausgetauschten Daten sicher. Es müssen automatisiert und regelmäßig zumindest alle Kernfunktionalitäten geprüft werden. Für den Rest sind Kosten und Nutzen abzuwägen. Zur Analyse, für welche Prozesse entsprechende Tests sinnvoll sind, hilft die Einteilung der Anwendungsfälle in folgende Kategorien:

A – Dauerhaft verwendete, geschäftskritische Kernprozesse.

B – Regelmäßig verwendete Standardprozesse, deren Ausfall eine hohe Auswirkung hätte.

C – Selten verwendete Prozesse, deren Ausfall eine nur geringe Auswirkung hätte.

Last- und Performance-Tests

Die Einhaltung der nichtfunktionalen Anforderungen sollte durch regelmäßige Last- und Performance-Tests sichergestellt werden. Diese Tests simulieren das reale Nutzerverhalten und können dann eine Aussage über den benötigten Ressourcenbedarf und die notwendige Skalierung des Systems treffen.

Die Tests führen werkzeuggestützt verschiedene Anwendungsfälle in variabler Kombination und Häufigkeit und Parallelität aus. Um diese richtig dimensionieren zu können, müssen vorab die Mengen für Nutzer, Sessions und Daten ermitteln worden sein. Es sind hierbei zumindest die Kernanwendungsprozesse zu prüfen. Zusätzlich müssen diejenigen Prozesse untersucht werden, die mit vielen Daten arbeiten oder komplexe Algorithmen implementieren und dadurch voraussichtlich einen großen Einfluss auf die Performance haben werden.

Codequalität

Bei der Übergabe der Software vom Entwicklungs- an das Wartungsteam, erwartet das Wartungsteam eine entsprechende Qualität der Software. Dabei helfen die Einhaltung der vorgegebenen Guidelines, Code-Reviews sowie der Einsatz von Werkzeugen für die statische Codeanalyse (zum Beispiel SonarQube). Die statische Codeanalyse sollte regelmäßig bei jedem Push in das Repository durchgeführt werden.

Für das Projekt sollte in dem verwendeten Werkzeug ein einheitliches Qualitätsziel (Quality Gate) eingerichtet werden, welches dann konsequent von allen Projektbeteiligten eingehalten wird. Ein gutes Beispiel für ein Quality Gate im SonarQube enthält unter anderem die folgenden Vorgaben:

  • keine Bugs
  • keine Vulnerabilities
  • keine Code Smells vom Typ Blocker
  • keine Code Smells vom Typ Critical
  • die Codeduplikationsrate liegt unter 5 Prozent

Ausnahmen vom gesetzten Qualitätsziel müssen im Quelltext oder dem verwendeten Werkzeug nachvollziehbar begründet sein.

Automatisierung (CI/CD)

In einem Softwareentwicklungsprojekt können alle regelmäßig laufenden, sich selten ändernden Prozesse im Rahmen von CI/CD automatisiert werden. Der Aufwand, um dies werkzeuggestützt (wie in Jenkins, GitLab oder Bamboo) einmalig zu konfigurieren, ist meist geringer als der Aufwand bei regelmäßiger manueller Abarbeitung der Aufgaben. Zusätzlich wird das Fehlerrisiko gesenkt und sichergestellt, dass zur Ausführung immer dieselbe Umgebung und Konfiguration zugrunde liegt.

Build Branch Job

Nach jedem Push ist der Quelltext eines Branches zu kompilieren. Basierend darauf können anschließend Komponententests und die statische Codeanalyse durchgeführt werden. Dies lässt sich unabhängig vom gewählten Branch-Modell für alle Zweige einrichten. Die Autoren haben hier beispielsweise sehr gute Erfahrung mit der Kombination des Git-flow-Workflows mit Bitbucket Pull Requests, einer Jenkins Multibranch-Pipeline und dem SonarQube Server. Andere Tools bieten jedoch ebensolche Funktionen.

Build Version Job

Regelmäßig, zum Beispiel nach jeder Änderung des Entwicklungszweigs, kann eine aktuelle Version der Software erstellt und diese in einem zentralen Repository abgelegt werden. Die so erstellten Artefakte können bereits als fertiger Release Candidate (RC) bezeichnet und später als Release ausgeliefert werden, sofern alle Tests und Prüfungen erfolgreich waren. Außerdem lassen sich in diesem Schritt, weitere Aufgaben wie Security-Checks oder eine Lizenzprüfung ergänzen.

Deploy Jobs

Der zuvor erstellte Release Candidate kann nun automatisiert auf eine Testumgebung ausgerollt (deploy) werden. Dazu wird automatisiert die Testumgebung (zum Beispiel mit Docker) vorbereitet oder komplett neu erstellt, konfiguriert und gestartet.

Thilko Lange, adesso SE

„In einem Softwareentwicklungsprojekt können alle regelmäßig laufenden, sich selten ändernden Prozesse im Rahmen von CI/CD automatisiert werden.“

Thilo Lange, adesso SE

Anschließend lassen sich auf dieser Umgebung manuelle Tests durchführen, gemeldete Fehler reproduzieren oder beispielsweise während eines Sprint Reviews der Arbeitsfortschritt präsentieren. Ergänzend kann ein weiterer Job nach jedem Release eine Umgebung mit der aktuellen Release-Version bereitstellen, um darauf aus der Produktivumgebung gemeldete Fehler nachstellen und Hotfixes bearbeiten zu können.

Test-Jobs

Regelmäßig, im besten Fall für jeden Release Candidate, zumindest aber vor jedem Release, sollten die Integrations-, Schnittstellen, Last- und Performance-Tests ebenfalls automatisiert ausgeführt werden. Dazu bereitet ein Deploy-Job eine der Testumgebungen vor und importiert oder generiert dann die dem Test zugrunde liegenden Testdaten. Anschließend werden die Tests durchgeführt und abschließend die Testergebnisse gesammelt, abgelegt und ausgewertet.

Fazit

Durch automatisierte Tests unterschiedlicher Art, Granularität und Abdeckung können Sicherheit und Schnelligkeit für umzusetzende Codeanpassungen erhöht werden. Das gibt dem Wartungsteam Sicherheit bei der Umsetzung notwendiger Codeanpassungen.

Anja Bethge, adesso SE

„Ein perfekter Komponententest ist gut lesbar und verständlich, erzielt eine hohe Abdeckung (Code-Coverage) und prüft dabei das Verhalten der Komponente in allen Aspekten.“

Anja Bethge, adesso SE

Es wurde gezeigt, welche Prozesse im Entwicklungsprojekt werkzeuggestützt automatisiert werden können. Automatisierung hilft, die Produktivität des Teams zu steigern und Fehler rechtzeitig zu erkennen beziehungsweise Fehler, die durch manuelle Wiederholung auftreten, gar nicht erst zu machen.

Um gut wartbare Software zu entwickeln, ist es wichtig, dass rechtzeitig, bereits zu Beginn eines Entwicklungsprojektes, Zeit und Budget bereitgestellt werden. Sowohl für das Planen, Umsetzen, Verwalten und Konfigurieren als auch für das Aufsetzen von Testsystemen und die Verwaltung der CI-Pipeline.

Über die Autoren:
Anja Bethge ist Softwarearchitektin bei der adesso SE und spezialisiert auf die Wartung, Pflege und Weiterentwicklung von (Lagacy-) Software. Sie hat umfangreiche Erfahrung in der Transition und langjähren Pflege verschiedenster Softwareprojekte und hat dabei schon viele gute und auch nicht so gute Beispiele für wartbare Software gesehen.

Thilo Lange ist Softwarearchitekt bei der adesso SE und kann mit seiner langjährigen Berufserfahrung auf ein umfangreiches Wissen in der Umsetzung von Webapplikationen mit Java-Backend zurückgreifen. Er hat bereits mehrere Projekte in der Wartung und Pflege übernommen und sich zum Ziel gesetzt, Software so zu entwickeln, dass sie gut wartbar ist und es auch bleibt.

Die Autoren sind für den Inhalt und die Richtigkeit ihrer Beiträge selbst verantwortlich. Die dargelegten Meinungen geben die Ansichten der Autoren wieder.

Erfahren Sie mehr über Softwareentwicklung