Eine allererste Einführung.
[HTML] https://delors.github.io/se-build-test-measure/folien.de.md.html
[PDF] https://delors.github.io/se-build-test-measure/folien.de.md.html.pdf
https://quizzy.michael-eichberg-at-dhbw-de.users.dhbw.site/practice.html
Bei der Erstellung der Unterlagen wurden KI Assistenten (insbesondere Claude aber ggf. auch ChatGPT, Ollama mit Gemma/LLama/Qwen oder OpenCode mit Kimi/Qwen/Deepseek...) unterstützend eingesetzt. Dies erfolgte insbesondere zur Unterstützung bei der Generierung von Grafiken (d. h. SVG Dateien), oder um sich Übersichtstabellen generieren zu lassen. Weiterhin wurde KI zur allgemeinen Qualitätssicherung eingesetzt. Inhalte, die ggf. von der KI vorgeschlagen wurden, wurden im Falle der Übernahme explizit validiert und angepasst.
Buildsysteme automatisieren repetitive Aufgaben
Sie haben eingebaute Unterstützung oder Plugins für häufige Aufgaben:
Aufgabe | Befehl (am Beispiel von |
|---|---|
Code zu einer ausführbaren Datei kompilieren |
|
Tests ausführen |
|
Start eines interaktiven Interpreters |
|
Ausführen des Programms |
|
Code formatieren, Code-Stil prüfen |
|
Code-Dokumentation erzeugen (meist HTML) |
|
Kompilierte Dateien entfernen |
|
Ausführbare Datei (auf Server) publizieren |
|
... |
Buildsysteme werden mit Buildskripten konfiguriert
Buildsysteme verarbeiten meist nur geänderte und davon abhängige Dateien
(Inkrementalität)
Heutzutage bringen fast alle Programmiersprachen eigene Buildsysteme mit oder es gibt etablierte Buildsysteme.
Make, CMake
mill, Maven, Gradle, Ant, sbt
sbt
Cargo
npm, pnpm
...
Kompilieren des Codes nach einem frischen Update (bei Fehler Abbruch)
Testen des Codes:
Unit-Tests (bei Fehler Abbruch)
Integrationstests (bei Fehler Abbruch)
Systemtests/Abnahmetests (bei Fehler Abbruch)
Packaging des Projekts (bei Fehler Abbruch)
Deployment (typischerweise in einer Testumgebung)
Insbesondere bei größeren Projekten kommen häufig noch viele weitere Schritte hinzu:
Code-Dokumentation erzeugen und veröffentlichen
verschiedene statische Analysen durchführen, um Fehler zu finden
zahlreiche Skripte, um zum Beispiel Datenbanken zu aktualisieren, Docker-Container zu bauen und zu starten...
...
„Bauen wir das richtige Produkt?“
D. h. bauen wir das Produkt, das die Probleme löst?
„Bauen wir das Produkt korrekt?“
D. h. verhält sich das Produkt gem. der Spezifikation?
Man unterscheidet zwei komplementäre Ansätze für V&V:
Software-Inspektionen oder Peer-Reviews (statische Technik)
Software-Inspektionen können in allen Phasen des Prozesses durchgeführt werden.
Software-Tests (dynamische Technik)
Ausgewählte Ansätze:
Ziel ist es, Programmfehler, Verstöße gegen Standards und mangelhaften Code zu finden, und nicht, allgemeinere Designfragen zu behandeln.
Programminspektionen werden in der Regel von einem Team durchgeführt, dessen Mitglieder den Code systematisch analysieren. Eine Inspektion wird in der Regel anhand von Checklisten durchgeführt.
Studien aus dem Vor-KI-Zeitalter haben gezeigt, dass eine Inspektion von etwa 100 Lines of Code (LOC) einen Personentag an Aufwand erfordert.
Diese umfasst u. a. Kontrollflussanalysen, Datenverwendungs-/flussanalyse, Informationsflussanalyse und Pfadanalyse.
Statische Analysen lenken die Aufmerksamkeit auf Anomalien.
Die formale Verifikation kann das Nichtvorhandensein bestimmter Fehler garantieren. So kann z. B. garantiert werden, dass ein Programm keine Deadlocks, Race Conditions oder Pufferüberläufe enthält. Die Garantie gilt jedoch nur bezogen auf die Spezifikation und die getroffenen Annahmen.
Test der Funktionalität
Test auf Robustheit
Test der Effizienz/Performance
Test auf Wartbarkeit
Test auf Nutzbarkeit
Black-Box-Testen
Wir wollen die Korrektheit zeigen.
Testdaten werden durch die Untersuchung der Domäne gewonnen. Was sind gültige bzw. ungültige Eingabewerte in der Domäne?
Der Test kann (und sollte!) ohne Betrachtung der konkreten Implementierung entwickelt werden.
White-Box-Testen
Wie auch beim Black-Box-Test wollen wir die Korrektheit zeigen.
Testdaten werden durch die Inspektion des Programms gewonnen.
Das heißt im Umkehrschluss, dass wir den Quellcode des Programms benötigen.
Umfasst eine relativ kleine ausführbare Datei; z. B. ein einzelnes Objekt.
Komplettes (Teil-)System. Schnittstellen zwischen den Einheiten werden getestet, um zu zeigen, dass die Einheiten gemeinsam funktionsfähig sind.
Eine vollständig integrierte Anwendung. Kategorisiert nach der Art der Konformität, die festgestellt werden soll: funktional, Leistung, Stress oder Belastung
Tests (durch den Kunden), um zu zeigen, dass das System die Anforderungen erfüllt.
Testpläne beschreiben, wie die Software getestet wird.
Es gibt zwei allgemeine Fehlermodelle und entsprechende Prüfstrategien:
konformitätsorientiertes Testen
fehlerorientiertes Testen
Entwickeln Sie einen Testplan für ein Programm, das ...
drei ganzzahlige Werte liest,
diese dann als die Längen der Seiten eines Dreiecks interpretiert
und danach ausgibt, ob das Dreieck...
gleichschenklig,
schief oder
gleichseitig ist.
Beschreibung | A | B | C | Erwartetes Ergebnis |
|---|---|---|---|---|
Gültiges schiefes Dreieck | 5 | 3 | 4 | Schief |
Gültiges gleichschenkliges Dreieck | 3 | 3 | 4 | Gleichschenklig |
Gültiges gleichseitiges Dreieck | 3 | 3 | 3 | Gleichseitig |
Erste Permutation von zwei gleichen Seiten | 50 | 50 | 25 | Gleichschenklig |
(Permutationen des vorherigen Testfalls) | ... | ... | ... | Gleichschenklig |
Eine Seite ist null | 1000 | 1000 | 0 | Ungültig |
Erste Permutation von zwei gleichen Seiten | 10 | 5 | 5 | Ungültig |
Zweite Permutation von zwei gleichen Seiten | 5 | 10 | 5 | Ungültig |
Dritte Permutation von zwei gleichen Seiten | 5 | 5 | 10 | Ungültig |
Drei Seiten größer als null, Summe der zwei kleinsten ist kleiner als die größte | 8 | 5 | 2 | Ungültig |
Drei Seiten mit maximaler Länge | MAX | MAX | MAX | Gleichseitig |
Zwei Seiten mit maximaler Länge | MAX | MAX | 1 | Gleichschenklig |
Eine Seite mit maximaler Länge | 1 | 1 | MAX | Ungültig |
Die Testfälle sind noch nicht vollständig (zum Beispiel wenn A, B und C alle 0 sind; oder wenn eine Seite gleich der Summe der Längen der anderen Seiten ist oder diese sogar überschreitet). Tests zum Beispiel in Hinblick auf objektorientierte Struktur, Fehlerbehandlung etc. fehlen.
Hierbei gibt es verschiedene Überdeckungskriterien, die verschiedene Metriken für die Beschreibung der Testgüte nutzen.
Zu testende Software:
1public double compute(boolean includeFactor) {2double result = 1.3;3if (includeFactor) {4result *= 1.19;5}6return result;7}
Testfälle:
1compute(false);2compute(true);
Zu testende Software:
1public double compute(boolean includeFactor) {2double result = 1.3;3if (includeFactor) {4result *= 1.19;5}6return result;7}
Kontrollflussgraph:
Zu testende Software:
1public double compute(boolean includeFactor,2boolean reducedBaseline,3double discount) {4double result = 1.3;5if (includeFactor) {6if (reducedBaseline) {7result *= 1.07;8} else {9result *= 1.19;10} }11if (discount > 0.0) {12result *= (1.0 - discount);13}14return result;15}
Testfälle:
1compute(false, false, 0.0);2compute(true, false, 0.0);3compute(true, true, 0.0);4compute(false, false, 0.1);5compute(true, false, 0.1);6compute(true, true, 0.1);
(einfache) Bedingungsüberdeckung ((Simple) Condition Coverage)
Eingangs-/Ausgangsüberdeckung (Entry/Exit Coverage)
Schleifenüberdeckung (Loop Coverage)
Zustandsüberdeckung (State Coverage)
(Erfordert ggf., dass ein endlicher Automat modelliert wird.)
Datenflussüberdeckung (Data Flow Coverage)
100 % Anweisungsüberdeckung
100 % Zweigüberdeckung für kritische Module
Abhängig von der Auswirkung von Systemfehlern. Beispiel: 100 % Anweisungsüberdeckung bei Verletzungsgefahr von Passagieren.
100 % Anweisungs-/Zweig-/Bedingungsüberdeckung je nach Sicherheitsanforderung
Abhängig von der Kritikalität der Komponente
Tests können nur das Vorhandensein von Fehlern zeigen, nicht deren Abwesenheit.
—E. Dijkstra
1import org.junit.Test;2import static org.junit.Assert.assertEquals;3import static org.junit.Assert.fail;45import java.util.Arrays;67public class SimpleCalculatorTest {89@Test // <= JUnit Test Annotation10public void testProcess() {1112String[] term = new String[] {13"4", "5", "+", "7", "*"14};15long result = SimpleCalculator.process(term);16assertEquals(Arrays.toString(term), 63, result); // <= JUnit Assertion17}18}
1// This method will provide data to any test method2// that declares that its Data Provider is named "provider1".3@DataProvider(name = "provider1")4public Object[][] createData1() {5return new Object[][] {6{ "Cedric", new Integer(36) },7{ "Anne", new Integer(37) }8};9}1011// This test method declares that its data should be12// supplied by the Data Provider named "provider1".13@Test(dataProvider = "provider1")14public void verifyData1(String n1, Integer n2) {15System.out.println(n1 + " " + n2);16}
Das Ziel ist, dass die Entwickler die Verhaltensabsichten des Systems, das sie entwickeln, definieren.[1] Hier mit Hilfe von ScalaTest.
1import org.scalatest._2import flatspec._3import matchers._45class SimpleCalculatorSpec extends AnyFlatSpec with should.Matchers {67"The Simple Calculator" should8"return the value 36 for the input {“6”,“6”,“*”}" in {910SimpleCalculator.process(Array("6","6","*")) should be (36)11}12}
A Tester’s Courage
The Director of a software company proudly announced that a flight software developed by the company was installed in an airplane and the airline was offering free first flights to the members of the company.
“Who are interested?” the Director asked.
Nobody came forward. Finally, one person volunteered.
The brave Software Tester stated, “I will do it. I know that the airplane will not be able to take off.”
—Unknown Author @ http://www.softwaretestingfundamentals.com
Testplan
Entwickeln Sie einen Testplan für das folgende Programm:
1import java.util.Stack;23public class RPN {45public static void main(String[] args) {6if (args.length == 0) {7System.out.println("Usage: java RPN <expr>");8return;9}10// Main logic11Stack<String> infix = new Stack<>();12Stack<Double> ops = new Stack<>();13for (String arg : args) {14switch (arg) {15case "+":16case "*":17case "/":18if (ops.size() < 2) {19System.out.println("Error: / requires two operands");20return;21}22var right = ops.pop();23var left = ops.pop();24var rightTerm = infix.pop();25var leftTerm = infix.pop();26switch (arg) {27case "+" -> ops.push(left + right);28case "*" -> ops.push(left * right);29case "/" -> ops.push(left / right);30}31infix.push("(" + leftTerm + " " + arg + " " + rightTerm + ")");32break;33case "sqrt":34if (ops.size() < 1) {35System.out.println("Error: sqrt requires one operand");36return;37}38ops.push(Math.sqrt(ops.pop()));39infix.push("sqrt(" + infix.pop() + ")");40break;41default:42infix.push(arg);43ops.push(Double.parseDouble(arg));44}45}46System.out.println(infix.pop() + " = " + ops.pop());4748if (infix.size() > 0) {49System.out.println("Unused: ");50infix.forEach(System.out::println);51}52}53}
Neue Features oder Code-Qualität erhöhen?
Kann ich Komponente C austauschen? Bzw. wie viel Aufwand ist das?
Haben die letzten Änderungen das System negativ beeinflusst?
Welche Systemteile sind besonders sicherheitskritisch?
Mögliche Bugs frühzeitig erkennen
Systeme quantitativ miteinander vergleichen
Wartbarkeit einschätzen
Refactoring planen
Änderungen bewerten
Evolution des Systems überwachen bzw. verstehen
Als Quality Gates in Buildsystemen
z. B. Zeilen pro Methode < 50
Zur Bewertung von Software
Alle Zeilen zählen so, wie sie im Quellcode stehen
Alle Zeilen ohne Leerzeilen oder Kommentare
Es zählen nur Zeilen mit Kommentaren; dies können ganze Zeilen sein oder einfach nur Inline-Kommentare; auskommentierter Code zählt ggf. auch
Codezeilen ohne Kommentarzeilen oder Zeilen, die nur Klammern enthalten, reine Import-Statements oder Methodendeklarationen
Zählt nur Anweisungen
Anzahl der linear unabhängigen Pfade durch den Code
Anzahl der Abhängigkeiten zwischen Komponenten
Misst die relative Schwierigkeit Code zu verstehen und somit – zumindest indirekt – den Wartungs- und Weiterentwicklungsaufwand.
Entwickelt als Antwort auf die Schwächen von McCabe (definiert 1976) im Hinblick auf moderne Sprachkonstrukte, wie zum Beispiel exception handling mittels try-catch oder lambdas.
...
Warnung
Umfangreiche Forschung hat gezeigt, dass es keine fixen Werte gibt, die für alle Projekte gelten. Es hat sich weiterhin gezeigt, dass es keine einzige Metrik gibt, die alleine zur Bewertung der Qualität eines Systems ausreicht. Welche Metriken die Qualität des Systems am besten beschreiben, lässt sich immer nur ex post beantworten.
Metriken sind immer kontextabhängig zu bewerten.
Metriken eignen sich insbesondere, um Veränderungen zu bewerten und um die Entwicklung von Software zu überwachen.
Konstruktiv vs. Analytisch
Programmiersprachen (mit Typsystemen)
Softwareentwicklungsprozesse
Domain Specific Languages (DSLs)
Wiederverwendbarkeit | Wartbarkeit | Korrektheit | Aufwand | |
|---|---|---|---|---|
Leichtgewichtige statische Analysen | ✓ | ✓ | ↓-○ | |
Semiformale Methoden | ✓ | ↓ | ||
Formale Methoden | ✓ | ↑ | ||
Strukturanalysen | ✓ | ✓ | ↓ | |
Stilüberprüfungen (Linting) | ✓ | ↓ | ||
KI-basierte Reviews | ○ | ○ | ○ | ○ |
Leichtgewichtige statische Analysen können zum Beispiel Code-Klone erkennen (Wartbarkeit) oder Verletzungen von empfohlenen Vorgehensweisen identifizieren und auf gängige Fehlermuster (Bug Patterns) hinweisen.
KI-basierte Reviews können helfen, Fehler und Probleme in allen Kategorien zu finden, es gibt jedoch keine Garantie, dass alle Probleme in einer bestimmten Kategorie gefunden werden. Weiterhin liegt die Bewertung von weichen Aspekten wie Wiederverwendbarkeit und Wartbarkeit häufig im Auge des Betrachters. Diesbezüglich kann es sehr schwierig sein, mittels Prompts das Ziel hinreichend präzise zu beschreiben.
Achtung!
Um einen Lernerfolg zu erhalten, sollten Sie die Schritte unten selber durchführen und nicht die ganze Aufgabenstellung als solche direkt an den KI-Assistenten Ihrer Wahl übergeben!
Bemerkung
Für die folgende Aufgabe sind Kenntnisse von HTML, JavaScript oder CSS hilfreich, aber nicht notwendig. Wenn es Probleme gibt, dann beschreiben Sie das Problem der KI und lassen Sie sich helfen!
Hinweis
Es kann sehr hilfreich sein, den Code häufig in ein Repository einzuchecken, da Sie bei Problemen somit immer auf einen vorherigen Stand zurückkehren können.
Eine eigene Webanwendung bauen mittels Vibe-Coding
Entwickeln Sie mittels Vibe-Coding eine einfache, aber vollständige Webanwendung (z. B. Tic-Tac-Toe oder 4-Gewinnt gegen den Computer), die nur Standard-JavaScript, CSS und HTML verwendet. Es sollen keine Frameworks oder externen Bibliotheken für die Ausführung der Anwendung notwendig sein.
Ein Beispiel für eine solche Anwendung wäre Tic-Tac-Toe oder Stein-Schere-Papier, welches Sie gegen den Computer spielen. Sie können selbstverständlich auch ein anderes kleines Spiel wählen, das keine komplexe Logik benötigt.
Erstellen Sie mit Hilfe eines eigenen Prompts die Anwendung. Dieser Prompt sollte die Anwendung und die zu verwendenden Technologien beschreiben. Fordern Sie ggf. explizit, dass Code und Styling in getrennten Dateien abgelegt werden sollen, falls nur eine Datei (index.html?) zu finden ist.
Lassen Sie sich ein npm-Build-Skript generieren, das die nachfolgenden Schritte durchführt. D. h. es soll eine package.json-Datei erzeugt werden, die alle notwendigen Abhängigkeiten und Skripte definiert. Es muss insbesondere möglich sein, npm run build und npm run clean auszuführen.
Formatierung aller Dateien (.js, .html, .css)
Linting aller Dateien (.js, .html, .css)
Validieren Sie, dass Ihr Build-Skript auch die neuesten Versionen der eingebundenen Tools verwendet. Dies erfordert in der Regel eine Google-Suche nach den aktuellen Versionen.
Wenn Sie Node.js installieren, dann steht Ihnen auch npm zur Verfügung.
Hinweis
KI-Codegeneratoren referenzieren häufig veraltete Tools und Bibliotheken. Ggf. müssen Sie danach auch die Konfigurationsskripte anpassen lassen.
Führen Sie Ihr Build-Skript aus und beheben Sie alle gefundenen Probleme.
Finden Sie heraus, wie Sie Ihre Anwendung für den Einsatz optimieren könn(t)en, und erweitern Sie das Build-Skript entsprechend.
Ein Ziel könnte die Minimierung des Ressourcenverbrauchs sein.
Lassen Sie sich Testfälle generieren und führen Sie diese als Teil des Builds aus.
Beheben Sie ggf. gefundene Probleme.
Hinterfragen Sie den generierten, lauffähigen Code. Lassen Sie sich ggf. von der KI beim Verständnis helfen und stellen Sie kritische Fragen.
[Bonus] Lassen Sie sich die Testüberdeckung (Coverage) des JavaScript-Codes ermitteln und versuchen Sie eine möglichst hohe Überdeckung zu erreichen.
Ann Campbell; {Cognitive Complexity} a new way of measuring understandability; SonarSource, 2023 https://www.sonarsource.com/docs/CognitiveComplexity.pdf