056.1 Lektion 1
Zertifikat: |
Open Source Essentials |
---|---|
Version: |
1.0 |
Thema: |
056 Zusammenarbeit und Kommunikation |
Lernziel: |
056.1 Werkzeuge der Softwareentwicklung |
Lektion: |
1 von 1 |
Einführung
Es gibt Tausende Werkzeuge zur Softwareentwicklung — sowohl Open Source als auch proprietär. Warum auch nicht? Programmierer lieben es, Werkzeuge für sich selbst und ihre Kollegen zu entwickeln; es ist nur natürlich, Zeit in die Entwicklung von Tools zu investieren, die besser funktionieren, Ärgernisse im Arbeitsablauf beseitigen oder die Bereitstellung erleichtern.
Diese Lektion konzentriert sich auf den Prozess der Softwareentwicklung und erklärt, welche Rolle verschiedene Arten von Entwicklungswerkzeugen in diesem Prozess spielen. Es werden nur wenige spezifische Werkzeuge genannt; möglicherweise gelten manche bereits als überholt und wurden durch neue Favoriten ersetzt, wenn Sie diese Lektion lesen.
Ziele der Entwicklung
Programmierwerkzeuge haben verschiedene Ziele, die manchmal zueinander in Konflikt stehen. Hier einige Beispiele:
-
Programme erzeugen, die stabil und korrekt sind
-
Programme erzeugen, die schnell laufen
-
Programme erzeugen, die gut skalieren
-
Programme erzeugen, die sich an verschiedene Benutzertypen, Geräte (Laptops, Handys, Tablets etc.) und Umgebungen anpassen lassen
-
Entwicklungsprozess beschleunigen
-
Bereitstellung neu entwickelter Funktionen oder Fehlerkorrekturen beschleunigen
-
Monotone Arbeitsabläufe und die Anzahl von Programmierfehlern reduzieren, die sich in den Testphasen des Programms einschleichen
-
Legacy Code und Geräte unterstützen, die ein Unternehmen nur schwer ersetzen kann
-
Mit anderen bekannten Tools zusammenarbeiten
-
Einfaches Rollback im Falle von Fehlern oder Planänderungen erlauben
Diese und andere Ziele führen zu den in dieser Lektion beschriebenen Prozessen und den daraus resultierenden Entwicklungswerkzeugen.
Allgemeine Entwicklungsprozesse
In diesem Abschnitt werden zwei wichtige Modelle gegenübergestellt: das Wasserfallmodell (das bereits in einer früheren Lektion vorgestellt wurde) und Continuous Integration/Continuous Delivery (CI/CD).
Wasserfallmodell
Obwohl das Wasserfallmodell vor allem in großen Unternehmen immer noch weit verbreitet ist, gilt es vielen Entwicklern heute als überholt. Das Modell war von den 1950er bis in die 1970er Jahre populär. Elemente des Modells sind auch heute noch aktuell, wie beispielsweise die Formalisierung von Anforderungen und das Testen von Programmen kurz vor ihrer Freigabe (Qualitätssicherung).
Schon zu der Zeit, als der Begriff “Wasserfall” für dieses Modell geprägt wurde, war es durch eine Reihe von Problemen in Verruf geraten:
-
Nach Beginn der Entwicklung war es schwierig, die Anforderungen zu ändern.
-
Anforderungen und Entwürfe wurden oft missverstanden, da Texte in einfacher Sprache mehrdeutig sein konnten. Das führte zu Produkten, die nicht den beabsichtigten Anforderungen entsprachen und deren Korrektur Monate in Anspruch nahm.
-
Der Prozess war langsam: Eine neue Version erschien vielleicht nur einmal im Jahr oder sogar noch seltener.
-
Fehler, die durch das Zusammenspiel verschiedener Module entstanden, waren erst spät zu erkennen, was zu weiteren Verzögerungen führte.
-
Der Prozess schuf Barrieren zwischen den verschiedenen Teilen der Organisation, was sich sowohl auf die Qualität des Produkts als auch auf den Zusammenhalt in der Organisation negativ auswirkte.
Grundsätze von Continuous Integration/Continuous Delivery (CI/CD)
Das Wasserfallmodell wurde in den 1970er Jahren zunehmend in Frage gestellt, was zu dem heute führenden (wenn auch nicht allgemein verwendeten) Modell führte: CI/CD. Wichtige Etappen auf dem Weg zu den CI/CD-Methoden waren das 2001 veröffentlichte Agile Manifest, Scrum und DevOps. Das neue Modell basiert auf der intensiven Kommunikation zwischen den Teammitgliedern, der Einbeziehung der Benutzer oder Kunden, der schnellen Umsetzung von Fehlerkorrekturen und neuen Funktionen sowie konsequent fortlaufenden Tests, um die Qualität in einer sich schnell verändernden — manchmal sogar chaotischen — Umgebung zu erhalten.
CI/CD automatisiert in jeder Phase so viele Schritte wie möglich, vom Schreiben des Codes bis zur Bereitstellung des fertigen Programms für die Benutzer. CI/CD erfordert, dass jeder Schritt eindeutig definiert und menschliches Handeln (wie die Installation von Software) durch einen von einem Programm ausführbaren Vorgang ersetzt wird. Somit ist CI/CD Teil einer Bewegung, die unter den Slogans “Alles als Code” und “Infrastruktur als Code” bekannt ist.
Sobald ein Team seine Prozesse in den Code integriert hat, können diese Prozesse ebenso wie der Code korrigiert, aktualisiert und aufgezeichnet werden. Alles, was das Team schreibt — seien es Programme, automatisierte Verfahren oder Dokumentation — sollte in einem Versionskontrollsystem gespeichert werden, das in einer der nächsten Lektionen beschrieben wird.
Werden mehrere Prozeduren automatisiert, können sie nacheinander ablaufen. Daher sprechen Teams oft von einer CI/CD Pipeline: ein erfolgreicher Schritt in der Pipeline löst automatisch einen oder mehrere nachfolgende Prozesse aus. Eine Pipeline muss automatisiert überwacht werden, so dass jeder Fehler auf dem Weg dazu führt, dass die Pipeline angehalten und Teammitglieder über den Fehler informiert werden.
In den folgenden Abschnitten werden CI und CD zwar getrennt beschrieben, doch werden sie häufig miteinander kombiniert, und die Grenzen zwischen ihnen sind fließend.
Continuous Integration (CI)
Continuous Integration beschreibt die schnelle Einarbeitung kleiner Änderungen in den Code. Das zentrale Repository, in dem das Produkt für die Benutzer entsteht, wird als Core Repository (oder Core Repo) bezeichnet. Jeder Programmierer erstellt einen persönlichen Arbeitsbereich (oft Sandbox genannt) auf seinem Computer und zieht die Dateien, die er korrigieren oder aktualisieren muss, aus dem Core Repository.
Der Programmierer kann einen persönlichen Laptop oder Desktop verwenden (als Local Development System), relevante Teile des Core Repository herunterladen und die Änderungen nach lokalen Tests hochladen. Alternativ kann er die Vorteile des Cloud Computing nutzen und auf einem gemeinsam genutzten System arbeiten, das von seiner Organisation als Remote Development System betrieben wird.
Der Programmierer prüft in der Regel mit einem Debugger, einem separaten Programm, das die Arbeit des Programmierers in einer kontrollierten Umgebung ausführt, auf Fehler. Debugger und andere Werkzeugen zum Auffinden von Fehlern werden später noch Thema sein.
Um Fehler so früh wie möglich im Entwicklungsprozess zu erkennen, führt der Programmierer auch Tests am Code durch, bevor er ihn in das Core Repository hochlädt. Diese werden Unit Tests genannt, weil sich jeder Test auf ein kleines Element des Codes konzentriert: Hat beispielsweise eine Funktion einen Zähler erhöht, wie es vorgesehen war?
In der letzten Phase von CI werden die Änderungen des Programmierers in das Core Repository eingepflegt. Dann führen die Tools Integrationstests durch, um sicherzustellen, dass der Programmierer nichts im Gesamtprojekt kaputt gemacht hat.
Unabhängig davon, wie weit die Automatisierung fortgeschritten ist, sollte ein fachkundiges Teammitglied die Integration überwachen, um sicherzustellen, dass die Änderung vom Team gewünscht wird. Automatisierte Tests können feststellen, dass nichts kaputt gegangen ist, und sogar, ob die Änderung den gewünschten Effekt im Programm erzeugt. Aber diese Tests können nicht auf alles prüfen, was das Team für wichtig hält.
Bevor ein Teammitglied mit dem Deployment, also der Bereitstellung, beginnt, sollten die Sicherheit, die Einhaltung von Kodierungsstandards, die ordnungsgemäße Dokumentation und andere Grundsätze überprüft werden — und es überrascht nicht, dass es auch für einige dieser Aufgaben wiederum eigene Tools gibt.
Während der CI finden viele Tests statt, die schnell und zuverlässig ablaufen müssen, um das Tempo der modernen Entwicklung zu unterstützen. Daher sind moderne Tools für die Integration von Code und die Ausführung von Tests automatisiert. Ein Programmierer sollte in der Lage sein, eine ganze Suite von Unit Tests mit einem einzigen Befehl oder einem Klick auf eine Schaltfläche auszuführen.
Üblicherweise wird ein teilweiser oder vollständiger Integrationstest ausgeführt, sobald ein Programmierer Code in das Core Repository hochlädt. Alle durch die Tests festgestellten Fehler werden dem Programmierer schnell gemeldet.
Continuous Delivery (CD)
Wie bereits erläutert, besteht CI aus automatisierten Verfahren, die zu einer neuen Version des Programms im Core Repository führen. Continuous Delivery bezieht sich auf alle nachfolgenden Prozesse, um die neue Version den Benutzern bereitzustellen. Das D in CD wird daher neben “Delivery” oft auch als “Development” oder “Deployment” verstanden.
Die Hauptaufgaben des CD bestehen darin, den Code gründlich zu testen und ihn auf die Computer der Benutzer oder in ein Anwendungsrepository hochzuladen.
In der CD-Phase wird eine Reihe von Tests durchgeführt, um sicherzustellen, dass das Produkt einwandfrei funktioniert. Oft wird ein größerer Satz von Integrationstests mit einer Reihe anderer Tests kombiniert, um die Auswirkungen für die Benutzer, die Leistung und die Sicherheit zu ermitteln. Einige dieser Tests werden in einem späteren Abschnitt beschrieben.
Die Tests sollten auf anderen Systemen als den Produktionssystemen erfolgen, die die Benutzer bedienen. Ein schwerwiegender Fehler könnte nicht nur das Produktionssystem zum Absturz bringen, sondern auch die Benutzerdaten kompromittieren. Außerdem benötigen die Tests Systemzeit und beeinträchtigen die Performance für die Benutzer.
Zum Testen sind daher eigene Umgebungen sinnvoll, die die Produktionsumgebung abbilden, mit ähnlicher Hardware und Software. Solche Zwischensysteme, in denen die Tests vor der Auslieferung erfolgen, werden oft als Staging-Umgebung bezeichnet.
Natürlich ist es nicht praktikabel, die Testumgebung so groß wie die Produktionsumgebung anzulegen, aber die wesentlichen Elemente der Produktionsumgebung (beispielsweise Datenbanken) sollten enthalten sein.
Voraussetzung für die CD-Automatisierung ist die Unterscheidung zwischen Test- und Produktionsumgebung: Alle Code-Installationen und -Prozesse sollten auf die Zielumgebung zugeschnitten sein.
CD bietet das größte Potenzial für eine schnelle Entwicklung, wenn der Dienst auf den eigenen Systemen des Unternehmens läuft. Ein Einzelhandelsunternehmen mit einer interaktiven Webseite hat in der Regel Zugriff auf alle Webserver; es kann diese bei Bedarf mehrmals am Tag aktualisieren und Änderungen schnell zurücknehmen, wenn sich herausstellt, dass sie den Benutzern Probleme bereiten.
Werkzeuge zur Softwareentwicklung
In den folgenden Abschnitten geht es um wichtige Arten von Tools, die Programmierteams auf drei Ebenen nutzen: Code-Generierung, Testen und Deployment.
Compiler
Ein Basiswerkzeug der Programmierung ist der Compiler, der komplexe, sogenannten höhere oder High-Level-Programmiersprachen in Anweisungen für den Computerprozessor übersetzt.
Computer führen Maschinencode aus, der aus Bitfolgen (Einsen und Nullen) besteht, die der Prozessor wiederum in Befehle und Daten umwandelt: Laden eines Wertes aus dem Speicher in ein Register, Addieren der Werte zweier Register usw. Prozessoren unterscheiden sich hinsichtlich des Formats und der Befehlssätze, die ihnen zur Verfügung stehen; darum nutzen sie auch unterschiedlichen Maschinencode. Hersteller versuchen, neue Prozessoren so zu entwickeln, dass sie denselben Maschinencode nutzen, damit Kunden ihre alten Programme auch auf neuen Prozessoren ausführen können. Man spricht in diesem Fall von Prozessorfamilien.
Die nächste Stufe in den Anfangsjahren der Programmierung war die Assemblersprache, die für jede Anweisung menschenlesbare Begriffe wie beispielsweise ADD
(für das Addieren) bereitstellte. Programmierer schrieben in Assemblersprache und übergaben ihren Code an ein Werkzeug, den Assembler, der die Anweisungen in Maschinencode übersetzte. Maschinencode wird übrigens auch als Maschinensprache bezeichnet.
Darauf folgte die Entwicklung höherer Programmiersprachen. Hier lassen sich komplexe Abläufe erstellen, ohne deren Details zu spezifizieren, indem man die gewünschten Ergebnisse bestimmt. Diese Programme benötigen einen Compiler, um den Quellcode in Maschinencode umzuwandeln. Diese Compiler können ziemlich intelligente Transformationen und Optimierungen durchführen.
Für viele Sprachen stehen mehrere Compiler zur Verfügung, beispielsweise für C und Java. In der FOSS Community kommen meist entweder die GNU Compiler Collection (GCC) oder der LLVM Compiler für C und C++ zum Einsatz.
Ursprünglich kompilierte ein Compiler jede Datei des Quellcodes und erzeugte daraus jeweils eine Objektdatei (Object File), um dann ein weiteres Programm, den Linker, aufzurufen, der alle Objektdateien zu einem Programm zusammenfügte. Moderne Compiler können mehrere Dateien gleichzeitig kompilieren, um Optimierungen durchzuführen, die die Grenzen von Dateien überschreiten.
Früher kompilierten Compiler in Assemblersprache, was nützlich sein konnte, da jeder Prozessortyp einen anderen Maschinencode, aber möglicherweise dieselbe Assemblersprache unterstützte. Es gibt mehrere Varianten von Assemblersprachen. Der letzte Schritt beim Kompilieren und Linken bestand darin, Maschinencode zu erzeugen. Wie beim Linken wissen moderne Compiler, wie sie den Code in Maschinencode umsetzen.
In vielen modernen Sprachen gibt es jedoch noch eine weitere Stufe: den Zwischencode, meist als Bytecode bezeichnet. Dieser Code wurde in ein Binärformat kompiliert, das high-level genug ist, um portabel zu sein. Die Sprache Java wird beispielsweise in Bytecode kompiliert, so dass das Programm auf viele verschiedene Prozessortypen geladen werden kann.
Bei der Erstellung von Bytecode aus Quellcode hat der Compiler einen Großteil der Arbeit erledigt. Jeder Computer hostet dann seine eigene Version eines Programms, das als virtuelle Maschine bezeichnet wird und die Umwandlung von Bytecode in eine Reihe von Anweisungen vollzieht, die die virtuelle Maschine ausführen kann. Manchmal wird Bytecode auch in Maschinencode kompiliert. Der Übergang von Bytecode zu einer Reihe von Anweisungen ist für den Benutzer bequemer als der Übergang von einer höheren Programmiersprache zu Maschinencode. Dies war ein großer Vorteil für Java, als es erfunden wurde, denn seine Entwickler wollten, dass es innerhalb eines Plug-ins für eine virtuelle Maschine für Webbrowser läuft, wo der Prozessor und die Betriebsumgebung des Benutzers sehr unterschiedlich sein können.
Die Java-Entwickler bewarben die Vorteile von Bytecode mit dem Slogan “compile once, run anywhere”. Später ergaben sich noch weitere Vorteile von Bytecode: Es ließen sich neue Sprachen mit ganz anderen Ansätzen für Programmierer entwickeln (was die Arbeit einfacher und zu besser wartbarem Code führen sollte), die denselben Bytecode erzeugten, so dass er von den Java Virtual Machines unterstützt wurde. Funktionen aus diesen Sprachen ließen sich leicht in bestehende Java-Programme einfügen.
Schließlich gibt es einige Sprachen wie Python, für die man den Quellcode überhaupt nicht kompilieren muss: Man gibt die Anweisungen einfach in ein Verarbeitungsprogramm ein, das als Interpreter bezeichnet wird und das den Code direkt in Maschinencode umwandelt und ausführt. Interpreter sind langsamer als Compiler, aber einige sind inzwischen so effizient, dass sie keine großen Leistungseinbußen mit sich bringen. Dennoch enthalten einige beliebte Bibliotheken in der Sprache Python Funktionen, auf die Entwickler in der interpretierten Sprache zugreifen können, die aber in der Sprache C programmiert sind, um die Ausführung zu beschleunigen.
Für viele interpretierte Sprachen gibt es auch Compiler, die entweder Byte- oder Maschinencode erzeugen, der dann schneller ausgeführt werden kann als von einem Interpreter.
Es gibt Build-Tools, die dem Programmierer bei der Verwaltung von Dateien und Funktionen helfen. Ein komplexes Programm kann Hunderte von Dateien enthalten, und der Programmierer muss verschiedene Kombinationen von Dateien mit verschiedenen Compiler-Optionen zu verschiedenen Zeitpunkten kompilieren (beispielsweise zur Unterstützung von Debugging). In einem Build-Tool kann man verschiedene Optionen und Kombinationen von Dateien speichern und einfach den gewünschten Build-Typ auswählen. Maven und Gradle sind gängige Build-Tools für Java und verwandte Sprachen.
Werkzeuge zur Code-Generierung
Programmierer starten nicht mit einem leeren Bildschirm. Es gibt inzwischen zahlreiche Tools, die Code auf der Grundlage von Klartextbeschreibungen generieren. Wie bei anderen Formen generativer KI wird dabei häufig kritisiert, dass sie auf der Arbeit anderer Programmierer basiert ohne diese zu honorieren und dass der erzeugte Quellcode weniger robust sei. Abgesehen von ethischen Kontroversen stellen zahlreiche Programmierer aber auch fest, dass die automatische Code-Generierung ihre Produktivität erheblich verbessert hat.
Eine Form der Code-Generierung, die seit vielen Jahren in vielen Programmiersprachen verfügbar ist, ist das Refactoring. Dabei wird ein großes Programm, das sich im Laufe der Zeit zu einem Gewirr von Dateien und Funktionen entwickelt hat, analysiert und so neu arrangiert, dass eine logischere Struktur entsteht, sich die Wartbarkeit verbessert und sich Doppelungen im Quellcode reduzieren.
Manche Programmierer haben die Aufgabe, die Funktionsweise von fremdem Code zu reproduzieren. Sie müssen vielleicht ein neues Programm schreiben, um ein altes Programm zu ersetzen, dessen Quellcode nicht mehr zur Verfügung steht oder ausgemustert werden muss. Oder sie ahmen das Programm eines Konkurrenten nach. Diese Art von Analyse wird als Reverse Engineering bezeichnet. Ein nützliches Werkzeug für diesen Zweck ist ein Disassembler, der Maschinencode in Assembler umwandelt. Es gibt auch Disassembler für Bytecode.
Debugger
Die meisten Programmierer verbringen mehr Zeit mit der Fehlersuche als mit dem Programmieren. Menschen denken einfach nicht vollkommen logisch und vergessen daher oft Details, die der Computer benötigt, um das Programm wunschgemäß auszuführen. Daher ist es wahrscheinlich, dass Code beim ersten Versuch scheitert; ein Debugger hilft, die Fehler aufzudecken.
Ein Debugger unterstützt intensive Analysen, die den Prozess der Fehlersuche um Stunden verkürzen können.
Ein Programmierer kann das Programm so schreiben, dass es an einer Schlüsselstelle (einem Breakpoint) stoppt, beispielsweise zu Beginn einer Funktion oder Schleife. Der Debugger zeigt die Werte von Variablen und sogar Computerregistern an der aktuellen Stelle im Programm an. Der Programmierer kann auch jede Anweisung einzeln ausführen und die Ergebnisse sehen (Single Stepping) oder einen Watchpoint setzen, um nachzuverfolgen, wann und wie sich eine Variable während des Programmablaufs ändert.
Der bekannteste Debugger in der FOSS-Welt, insbesondere für C und C++, ist der GNU Debugger, der mit dem bereits erwähnten GNU Compiler zusammenarbeitet. Auch für andere Sprachen gibt es spezielle Debugger.
Analysetools
Obwohl Fehler durch Debugging in der Regel recht schnell entdeckt werden, empfiehlt es sich, Rechtschreibfehler und andere grundlegende Probleme bereits in einem früheren Stadium des Programmierprozesses zu beseitigen. Es gibt viele ausgeklügelte Analysewerkzeuge, um ein Programm zu prüfen. Statische Analyse untersucht den Programmcode, dynamische Analyse führt ein Programm aus und prüft es während seiner Ausführung auf Probleme.
Eines der frühesten Tools zur statischen Analyse wurde als Linter bezeichnet. Dieser stellt beispielsweise fest, ob der Wert einer Fließkommavariablen einer Ganzzahl zugewiesen wurde. Dies kann zu Problemen führen — oder auch nicht; und es kann vom Compiler erkannt werden — oder auch nicht. Im produktiven Einsatz kann es daher zu falschen Ergebnissen führen.
Heute übernehmen die meisten Compiler die Aufgabe eines Linters. Einige Compiler, wie der für die Sprache Rust, zeichnen sich dadurch aus, dass sie schlecht geschriebenen Code strikt ablehnen.
Viele andere Arten von Analysewerkzeugen laufen unabhängig vom Compiler. Tools zur Sicherheitsanalyse spüren beispielsweise Probleme auf, die ein Programm anfällig für Hacker machen. Ein häufiger Programmierfehler besteht zum Beispiel darin, dass Code eine Funktion aufruft und nicht überprüft, ob diese Funktion einen Fehler zurückgibt.
Integrated Development Environments (IDEs)
Viele Programmierer nutzen Texteditoren, um ihren Code zu schreiben und zu bearbeiten. Texteditoren unterscheiden sich von Textverarbeitungsprogrammen, die zahlreiche Funktionen zur Formatierung (wie kursiv und fett, Aufzählungszeichen und nummerierte Listen usw.) mitbringen. Texteditoren erzeugen blanken Text, den Programmiersprachen erfordern.
Es gibt aber auch hochentwickelte Tools, die die Softwareentwicklung unterstützen. Diese Tools sind auf die verwendete Programmiersprache eingestellt. Gibt man beispielsweise den Namen einer Variablen oder Funktion ein, kann das Tool eine Vervollständigung vorschlagen. Diese Tools können auch bereits während des Programmierens nach Fehlern suchen, den Code einheitlich und ansprechend formatieren, Analysewerkzeuge und Debugger ausführen, den Code kompilieren, Check-Ins in Versionskontrollsysteme durchführen und andere Aufgaben übernehmen. Daher werden sie auch als Integrated Development Environments (IDE), also integrierte Entwicklungsumgebungen, bezeichnet.
Eclipse ist eine beliebte Open-Source-IDE.
Arten von Softwaretests
Das Testing ist ein wichtiger Teil der Softwareentwicklung. Programmierer führen Unit Tests durch, während sie den Code entwickeln. Andere Arten von Tests erfolgen typischerweise, wenn Code zurück ins Core Repository übertragen wird, oder beim Deployment.
Unit Testing
Wir haben gesehen, dass Programmierer unbedingt Fehler finden sollten, bevor sie Code zur Integration in das Core Repository einreichen. Unit Tests sind entscheidend bei der Fehlersuche.
Das Schreiben dieser Tests ist Kunst und Wissenschaft zugleich, und der Umfang des Testcodes kann den des produktiven Codes übersteigen. Es gibt sogar ein Entwicklungsmodell namens Test-Driven Development (TDD), bei dem Programmierer zunächst Tests schreiben, bevor sie den zu testenden Code entwickeln. Dieses Vorgehen soll Lücken beim Testen vermeiden und dazu beitragen, dass der Code seine Aufgabe verlässlicher ausführt.
Es ist wichtig, sowohl das zu testen, was während der Programmausführung schief geht, als auch das, was korrekt läuft. Wenn ein Benutzer oder ein anderer Teil des Programms einer Funktion eine ungültige Eingabe übergibt, ist es wichtig, dass die Funktion das Problem erkennt und eine entsprechende Fehlermeldung ausgibt.
JUnit ist ein beliebtes Open Source Tool zur Durchführung von Unit Tests für Java-Programme.
Integrations-, Regressions- und Smoke Tests
Während sich Unit Tests auf einzelne Aktionen bestimmter Funktionen konzentrieren, sollte das Team auch Tests auf einer höheren Ebene durchführen, um sicherzustellen, dass das Produkt als Ganzes ordnungsgemäß funktioniert. Die Tests imitieren in der Regel das Benutzerverhalten. In einer Anwendung für das Restaurantmanagement könnten die Tests beispielsweise prüfen, ob ein Benutzer den gewünschten Artikel erhält.
Wenn eine Änderung an einem Programm eine Funktion unterbricht, die vorher funktionierte, nennt man den Fehler eine Regression, und die Tests entsprechend Regressionstests.
Wenn ein Team ein Produkt zur Freigabe vorbereitet, ist die erste Testphase oft sehr kurz. Das Team prüft die wichtigsten Abläufe, die ein Programm ausführt, und bricht den Test ab, sobald Fehler auftauchen, um Zeit zu sparen. Diese Art des Testens wird als Smoke Test bezeichnet, denn eine Anwendung, die derart fehlerhaft ist, gleicht einem defekten Gerät, das Feuer fängt.
Einige Produkte erfordern Benutzerinteraktion, beispielsweise Web- und mobile Anwendungen. Der Test läuft automatisch ab und löst den Code aus, der ausgeführt worden wäre, wenn ein Benutzer die Schaltfläche gedrückt hätte. Selenium ist ein beliebtes Tool in dieser Kategorie.
Abnahmetests
Anwendungen mit einer Benutzeroberfläche benötigen eine zusätzliche Testebene, die nicht nur prüft, ob sie auf bestimmte Eingaben richtig reagieren, sondern auch, ob sie auf dem Bildschirm korrekt dargestellt werden. Acceptance Tests (also Akzeptanz- oder Abnahmetests) prüfen die Wirkung des Programms auf den Benutzer. Teams zur Qualitätssicherung führen diese Tests in der Regel durch, nachdem Integrations- und Regressionstests gezeigt haben, dass das Programm formal korrekt ist und die Erwartungen der Benutzer erfüllt.
Sicherheitstests
Sicherheit ist natürlich von entscheidender Bedeutung, denn selbst das kleinste Sicherheitsversagen kann einem Unternehmen großen Schaden zufügen. Wir haben gesehen, dass Analysewerkzeuge die Sicherheit des Codes prüfen. In der Qualitätssicherungsphase können Tests auch feststellen, ob das Programm Schwachstellen aufweist. Die Tests führen das Programm mit bösartigen Eingaben aus und stellen sicher, dass das Programm die Eingaben zurückweist, ohne abzubrechen, unbeabsichtigte Aktionen auszuführen oder sensible Informationen preiszugeben.
Eine Art von Test, die sich in manchen Fällen als wertvoll erwiesen hat, ist das Fuzz Testing. Das Test-Framework generiert zufällige, sinnlose Zeichenketten und übergibt sie als Eingabe dem Programm. Das mag Zeitverschwendung scheinen, deckt aber oft Schwachstellen auf, die bei normalen Tests nicht gefunden werden.
Performancetests
Hat ein Programm andere Tests erfolgreich bestanden, sollten die Teams feststellen, ob es auch schnell genug läuft. Performancetests erfordern eine Umgebung, die derjenigen ähnelt, in der die Benutzer mit dem Programm interagieren werden. Senden Benutzer beispielsweise Anfragen aus großer Entfernung über ein Netzwerk, sollten die Leistungstests auch über ein solches Netzwerk durchgeführt werden.
Einige Programmierbibliotheken werden durch Benchmarks getestet; das sind Standardtests, die dem Vergleich verschiedener Bibliotheken oder verschiedener Versionen derselben Bibliothek dienen.
Deployment-Umgebungen
Ein CI/CD-Tool bietet ausgefeilte Möglichkeiten zum Aufbau von Pipelines. Wie Computerprogramme kann eine Pipeline Tests und Branches enthalten. Durch Branches lassen sich manche Aktionen in der Testumgebung, andere in der Produktionsumgebung durchführen. Das Tool kann beispielsweise dazu dienen, automatisch die richtige Datenbank oder andere Software zu installieren, die für verschiedene Programme benötigt wird.
CD überschneidet sich mit DevOps. In Cloud-Umgebungen erstellen CD- und DevOps-Tools automatisiert die virtuellen Computersysteme mit allen Komponenten, die für die Ausführung der Programme erforderlich sind. Automatisierte Tools (manchmal auch als Werkzeuge zur Orchestrierung bezeichnet) prüfen, ob die virtuellen Systeme ausfallen, und starten sie automatisch neu.
Die Hauptaufgabe eines CD-Werkzeugs besteht darin, jede Pipeline zu starten und schrittweise zu durchlaufen. Das Werkzeug prüft die Ergebnisse jeder Phase der Pipeline und entscheidet, ob es fortgesetzt oder gestoppt werden soll. Das Werkzeug ermöglicht auch die Zeitplanung und protokolliert seine Aktivitäten.
Oft muss eine Aufgabe mit geringfügigen Änderungen wiederholt werden. So unterscheiden Teams beispielsweise zwischen der Bereitstellung in einer Test- und in der Produktionsumgebung. Daher stellen CD-Tools Parameter bereit, denen beim Ausführen der Pipeline unterschiedliche Werte mitgegeben werden.
Manchmal benötigt ein Prozess Befehle, die traditionell am Terminal eingegeben wurden. Daher bietet ein CD-Werkzeug Mechanismen, um beliebige Befehle auszuführen. Normalerweise bietet es Hooks wie preprocess
, um Befehle vor einer Phase der Pipeline auszuführen, und postprocess
, um Befehle nach einer Phase der Pipeline auszuführen.
Jenkins ist wahrscheinlich das beliebteste Open Source Tool für die in diesem Abschnitt beschriebene Orchestrierung.
Geführte Übungen
-
Welche Möglichkeiten gibt es, die Sicherheit eines Programms zu überprüfen?
-
Warum sollten Sie mehrere Unit Tests für eine einzige Programmfunktion schreiben?
Offene Übungen
-
Ihr Team hat eine alte Anwendung übernommen, die langsam läuft und nur schwer um neue Funktionen zu erweitern ist. Welche Möglichkeiten gibt es, die Anwendung zu verbessern, ohne sie zu verwerfen und eine neue Anwendung von Grund auf zu schreiben?
-
In großen Projekten kommt es häufig vor, dass Team A eine Funktion in einem Teil des Systems implementieren möchte, der von Team B gepflegt wird, Team B die Funktion jedoch nicht als vorrangig ansieht. Wie kann Team A die Funktion als Teil des Projekts von Team B kodieren?
Zusammenfassung
In dieser Lektion wurden verschiedene Arten von Tools zur Softwareentwicklung vorgestellt: Compiler und andere Werkzeuge zur Code-Generierung, für Analyse und Tests sowie CI/CD-Tools, die die Integration und Bereitstellung automatisieren. Es gibt zahlreiche Optionen für jede dieser Aufgaben, und ein Tool, das heute beliebt ist, kann in einem Jahr ersetzt werden. Wenn Sie verstehen, wie diese Tools im Entwicklungsprozess zusammenpassen, können Sie herausfinden, was Sie brauchen.
Antworten zu den geführten Übungen
-
Welche Möglichkeiten gibt es, die Sicherheit eines Programms zu überprüfen?
Zunächst können Experten den Code prüfen.
Darüber hinaus gibt es zahlreiche statische und dynamische Analysetools, die schlechte Programmierpraktiken aufspüren, die ein Programm anfällig für Angriffe auf die Sicherheit machen.
Andere Tools senden bösartige Eingaben an laufende Programme und überprüfen deren Reaktionen.
-
Warum sollten Sie mehrere Unit Tests für eine einzige Programmfunktion schreiben?
Eine Programmfunktion muss in der Regel mit vielen verschiedenen Eingabewerten laufen, so dass jede Variante einen eigenen Test verdient. Zum Beispiel könnte die Funktion den Eingabewert Null auf eine spezielle Weise behandeln. Sie müssen auch ungültige Eingaben vorhersehen und Tests schreiben, um zu zeigen, dass die Funktion sie angemessen behandelt.
Antworten zu den offenen Übungen
-
Ihr Team hat eine alte Anwendung übernommen, die langsam läuft und nur schwer um neue Funktionen zu erweitern ist. Welche Möglichkeiten gibt es, die Anwendung zu verbessern, ohne sie zu verwerfen und eine neue Anwendung von Grund auf zu schreiben?
Vergewissern Sie sich zunächst, dass das Projekt unter Versionskontrolle steht, falls das nicht bereits der Fall ist.
Führen Sie nach dem Hinzufügen einer neuen Funktion Regressionstests durch, um festzustellen, wo das Programm versagt, und beauftragen Sie Programmierer, herauszufinden, welche Funktionen dafür verantwortlich sind. Diese Funktionen können selektiv ersetzt werden.
Durch Performancetests lassen sich Funktionen ermitteln, die langsam laufen, so dass Sie Ihre Bemühungen darauf konzentrieren können, den ineffizientesten Code zu korrigieren oder zu ersetzen.
Wenn der Code in einer nicht mehr gebräuchlichen Sprache vorliegt, sollten Sie Funktionen in einer vom Team bevorzugten Sprache hinzufügen. Stellen Sie sicher, dass die Funktionen in der neuen Sprache mit den alten Funktionen integriert werden können.
-
In großen Projekten kommt es häufig vor, dass Team A eine Funktion in einem Teil des Systems implementieren möchte, der von Team B gepflegt wird, Team B die Funktion jedoch nicht als vorrangig ansieht. Wie kann Team A die Funktion als Teil des Projekts von Team B kodieren?
Team B kann Team A erlauben, einen neuen Branch zu erstellen, die Funktion zu kodieren und den Branch an Team B zu übermitteln, damit es ihn in sein Projekt einbindet. Team A darf jedoch nicht alles tun, was es will. Team B sollte Dokumentation und Hilfe für Team A bereitstellen, um die Projektstandards zu befolgen. Ein Mitglied von Team B muss außerdem die Beiträge von Team A überprüfen und alle üblichen Integrationstests durchführen.
Diese Form der Zusammenarbeit wird manchmal als InnerSource bezeichnet, weil sie Open Source ähnelt, aber innerhalb einer einzigen Organisation stattfindet.