Python-Programme über PIP unter Linux installieren

Vor sehr vielen Jahren hat die nach der britischen Komikertruppe benannte Skriptsprache Python unter Linux das altehrwürdige Perl abgelöst. Das heißt, jede Linux Distribution enthält einen Python-Interpreter von Hause aus. Eigentlich eine recht praktische Sache. Eigentlich! Wenn da nicht das leidige Thema mit der Sicherheit wäre. Aber fangen wir einmal der Reihe nach an, denn dieser kleine Artikel ist für Leute gedacht, die unter Linux in Python geschriebene Software zum Laufen bringen möchten, aber selbst weder Python kennen noch irgendetwas mit Programmieren am Hut haben. Daher ein paar kleine Hintergrundinformationen, um zu verstehen, worum es überhaupt geht.

Alle aktuellen Linux Varianten, die von der Distribution Debian abgeleitet sind, also Ubuntu, Mint und so weiter, werfen einen kryptischen Fehler, wenn man versucht, ein Pythonprogramm zu installieren. Da man vermeiden möchte, dass wichtige Systembibliotheken, die in Python geschrieben wurden, durch die Installation zusätzlicher Programme überschrieben werden und dadurch ein Fehlverhalten im Betriebssystem hervorrufen, wurde ein Schutz eingebaut, der das verhindert. Leider steckt auch hier wie so oft der Teufel im Detail.

ed@P14s:~$ python3 -m pip install ansible
error: externally-managed-environment

× This environment is externally managed
╰─> To install Python packages system-wide, try apt install
    python3-xyz, where xyz is the package you are trying to
    install.
    
    If you wish to install a non-Debian-packaged Python package,
    create a virtual environment using python3 -m venv path/to/venv.
    Then use path/to/venv/bin/python and path/to/venv/bin/pip. Make
    sure you have python3-full installed.
    
    If you wish to install a non-Debian packaged Python application,
    it may be easiest to use pipx install xyz, which will manage a
    virtual environment for you. Make sure you have pipx installed.
    
    See /usr/share/doc/python3.13/README.venv for more information.

Als Lösung soll nun eine virtuelle Umgebung eingerichtet werden. Debian 12 und auch das gerade im August 2025 frisch erschienene Debian 13 nutzen Python in der Version 3. Python2 und Python 3 sind zueinander nicht kompatibel. Das heißt, in Python2 geschriebene Programme laufen nicht ohne Weiteres in Python 3.

Wenn man nun unter Python irgendein Programm installieren möchte, erledigt dies der sogenannte Paketmanager. Die meisten Programmiersprachen haben einen solchen Mechanismus. Der Paketmanager für Python heißt PIP. Hier kommen schon die ersten Komplikationen ins Spiel. Es gibt pip, pip3 und pipx. Solche Namensinkonsistenzen findet man auch beim Python Interpreter selbst. Version 2 startet man auf der Konsole mit python und Version 3 mit python3. Da sich dieser Artikel auf Debian 12 / Debian 13 und seine Derivate bezieht, wissen wir, dass zumindest mal Python 3 am Start ist. Um die tatsächliche Python Version zu erfahren, kann man auf der Shell auch python3 -V eingeben, was bei mir die Version 3.13.5 zutage fördert. Versucht man indies python oder python2 kommt eine Fehlermeldung, dass der Befehl nicht gefunden werden konnte.

Schauen wir uns zuerst einmal an, was überhaupt pip, pip3 und pipx bedeuten soll. PIP selbst heißt nichts anderes als Package Installer for Python [1]. Bis Python 2 galt PIP und ab Version 3 haben wir entsprechend auch PIP3. Ganz speziell ist PIPX [2] welches für isolierte Umgebungen gedacht ist, also genau das, was wir benötigen. Daher geht es im nächsten Schritt darum, PIPX zu installieren. Das erreichen wir recht leicht über den Linux-Paketmanager: sudo apt install pipx. Um festzustellen, welche PIP Variante bereits auf dem System installiert ist, benötigen wir folgenden Befehl: python3 -m pipx --version, der in meinem Fall 1.7.1 ausgibt. Das heißt, ich habe auf meinem System als Ausgangsbasis das originale Python3 und dazu PIPX installiert.

Mit der gerade beschriebenen Voraussetzung kann ich nun unter PIPX alle möglichen Python Module installieren. Der Grundbefehl lautet pipx install <module>. Um ein nachvollziehbares Beispiel zu schaffen, wollen wir jetzt im nächsten Schritt Ansible installieren. Auf die Verwendung von pip und pip3 sollte verzichtet werden, da diese zum einen erst installiert werden müssen und zum anderen zu dem eingangs krypischen Fehler führen.

Ansible [3] ist ein Programm, das in Python geschrieben wurde und ab der Version 2.5 auf Python 3 migriert wurde. Nur sehr knapp ein paar Worte zu dem, was Ansible selbst für ein Programm ist. Ansible gehört in die Klasse der Konfigurationsmanagementprogramme und erlaubt es völlig automatisiert mit einem Skript Systeme zu provisionieren. Eine Provisionierung kann zum Beispiel als virtuelle Maschine erfolgen und umfasst das Festlegen der Hardware-Ressourcen (RAM, HDD CPU Kerne, etc.), Installieren des Betriebssystems, das Einrichten des Benutzers und das Installieren und Konfigurieren weiterer Programme.

Als Erstes müssen wir Ansible mit pipx install ansible installieren. Wenn die Installation beendet ist, können wir den Erfolg mit pipx list überprüfen, was bei mir zu folgender Ausgabe führt:

ed@local:~$ pipx list
venvs are in /home/ed/.local/share/pipx/venvs
apps are exposed on your $PATH at /home/ed/.local/bin
manual pages are exposed at /home/ed/.local/share/man
   package ansible 12.1.0, installed using Python 3.13.5
    - ansible-community

Die Installation ist aber hier noch nicht beendet, denn der Aufruf ansible –version wird mit einer Fehlermeldung quittiert. Das Problem, welches sich hier ergibt, hat etwas mit der Ansible Edition zu tun. Wie wir bereits in der Ausgabe von pipx list sehen können, haben wir die Community Edition installiert. Weswegen der Befehl ansible-community --version lautet, der bei mir aktuell die Version 12.2.0 zutage fördert.

Wer lieber in der Konsole ansible anstatt ansible-community eingeben möchte, kann das über eien alias erreichen. Das Setzen des Alias ist hier nicht ganz so trivial, da dem Alias Parameter übergeben werden müssen. Wie das gelingt, ist aber Thema eines anderen Artikels.

Gelegentlich kommt es aber auch vor, dass Pythonprogramme über PIPX nicht installiert werden können. Ein Beispiel hierfür ist streamdeck-ui [4]. Die Hardware StreamDeck von Elgato konnte lange Zeit unter Linux mit dem in Python implementierten stremdeck-ui genutzt werden. Mittlerweile gibt es noch eine Alternative namens Boatswain, die nicht in Python geschrieben wurde, auf die man besser ausweichen sollte. Denn leider führt die Installation von streamdeck-ui zu einem Fehler, der aus der Abhängigkeit zur Bibliothek ‘pillow’ herrührt. Versucht man nun, das Installationsscript aus dem Git Repository von streamdeck-ui zu nutzen, findet man einen Verweis auf die Installation von pip3, über das streamdeck-ui bezogen werden kann. Kommt man dann an den Punkt, um den Befehl pip3 install --user streamdeck_ui auszuführen, erhält man die Fehlermeldung, externally-managed-environment die ich zu Anfang des Artikels bereits beschrieben habe. Da wir bereits PIPX nutzen, ist es nicht zielführend, eine weitere virtuelle Umgebung für Python Programme zu erstellen, da diese auch nur wieder zu dem Fehler mit der Bibliothek pillow führen wird.

Da ich selbst kein Python-Programmierer bin, aber durchaus einige Erfahrungen mit komplexen Abhängigkeiten in großen Java Projekten habe und das Programm streamdeck-ui eigentlich besser als Boatwain empfunden hatte, habe ich mich ein wenig im GitHub Repository umgeschaut. Zuerst fällt auf, dass die letzte Aktivität im Frühjahr 2023 stattgefunden hat und eine Reaktivierung eher unwahrscheinlich ist. Schauen wir uns dennoch einmal die Fehlermeldung genauer an, um eine Idee zu bekommen, wie man bei Installationen anderer Programme das Problem eingrenzen kann.

Fatal error from pip prevented installation. Full pip output in file:
    /home/ed/.local/state/pipx/log/cmd_pip_errors.log

pip seemed to fail to build package:
    'pillow'

Wenn man nun einen Blick in die zugehörige Logfile wirft, erfährt man, dass die Abhängigkeit von pillow kleiner als Version 7 und größer als Version 6.1 definiert ist, was zur Verwendung von der Version 6.2.2 führt. Schaut man nun einmal nach, was pillow eigentlich macht, erfährt man, dass dies eine Bibliothek für Python 2 war, mit der Grafiken gerendert werden konnten. Die in streamdeck-ui verwendete Version ist ein Fork von pillow für Python 3 und Ende 2025 bereits in der Version 12 verfügbar. Möglicherweise könnte das Problem dadurch behoben werden, indem eine aktuelle Version von pillow zum Einsatz kommt. Damit das gelingt, sind mit hoher Wahrscheinlichkeit auch Anpassungen am Code von streamdeck-ui vorzunehmen, da vermutlich seit der Version 6.2.2 einige Inkompatibilitäten der genutzten Funktionen vorhanden sind.

Mit dieser Analyse sehen wir, dass die Wahrscheinlichkeit, streamdeck-ui unter pip3 zum Laufen zu bekommen, in dasselbe Ergebnis führt wie mit PIPX. Wer nun auf die Idee kommt, unbedingt auf Python 2 zu downzugraden, um alte Programme zum Laufen zu bekommen, der sollte dies besser in einer gesonderten, isolierten Umgebung, zum Beispiel mit Docker versuchen. Python 2 hat seit vielen Jahren keinen Support durch Updates und Security Patches, weswegen eine parallele Installation zu Python 3 auf dem eigenen Betriebssystem keine gute Idee ist.

Wir sehen also, dass die anfangs beschriebene Fehlermeldung gar nicht so kryptisch ist, wenn man indes gleich auf PIPX setzt. Bekommt man sein Programm dann immer noch nicht installiert, hilft ein Blick auf die Fehlermeldung, der uns meist darüber aufklärt, dass wir ein veraltetes und nicht mehr gepflegtes Programm verwenden wollen.

Resources

Abonnement / Subscription

[English] This content is only available to subscribers.

[Deutsch] Diese Inhalte sind nur für Abonnenten verfügbar.


RTFM – benutzbare Dokumentationen

Ein alter Handwerksmeister pflegte immer zu sagen: Wer schreibt, der bleibt. Seine Intention war vor allem, ein vernünftiges Aufmaß und Wochenberichte seiner Gesellen zu bekommen. Diese Informationen benötigte er, um eine korrekte Rechnung stellen zu können, was für den Erfolg des Unternehmens maßgeblich war. Dieses Bild lässt sich auch gut auf die Softwareentwicklung übertragen. Erst als die in Japan von Yukihiro Matsumoto entwickelte Programmiersprache Ruby eine englischsprachige Dokumentation besaß, begann der weltweite Siegeszug von Ruby.

Wir sehen also, dass Dokumentation einen durchaus hohen Stellenwert für den Erfolg eines Softwareprojektes haben kann. Es beschränkt sich nicht nur auf einen Informationsspeicher im Projekt, wo neue Kollegen notwendige Details erfahren. Natürlich ist für Entwickler Dokumentation ein recht leidiges Thema. Stetig muss diese aktuell gehalten werden und oft fehlen auch Fertigkeiten, um die eigenen Gedanken sortiert und nachvollziehbar für andere auf Papier zu bringen.

Ich selbst kam vor sehr vielen Jahren erstmalig mit dem Thema Dokumentation durch das Lesen des Buches „Softwaretechnik“ von Johannes Siedersleben in Berührung. Dort wurde Ed Yourdon mit der Aussage zitiert, dass vor Methoden wie UML die Dokumentation oft in der Form einer viktorianischen Novelle vorlag. Während meines Berufslebens habe ich auch einige solcher viktorianischen Novellen angetroffen. Das Ärgerliche daran war: Nachdem man sich durch die Textwüste gekämpft hatte – anders als mit Überwindung und Kampf kann man das Gefühl nicht beschreiben –, hatte man die gesuchten Informationen immer noch nicht. Frei nach Goethes Faust: „So steh ich da ich armer Tor und bin so klug als wie zuvor.“

Hier sehen wir bereits einen ersten Kritikpunkt für schlechte Dokumentationen: eine unangemessene Länge der Ausführungen, die wenig Informationen enthalten. Hier müssen wir uns der Tatsache bewusst werden, dass das Schreiben nicht jedem in die Wiege gelegt wurde. Schließlich ist man ja Softwareentwickler und nicht Buchautor geworden. Das bedeutet für das Konzept „erfolgreiche Dokumentation“, dass man möglichst niemanden zu seinem Glück zwingen sollte und sich besser im Team nach Personen umschaut, die für Dokumentation ein Händchen haben. Das soll nun aber nicht bedeuten, dass alle anderen von der Aufgabe Dokumentation freigestellt sind. Ihr Input ist für die Qualität essenziell. Korrekturlesen, auf Fehler hinweisen und Ergänzungen vorschlagen sind durchaus notwendige Punkte, die sich gut auf vielen Schultern verteilen lassen.

Es ist durchaus ratsam, das Team, oder einzelne Teammitglieder gelegentlich rhetorisch zu schulen. Dabei sollte der Fokus auf einer präzisen, kompakten und verständlichen Ausdrucksweise liegen. Dabei geht es auch darum, die eigenen Gedanken so zu sortieren, dass diese auf Papier gebracht werden können und dabei einem roten Faden folgen. Die dadurch verbesserte Kommunikation wirkt sich sehr positiv auf die Entwicklungsprojekte aus.

Eine aktuell gehaltene Dokumentation, die gut zu lesen ist und wichtige Informationen enthält, wird schnell zu einem lebenden Dokument, ganz gleich, welche Formen gewählt wurden. Dies ist auch ein grundlegendes Konzept für erfolgreiches DevOps und agile Vorgehensmodelle. Denn diese Paradigmen setzen auf einen guten Informationsaustausch und adressieren auch das Vermeiden von sogenannten Informationssilos.

Ein Punkt, der mich wirklich triggert, ist die Aussage: Unsere Tests sind die Dokumentation. Nicht alle Stakeholder können programmieren und sind daher auch nicht in der Lage, die Testfälle zu verstehen. Zudem demonstrieren Tests zwar das Verhalten von Funktionen, sie demonstrieren aber nicht per se die richtige Verwendung. Meist fehlen auch Variationen von verwendbaren Lösungen. Damit Testfälle einen dokumentativen Charakter haben, ist es notwendig, spezielle Tests exakt für diesen Zweck zu entwickeln. Dieses Vorgehen hat meiner Ansicht nach zwei gravierende Vorteile. Zum Ersten bleibt die Dokumentation zur Implementierung aktuell, denn bei Änderungen schlägt der Testfall fehl. Ein weiterer positiver Effekt ist, dass der Entwickler über die Verwendung seiner Implementierung bewusst wird und einen ungünstigen Entwurf zeitnah korrigieren kann.

Natürlich gibt es mittlerweile unzählige technische Lösungen, die je nach Sichtweise auf das System für unterschiedliche Personengruppen geeignet sind. Issue- und Bug-Tracking Systeme wie beispielsweise das kommerzielle JIRA oder das freie Redmine bilden ganze Prozesse ab. Sie erlauben es den Testern, erkannte Probleme und Fehler der Software einer Releaseversion zuzuordnen. Projektleiter können mit dem Release Management eine Priorisierung der Korrekturen vornehmen und die Entwickler dokumentieren die verwendete Korrektur. Soweit die Theorie. In der Praxis habe ich in nahezu jedem Projekt erlebt, wie in diesen Systemen die Kommentarfunktion als Chat missbraucht wurde, um den Änderungsstatus zu beschreiben. Als Ergebnis hat man eine Fehlerbeschreibung mit unzähligen nutzlosen Kommentaren und wirkliche weiterführende Informationen fehlen komplett.

Eine weitverbreitete technische Lösung in Entwicklungsprojekten sind auch sogenannte Enterprise Wikis. Sie ergänzen einfache Wikis durch eine Navigation und ermöglichen das Erstellen geschlossener Spaces, auf die nur explizit zugelassene Nutzergruppen feingranulare Berechtigungen wie Lesen oder Schreiben erhalten. Neben der weitverbreiteten kommerziellen Lösung Confluence gibt es auch eine freie Variante namens Blue Spice, die auf dem MediaWiki basiert. Wikis erlauben die kollaborative Arbeit an einem Dokument und die einzelnen Seiten können auch über verschiedene Zusammenstellungen als PDF exportiert werden. Damit die Wikiseiten auch benutzbar bleiben, sollte man Wert auf eine saubere und möglichst einheitliche Formatierung legen. Tabellen sollten mit ihrem Inhalt auf eine A4 Seite passen, ohne dass es zu unerwünschten Umbrüchen kommt. Das verbessert den Lesefluss. Es gibt auch viele Fälle, in denen Aufzählungen der Übersichtshalber Tabellen vorzuziehen sind.

Dies bringt uns auch zu einem weiteren sehr heiklen Thema, den Grafiken. Es ist durchaus korrekt, dass ein Bild oft mehr als tausend Worte sagt. Aber eben nicht immer! Im Umgang mit Grafiken ist es wichtig, sich bewusst zu sein, dass Bilder oft einiges an Zeit für die Erstellung benötigen und oft auch nur mit viel Aufwand angepasst werden können. Daraus ergeben sich einige Konsequenzen, um sich das Leben zu erleichtern. Zum Erstellen von Grafiken wird sich auf ein Standardprogramm (Format) festgelegt. Auf teure Grafikprogramme wie Photoshop und Corel ist zu verzichten. Grafiken, die für Wikiseiten erstellt wurden, sind in ihrem Original, also der änderbaren Datei, an die Wikiseite anzufügen. Es kann auch ein eigenes Repository dafür aufgebaut werden, um so eine Wiederverwendung in anderen Projekten zu ermöglichen.

Wenn ein Bild keinen Mehrwert bedeutet, sollte man darauf besser verzichten. Dazu ein kleines Beispiel. Es ist nicht notwendig, eine Grafik anzufertigen, auf der 10 Strichmännchen abgebildet sind, unter denen dann der Rollenname oder eine Person steht. Hier ist es zielführend, eine einfache Aufzählung anzufertigen, die sich im Übrigen auch leichter ergänzen beziehungsweise anpassen lässt.

Aber auch auf überfrachtete Grafiken sollte man verzichten. Treu nach dem Motto „Viel hilft viel“: Sorgen zu detaillierte Informationen eher für Verwirrung und können zu Missinterpretationen führen. Eine Buchempfehlung ist „Softwarearchitekturen dokumentieren und kommunizieren“ von Stefan Zörner. Er arbeite in diesem Titel optimal heraus, wie wichtig die verschiedenen Sichtweisen auf ein System sind und welche Personengruppen mit einer expliziten Sicht angesprochen werden. Dazu möchte ich auch die Gelegenheit nutzen, um seine 7. Regeln für eine gute Dokumentation wiederzugeben.

  1. Schreibe aus Sicht des Lesers.
  2. Vermeide unnötige Wiederholungen.
  3. Vermeide Mehrdeutigkeiten, wenn nötig Notation erläutern.
  4. Verwende Standards wie z. B. UML.
  5. Halte Begründungen (Warum) fest.
  6. Die Dokumentation ist aktuell zu halten, aber nie zu aktuell.
  7. Überprüfe die Gebrauchstauglichkeit (Review).

Wer im Projekt damit beauftragt ist, die Dokumentation zu schreiben beziehungsweise deren Fortschritt und Aktualität sicherzustellen hat, sollte sich immer bewusst sein, dass wichtige Informationen enthalten sind, diese auch korrekt und verständlich dargestellt werden. Eine kompakte und übersichtliche Dokumentation lässt sich auch bei fortschreitendem Projekt problemlos anpassen und erweitern. Anpassungen gelingen immer dann am besten, wenn der betroffene Bereich möglichst zusammenhängend ist und möglichst nur einmal vorkommt. Diese Zentralisierung erreicht man durch Referenzen und Verlinkungen, so dass die Änderung im Original sich auf die Referenzen auswirkt.

Natürlich gibt es zum Thema Dokumentation noch viel mehr zu sagen, schließlich ist es Gegenstand verschiedener Bücher, aber das würde den Rahmen dieses Artikels übersteigen. Mir ging es vor allem darum, für das Thema eine Sensibilisierung zu schaffen, denn Paradigmen wie Agilität und DevOps basieren auf einem guten Informationsfluss.


BugChaser – Die Grenze der Testabdeckung

Die mittlerweile im Software Engineering etablierten Paradigmen wie Test Driven Development (TDD) und Behavior Driven Development (BDD) mit entsprechend einfach zu bedienenden Werkzeugen haben eine neue pragmatische Sichtweise auf das Thema Software Tests eröffnet. Ein wichtiger Faktor in kommerziellen Softwareprojekten sind automatisierte Tests. Deshalb spricht man in diesem Kontext von einer erfolgreichen Teststrategie, wenn die Testausführung ohne menschliches Zutun vonstattengeht.

Testautomatisierung bildet die Grundlage, um Stabilität und Risikoreduzierung bei kritischen Arbeiten zu erreichen. Zu solchen kritischen Tätigkeiten zählen insbesondere das Refactoring, Maintenance und Fehlerkorrekturen. Allen diesen Aktivitäten obliegt eine Gemeinsamkeit: dass sich keine neuen Fehler in den Code einschleichen dürfen.

In dem Artikel „The Humble Programmer“ von 1972 stellte Edsger W. Dijkstra folgendes fest:

„Programmtests können ein sehr effektiver Weg sein, um das Vorhandensein von Fehlern aufzuzeigen, sind aber völlig unzureichend, um deren Abwesenheit nachzuweisen.“

Eine alleinige Automatisierung der Testausführung ist deshalb nicht ausreichend, um sicherzustellen, dass Änderungen der Codebasis keine unerwünschten Effekte auf bestehende Funktionen haben. Aus diesem Grund muss die Qualität der Testfälle bewertet werden. Hierzu gibt es bereits bewährte Werkzeuge.

Bevor wir tiefer in die Thematik einsteigen, wollen wir uns zuerst überlegen, was eigentlich automatisiertes Testen bedeutet. Diese Frage ist recht einfach zu beantworten. Nahezu jede Programmiersprache hat ein entsprechendes Unit Test Framework. Unit Tests rufen eine Methode mit verschiedenen Parametern auf und vergleichen den Rückgabewert mit einem Erwartungswert. Stimmen beide Werte überein, gilt der Test als bestanden. Zusätzlich kann noch überprüft werden, ob eine Ausnahme geworfen wurde.

Für den Fall, dass eine Methode keinen Rückgabewert hat oder keinen Fehler wirft, kann diese Methode nicht getestet werden. Auch als private gekennzeichnete Methoden oder innere Klassen, sind nicht ohne Weiteres zu testen, da sie nicht direkt aufgerufen werden können. Diese sind über öffentliche Methoden, welche die ‚versteckten‘ Methoden aufrufen, zu testen.

Im Umgang mit als private gekennzeichneten Methoden ist es keine Option, die dadurch abgebildete Funktionalität über Techniken wie die Verwendung der Reflection API zu erreichen und zu testen. Denn wir müssen uns bewusst machen, dass solche Methoden oft auch dazu verwendet werden, Fragmente zu kapseln, um Dopplungen zu vermeiden.

public boolean method() {
    boolean success = false;
    List collector = new ArryList();
    collector.add(1);
    collector.add(2);
    collector.add(3);
    
    sortAsc(collector);
    if(collector.getFirst().equals(1)) {
        success = true;
    }
    return success;
}

private void sortAsc(List collection) {
    collection.sort( 
            (a, b) -> { 
                return -1 * a.compareTo(b); 
            });
}

Um also effektiv automatisierte Tests schreiben zu können, ist es notwendig, einem gewissen Codingstil zu folgen. Das vorangegangene Listing 1 demonstriert auf einfache Weise, wie testbarer Code aussehen kann.

Da Entwickler für ihre eigene Implementierung auch die zugehörigen Komponententests schreiben, ist das Problem von schwer testbarem Code in Projekten, die einem testgetriebenen Ansatz folgen, weitgehend eliminiert. Die Motivation zu testen liegt nun beim Entwickler, da dieser mit diesem Paradigma feststellen kann, ob seine Implementierung sich wie gewünscht verhält. Dabei müssen wir uns aber fragen: Ist das bereits alles, was wir tun müssen, um gute und stabile Software zu entwickeln?

Wie wir uns bei solchen Fragen immer denken können, lautet die Antwort nein. Ein essenzielles Werkzeug, um die Qualität der Tests zu bewerten, ist das Erreichen einer möglichst hohen Testabdeckung. Dabei wird zwischen Branch und Line Coverage unterschieden. Um den Unterschied etwas besser zu verdeutlichen, schauen wir kurz auf den Pseudocode in Listing 2.

if( Expression-A OR Expression-B ) {
print(‘allow‘);
} else {
print(‘decline‘);
}

Unser Ziel ist es, nach Möglichkeit alle Zeilen zu durchlaufen. Dazu brauchen wir bereits zwei separate Testfälle. Einen für das Betreten des IF-Zweiges und einen für das Betreten des ELSE-Zweiges. Damit wir aber auch eine hundertprozentige Branch-Coverage erzielen, müssen wir alle Varianten des IF-Zweiges abdecken. Für das Beispiel heißt das: ein Test, der Expression-A true werden lässt, und ein weiterer Test, der Expression-B true werden lässt. Daraus ergeben sich insgesamt drei verschiedene Testfälle.

Der Screenshot aus dem Projekt TP-CORE zeigt, wie eine solche Testabdeckung in ‚echten‘ Projekten aussehen kann.

Natürlich ist dieses Beispiel sehr einfach und es gibt im wirklichen Leben oft Konstrukte, bei denen man trotz aller Bemühungen nicht alle Lines beziehungsweise Branches erreicht. Sehr typisch sind Exceptions aus Fremdbibliotheken, die zu fangen sind, aber unter normalen Umständen nicht provoziert werden können.

Aus diesem Grund versuchen wir zwar, eine möglichst hohe Testabdeckung zu erreichen, und streben natürlich die 100 % an, aber es gibt genügend Fälle, in denen dies nicht möglich ist. Eine Testabdeckung von 90% gelingt aber durchaus. Der Industriestandard für kommerzielle Projekte liegt bei 85 % Testabdeckung. Mit diesen Erkenntnissen können wir also sagen, dass die Testabdeckung mit der Testbarkeit einer Anwendung korreliert. Das bedeutet, die Testabdeckung ist ein geeignetes Maß für die Testbarkeit.

Hier muss man allerdings auch zugeben, dass die Metrik der Testabdeckung ihre Grenze hat. Reguläre Ausdrücke und Annotationen zur Datenvalidierung sind nur einige einfache Beispiele für eine nicht aussagefähige Testabdeckung.

Ohne allzu sehr in die Implementierungsdetails einzugehen, stellen wir uns vor, wir müssten einen regulären Ausdruck schreiben, um Eingaben auf ein korrektes 24 Stunden Format zu überprüfen. Haben wir das korrekte Intervall nicht im Auge, ist unser regulärer Ausdruck möglicherweise nicht korrekt. Das richtige Intervall für das 24-Stunden-Format lautet: 00:00 – 23:59. Beispiele für fehlerhafte Werte sind 24:00 oder 23:60. Ist uns dieser Fakt nicht bewusst, können trotz Testfällen Fehler in unserer Anwendung versteckt sein, bis sie verwendet werden un zu Fehlern führen.

Dies ist ein hervorragendes Beispiel für das Zitat von Dijkstra zu Beginn des Artikels. Zudem möchte ich noch ein weiteres Zitat aus einem Artikel anführen, an dem Christian Bird mitgewirkt hat. Der Artikel heißt „The Design of Bug Fixes“ und ist aus dem Jahr 2013.

„… In a few cases, participants were unable to think of alternative solutions …“

Hier ging es um die Fragestellung, ob eine Fehlerkorrektur immer die optimale Lösung darstellt. Abgesehen davon wäre zu klären, was in kommerziellen Softwareentwicklungsprojekten eine optimale Lösung darstellt. Sehr demonstrativ ist die Aussage, dass es Fälle gibt, in denen Entwickler nur einen Weg kennen beziehungsweise verstehen. Das spiegelt auch unser Beispiel der RegEx wider. Softwareentwicklung ist ein Denkprozess, der sich auch nicht beschleunigen lässt. Unser Denken wird durch unsere Vorstellungskraft bestimmt, die wiederum von unserer Erfahrung beeinflusst ist.

Dies zeigt uns bereits ein weiteres Beispiel für Fehlerquellen in Testfällen. Ein Klassiker sind z. B. inkorrekte Vergleiche in Collections. Es geht unter anderem um das Vergleichen von Arrays. Die Problematik, mit der wir hier zu kämpfen haben, ist das Thema, wie auf Variablen zugegriffen wird: Call by Value oder Call by Reference. Bei Arrays erfolgt der Zugriff über Call by Reference, also direkt auf die Speicherzelle. Weist man nun ein Array einer neuen Variable zu und vergleicht beide Variablen, sind diese immer gleich, da man das Array mit sich selbst vergleicht. Ein Beispiel für einen Testfall, der eigentlich keine Aussagekraft hat. Ist die Implementierung dennoch korrekt, wird dieser fehlerhafte Testfall nie ins Gewicht fallen.

Diese Erkenntnis zeigt uns, dass ein blindes Erreichen der Testabdeckung für die Qualität nicht zielführend ist. Natürlich ist es verständlich, dass im Management diese Metrik einen hohen Stellenwert hat. Wir haben aber auch nachweisen können, das man sich darauf alleine nicht verlassen darf. Wir sehen also, dass auch für Testfälle Codeinspektionen und Refactorings ein Bedarf besteht. Da man aus Zeitgründen nun nicht den ganzen Code von vorn bis hinten lesen und auch verstehen kann, ist es wichtig, auf problematische Bereiche zu konzentrieren. Wie kann man aber diese Problembereiche finden? Hier hilft uns eine vergleichsweise neue Technik. Die theoretischen Arbeiten dazu sind bereits etwas älter, es hat nur eine Weile gedauert, bis entsprechende Implementierungen verfügbar wurden.

Mutation Testing. Dies erlaubt, durch Veränderung des Originalcodes Testfälle zu finden, die trotz verschiedener Mutationen nicht fehlschlagen. Damit haben wir ein weiteres Werkzeug an der Hand, die Qualität der vorhandenen Tests zu bewerten. In diesem Artikel habe ich zeigen können, dass man sich nicht komplett auf die Testcoverage verlassen darf. Um dieses Problem zu lösen, können wir uns des Mutation Testing bedienen. Wie konkret Mutation Testing funktioniert, kann wiederum Thema eines eigenständigen Artikels sein und würde an dieser Stelle den Rahmen sprengen.

Kochbuch: Maven Codebeispiele

In unserem Git Repository befindet sich eine umfangreiche Sammlung an verschiedenen Codebeispielen für Apache Maven Projekte. Alles gut übersichtlich nach Themengebieten sortiert.

Zurück zum Inhaltsverzeichniss: Apache Maven Master Class

  1. Token Replacement
  2. Compiler Warnings
  3. Excecutable JAR Files
  4. Enforcments
  5. Unit & Integration Testing
  6. Multi Module Project (JAR / WAR)
  7. BOM – Bill Of Materials (Dependency Management)
  8. Running ANT Tasks
  9. License Header – Plugin
  10. OWASP
  11. Profiles
  12. Maven Wrapper
  13. Shade Ueber JAR (Plugin)
  14. Java API Documantation (JavaDoc)
  15. Java Sources & Test Case packaging into JARs
  16. Docker
  17. Assemblies
  18. Maven Reporting (site)
  19. Flatten a POM
  20. GPG Signer