5 Grundbausteine für die Entwicklung qualitativ hochwertiger Software mit Vector Tools

Viele Anwender wissen aus Erfahrung, dass sie neue Software besser erst dann einspielen sollten, wenn die unvermeidlichen Wartungsversionen, Service Packs und Patches zur Verfügung stehen. Selbst große Unternehmen sind nicht davor gefeit, fehlerbehaftete Software zu veröffentlichen. Denken wir nur einmal an die Upgrade-Zyklen der Software für Mobiltelefone: Auf die Hauptversion der Software folgen in der Regel mehrere schnelle Updates zur Fehlerbehebung. Zwar wissen wir alle um die Qualitätslücke zwischen der ersten Version einer Software und ihrer stabilen Version, doch sind bisher nicht viele Fortschritte zur Lösung dieses Problems zu erkennen.

Dieser Artikel erläutert 5 praxistaugliche Grundregeln, mit denen Softwareentwickler die Qualitätslücke schließen können.

1. Testabdeckung

Codeabdeckung analysieren

Die Codeabdeckungsanalyse bzw. Code Coverage Analysis gibt Aufschluss über den Anteil des Quellcodes einer Anwendung, der in einer Reihe von Testfällen tatsächlich ausgeführt wird. Die Analyse der Codeabdeckung ist das beste Mittel, um die Vollständigkeit Ihrer Tests zu messen. Ohne Messung der Code Coverage tappen Sie mit Ihren Tests völlig im Dunkeln.
 

Analysis of code coverage to measure test completeness
Bild 1 – Analyse der Code-Coverage zur Messung der Testvollständigkeit

Dabei gilt es zu bedenken, dass eine „100%ige Codeabdeckung“ zwar noch kein Beweis für eine perfekte Anwendung ist, sie jedoch einen wesentlichen Baustein bei der Entwicklung qualitativ hochwertiger Software darstellt. So schreiben alle Normen, die sich mit der Entwicklung sicherheitskritischer Software befassen, die Codeabdeckung als Teil des Entwicklungsprozesses vor.

 

Mehr zum Thema  “Codeabdeckung” erfahren Sie in folgendem Whitepaper:

2. Unit-Tests

Testabdeckung durch Unit-Tests verbessern

Bild 2 – 100 % Codeabdeckung durch Schließen der Abdeckungslücken mit Hilfe von Unit-Tests

Bei der Messung der Testabdeckung wird man mit den bisher existierenden Tests das Ziel einer 100%-Coverage in der Regel deutlich verfehlen. Diese Abdeckungslücke bzw. Coverage Gap ist darauf zurückzuführen, dass Tester sich auf die üblichen Anwendungsfälle konzentrieren und dabei Fehlerfälle und Randbereiche außer Acht lassen.

Zusätzliche Funktionstests sind das nahe liegende Mittel, um diese Coverage Gap zu schließen. Dennoch werden 20 bis 30 % des Anwendungscodes in einer Produktionsumgebung mit Funktionstests nur sehr schwer zu testen sein, denn eine Fehlerinjektion, d. h. das absichtliche Einfügen von Fehlern, damit auch die für die Fehlerbehandlung zuständigen Codepfade durchlaufen werden, gestaltet sich äußerst kompliziert.

Die in der Praxis auftretenden kritischen Fehler in der Anwendung sind das Ergebnis eines ungewöhnlichen und nicht zu erwartenden Zusammenspiels von Auslösern. Nehmen wir nur einmal den berühmt-berüchtigten Heisenbug, ein Fehler, der verschwindet oder sein Verhalten ändert, wenn man versucht, ihn näher zu untersuchen oder zu isolieren. Für C-Programmierer ist er das Ergebnis nicht initialisierter Variablen. Er sorgt unter den Entwicklern für reichlich Frust, da scheinbar allein die Beobachtung des Codes ihn bereits verändert [1].

Hier ist die Durchführung von Low-Level-Unit-Tests von elementarer Bedeutung. Unit-Tests ermöglichen eine Fehlerinjektion und somit das Testen der Fehlerbehandlung auf eine Art und Weise, wie es in der Produktionsumgebung nicht möglich wäre.

3. Testinfrastruktur

Tests vereinfachen und Ergebnisse verständlicher machen

The test pyramid includes different types of tests: unit tests, service tests, API layer tests, HMI tests functional tests
Bild 3 – Die Test-Pyramide setzt sich aus unterschiedlichen Testtypen zusammen

In der Theorie klingt es nach einem simplen Plan: Sorgen Sie dafür, dass Ihre Tests einfach durchführbar und die Testergebnisse einfach zu verstehen sind. In der Praxis wird dieser Plan jedoch schnell zu einer großen Herausforderung. Seit jeher werden diverse Testvarianten von verschiedenen Entwicklern oftmals unter Einsatz unterschiedlicher Tools entwickelt und gepflegt:

  • Unit-Tests werden verwendet, um die Korrektheit der Low-Level-Bausteine einer Anwendung nachzuweisen.
  • Service-Layer- & API-Layer-Tests dienen dazu, die ordnungsgemäße Funktion ganzer Subsysteme nachzuweisen.
  • Human-Machine-Interface-Tests (HMI) / Funktionstests kommen zum Einsatz, um den korrekten Ablauf aus Sicht des Endnutzers nachzuweisen.

Sind Tests nach obigem Schema gegliedert, wird jede einzelne Testvariante von einer bestimmten Gruppe von Entwicklern betreut und gepflegt, statt dass alle Mitarbeiter des Entwicklungsteams zuständig sind. So wäre es in den meisten Unternehmen vermutlich auch unmöglich, dass ein QS-Ingenieur einen Entwicklertest durchführt oder ein Entwickler einen Systemtest.

Um die Qualität zu verbessern, sollte jedes Mitglied des Entwicklungsteams in der Lage sein, zu jedem Zeitpunkt jede Art von Test mit jeder Version der Software durchzuführen.

Der Schlüssel zu einem solchen Workflow ist eine gemeinsame Kooperationsplattform für Tests, die alle Testvarianten sowie die jeweiligen Voraussetzungen und erwarteten Ergebnisse umfasst. Dabei sollten Entwickler einen einzelnen Test oder auch alle Tests „auf Knopfdruck“ durchführen können. Außerdem ist es essenziell wichtig, dass Entwickler in der Lage sind, die im Rahmen der Tests festgestellten Fehler schnell zu beheben.

 

Weiterführende Informationen hierzu finden Sie in folgendem Dokument:

4. Testeffizienz

Automatisierte, parallele und änderungsbasierte Tests implementieren

Wenn die Vollständigkeit der Tests durch die Analyse der Code Coverage gewährleistet ist und die Tests im gesamten Unternehmen zum Einsatz kommen, muss in einem nächsten Schritt die zügige Durchführung der Tests sichergestellt werden. Einer der Gründe, warum Tests auf verschiedene Gruppen aufgeteilt werden, ist, dass ein vollständiger Systemtest sich unter Umständen über Stunden oder gar Tage hinziehen kann. Wenn ein Entwickler, der eine einzige Codezeile geändert hat, einen zehn Stunden dauernden Test durchführen soll, wird dies natürlich wenig Begeisterung hervorrufen. Wie können wir also die Testdauer reduzieren, gleichzeitig jedoch die Vollständigkeit der Tests gewährleisten?

Der Schlüssel zur Lösung dieses Problems ist die Schaffung einer skalierbaren Testinfrastruktur unter Nutzung paralleler und änderungsbasierter Tests. Die einzelnen Tests müssen sich auf isolierte Einheiten beziehen, einen kleinen Testumfang haben und schnell sein. Zu häufig werden Testsuiten im Laufe der Zeit mit neuen Tests gekoppelt, die einfach in bereits vorhandene Tests eingebunden werden. Tests werden hierdurch anfällig, ihre Pflege wird zeitintensiv. Bei der Auslegung von Tests gilt es deshalb, folgende simple Regel zu befolgen: Jeder Test sollte seine eigenen Voraussetzungen definieren und sich nicht auf das Ergebnis anderer Tests verlassen.

Abgesehen von den Vorteilen bei der Pflege der Tests eröffnet eine Neuauslegung Ihrer Tests auf kleinste Einheiten folgende Möglichkeiten:

  • Änderungsbasiertes Testen: Es werden nur die von der Softwareänderung betroffenen Tests durchgeführt.
  • Parallele Testdurchführung: Es werden mehrere Hundert Einzeltests gleichzeitig durchgeführt.

Auch wenn heute jedes Unternehmen ein Software-Build-System entwickelt hat, das eine automatisierte inkrementelle Anwendungserstellung ermöglicht, haben die meisten Unternehmen dennoch keine inkrementellen Tests implementiert. Viel zu häufig werden Tests zwar regelmäßig, aber nicht fortlaufend, inkrementell und voll automatisiert durchgeführt. Änderungsbasiertes Testen (Change-Based Testing bzw. CBT) analysiert alle Änderungen der Codebasis und selektiert in einem intelligenten Prozess die jeweils von den Änderungen betroffene Teilmenge aller Tests. CBT ermöglicht somit ein lückenloses Testen in einem Bruchteil der Zeit, die sonst für einen vollständigen Testlauf erforderlich wäre. Außerdem bietet das änderungsbasierte Testen einen gangbaren Weg zur Implementierung einer konsequenten Continuous Integration (CI) im Softwareentwicklungsprozess. Während der Check-in-Phase der CI ist das änderungsbasierte Testen ein effizientes Mittel, um den Build zu prüfen und Probleme bzw. Fehler frühzeitig zu erkennen.
 

Bild 4 – Regressionstests der von den Codeänderungen betroffenen Testfälle

Parallele Tests bieten eine weitere Möglichkeit, um zusätzlich Zeit zu sparen. Durch Einbindung eines Continuous-Integration-Servers und virtualisierter Testmaschinen in die Testplattform lässt sich die Gesamttestzeit von Stunden auf Minuten bzw. von Minuten auf Sekunden verkürzen.

5. Refaktorierung

Codebasis zwecks verbesserter Pflege refaktorieren

Bild 5 – Code-Refactoring-Ansatz

Unter dem Refaktorieren des Codes bzw. Code Refactoring versteht man die Neustrukturierung von Anwendungskomponenten, ohne dabei das externe Verhalten des Codes (d. h. die API) zu verändern.

Ohne Refactoring wird der Anwendungscode zu kompliziert und im Laufe der Zeit immer schwieriger zu pflegen. Aufgrund der Einbindung neuer Funktionen und Patches in vorhandene Lösungen, bleibt von dem anfangs eleganten Design meist nicht mehr viel übrig.

Das Code Refactoring verbessert die Lesbarkeit des Codes und reduziert gleichzeitig die Komplexität und somit auch die Wartungskosten. Durch Vereinfachung der zugrunde liegenden Logik und Eliminierung unnötiger Komplexität können mit einem effizienten Code Refactoring zudem versteckte, latente und bisher unentdeckte Fehler oder Sicherheitslücken im System festgestellt und behoben werden.

Jede Anwendung beinhaltet fragile und fehlerhafte Sektionen, die von Softwareentwicklern aus Angst, bestehende Funktionen möglicherweise zu zerstören, nur ungern geändert werden. Der einzige Weg, diese fragilen Module konsequent zu refaktorieren, ist die Erstellung von Tests, mit denen sich das erwartete Verhalten formalisieren lässt.

Fazit

Die vergangenen 30 Jahre waren durch eine Vielzahl neuer Tools und Entwurfsmuster sowie durch zahlreiche Paradigmenwechsel in der Softwareentwicklung gekennzeichnet. Viele von ihnen versprachen Qualitätsverbesserungen ohne zusätzlichen Zeit- oder Arbeitsaufwand. Inzwischen dürfte jedem in der Softwarebranche klar sein, dass ein kostenloses Wundermittel zur Qualitätsverbesserung nicht existiert und auch niemals existieren wird. Der einzig vernünftige Weg zur Verbesserung der Softwarequalität besteht darin, die Effektivität der Softwaretests zu steigern.

--------------------

Quellen:

[1] Hristov, Ivan. Chasing Heisenbugs from an AKKA actor integration test with awaitility. September 16, 2012. honeysoft.wordpress.com/category/heisenbug/