Eine allererste Einführung
1.1
Buildsysteme automatisieren repetitive Aufgaben
Sie haben eingebaute Unterstützung oder Plugins für häufige Aufgaben:
Code zur einer ausführbaren Datei kompilieren: sbt compile
Tests ausführen: sbt test
Kompilierte Dateien entfernen: sbt clean
Start eines interaktiven Interpreters: sbt console
Code-Dokumentation als HTML oder PDF ausgeben: sbt doc
Code formatieren, Code-Stil prüfen: sbt scalafmt
, sbt scalastyle
...
Ausführbare Datei (auf Server) publizieren: sbt publish
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
Maven, Gradle, Ant, sbt
sbt
Cargo
...
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?“
„Bauen wir das Produkt korrekt?“
Zwei komplementäre Ansätze die (V&V) unterscheiden:
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:
Programminspektionen
Ziel ist es, Programmfehler, Verstöße gegen Standards und mangelhaften Code zu finden, und nicht, allgemeinere Designfragen zu berücksichtigen; sie 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 haben gezeigt, dass eine Inspektion von etwa 100LoC etwa einen Personentag an Aufwand erfordert.
automatisierte Quellcodeanalyse
Diese umfasst u. a. Kontrollflussanalysen, Datenverwendungs-/flussanalyse, Informationsflussanalyse und Pfadanalyse.
Statische Analysen lenken die Aufmerksamkeit auf Anomalien.
Formale Verifikation
Die formale Verifizierung 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.
Zusammenfassung
Software-Inspektionen zeigen nicht, dass die Software nützlich ist.
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 und was sind 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ändige 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.
Beobachtung
Da die Anzahl der Tests praktisch unendlich ist, müssen wir (für praktische Zwecke) eine Annahme darüber treffen, wo Fehler wahrscheinlich zu finden sind; d. h. die Tests müssen auf einem Fehlermodell beruhen.
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änge der Seiten eines Dreiecks interpretiert
danach ausgibt ob das Dreieck...
gleichschenklig,
schief oder
gleichseitig ist.
Hinweis
Ein gültiges Dreieck muss zwei Bedingungen erfüllen:
Keine Seite darf eine Länge von Null haben
Jede Seite muss kürzer sein als die Summe aller Seiten geteilt durch 2
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 die Länge der beiden anderen Seiten addiert hat). Tests zum Beispiel in Hinblick auf objektorientierte Struktur, Fehlerbehandlung, etc. ... fehlen.
Frage
Wie wissen wir aber wie “gut” unsere Tests sind?
Definition
Testüberdeckung beschreibt, wie viel des Programms durch die Tests geprüft wird.
Hierbei gibt es verschiedene Überdeckungskriterien, die verschiedene Metriken für die Beschreibung der Testgüte nutzen.
Alternativ auch Zeilenüberdeckung oder Line Coverage genannt.
Zu testende Software:
1public double compute (boolean includeTax) {
2double result = 1.3;
3if (includeTax) {
4result *= 1.19;
5}
6return result;
7}
Testfälle:
1compute(false);
2compute(true);
Frage
Wie hoch ist die Anweisungsüberdeckung? Sind beide Testfälle notwendig?
Definition
Zu testende Software:
1public double compute (boolean includeTax) {
2double result = 1.3;
3if (includeTax) {
4result *= 1.19;
5}
6return result;
7}
Kontrollflussgraph:
Frage
Wie hoch ist die Anweisungsüberdeckung? Sind beide Testfälle notwendig?
Definition
Definition
Zu testende Software:
1public double compute (boolean includeTax,
2boolean reducedTax,
3double discount) {
4double result = 1.3;
5if (includeTax) {
6if (reducedTax) {
7result *= 1.07;
8else {
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);
Frage
Wie hoch ist die Pfadüberdeckung?
Achtung!
Die Pfadüberdeckung ist in der Praxis oft nicht realisierbar, da die Anzahl der möglichen Pfade exponentiell mit der Anzahl der Verzweigungen wächst.
In der Praxis wird daher oft die Zweigüberdeckung als Kompromiss genutzt.
(einfache) Bedingungsüberdeckung ((Simple) Condition Coverage)
Eingangs-/Ausgangsüberdeckung (Entry/Exit Coverage)
Schleifenüberdeckung (Loop Coverage)
Zustandsüberdeckung (State Coverage)
(Erfodert ggf. das ein endlicher Automat modelliert wird.)
Datenflussüberdeckung (Data Flow Coverage)
100% Anweisungsabdeckung
100% Zweigabdeckung für kritische Module
Abhängig von der Auswirkung von Systemfehlern. Beispiel: 100% Anweisungsabdeckung bei Verletzungsgefahr von Passagieren.
100% Anweisungs-/Zweig-/Bedingungsabdeckung je nach Sicherheitsanforderung
Abhängig von der Kritikalität der Komponente
Zusammenfassung
Das Erreichen eines Überdeckungsziels erfordert Aufwand, der durch die Kritikalität möglicher Fehler motiviert sein muss.
In vielen Anwendersystemen ist eine hundertprozentige Testabdeckung nicht notwendig. Testabdeckung ist dennoch zu messen.
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;
45
import java.util.Arrays;
67
public class SimpleCalculatorTest {
89
@Test // <= JUnit Test Annotation
10public void testProcess() {
1112
String[] term = new String[] {
13"4", "5", "+", "7", "*"
14};
15long result = SimpleCalculator.process(term);
16assertEquals(Arrays.toString(term), 63, result); // <= JUnit Assertion
17}
18}
1// This method will provide data to any test method
2// 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 be
12// 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.[2] Hier mit Hilfe von ScalaTest.
1import org.specs.runner._
2import org.specs._
34
object SimpleCalculatorSpec extends Specification {
56
"The Simple Calculator" should {
7"return the value 36 for the input {“6”,“6”,“*”}" in {
8SimpleCalculator.process(Array("6","6","*")) must_== 36
9}
10}
11}
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
Entwickeln Sie einen Testplan für das folgende Programm:
1import java.util.Stack;
23
public class RPN {
45
public static void main(String[] args) {
6if (args.length == 0) {
7System.out.println("Usage: java RPN <expr>");
8return;
9}
10// Main logic
11Stack<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());
4748
if (infix.size() > 0) {
49System.out.println("Unused: ");
50infix.forEach(System.out::println);
51}
52}
53}
Warum?
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?
Ziele
Mögliche Bugs frühzeitig erkennen
Systeme quantitativ miteinander vergleichen
Wartbarkeit einschätzen
Refactoring planen
Änderungen bewerten
Evolution des Systems überwachen bzw. verstehen
Verwendung
Als Quality Gates in Buildsystemen
z. B. Zeilen pro Methode < 50
Zur Bewertung von Software
Einfache Metriken
Alle Zeilen zählen so, wie sie im Quellcode stehen
Alle Zeilen ohne Leerzeile 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 Kommentarzeile oder Zeilen, die nur Klammern enthalten, reine Import Statements oder Methodendeklarationen
Zählt nur Anweisungen
Fortgeschrittene Metriken
Anzahl der linear unabhängigen Pfade durch den Code
Anzahl der Abhängigkeiten zwischen Komponenten
...
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 posthum 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)
Wiederverwend- barkeit |
Wartbarkeit |
Korrektheit |
Aufwand |
|
---|---|---|---|---|
leichtgewichtige statische Analysen |
✓ |
✓ |
↓-○ |
|
Semiformale Methoden |
✓ |
↓ |
||
formale Methoden |
✓ |
↑ |
||
Strukturanalysen |
✓ |
✓ |
↓ |
|
Stilüberprüfungen (Linting) |
✓ |
↓ |
Leichtgewichtige statische Analysen können zum Beispiel Code-Clone erkennen (Maintenance), oder Verletzungen von empfohlenen Vorgehensweisen identifizieren und auf gängige Fehlermuster (Bug Patterns) hinweisen.
Achtung!
Um einen Lernerfolg zu erhalten, sollten Sie die Schritte unten selber durchführen und nicht die Aufgabenstellung als solches direkt an den KI Assistenten Ihrer Wahl übergeben!
Bemerkung
Für die folgenden Aufgabe sind keine Kenntnisse von HTML, JavaScript oder CSS notwendig. Wenn es Probleme gibt, dann beschreiben Sie das Problem der KI und lassen sich helfen!
Entwickeln Sie mittels Vibe-Coding eine einfache, aber vollständige Webanwendung, die nur Standard JavaScript, CSS und HTML verwendet. Es sollen keine Frameworks oder externe Bibliotheken für die Ausführung der Anwendung notwendig sein.
Ein Beispiel für eine solche Anwendung wäre TicTacToe oder Stein-Schere-Papier, welches Sie gegen den Computer spielen. Sie können selbstverständlich auch ein anderes kleines Spiel wählen, dass keine komplexe Logik benötigt.
Erstellen Sie mit Hilfe eines eigenem Prompts die Anwendung. Dieser Prompt sollte die Anwendung beschreiben und die zu verwendenden Technologien. Fordern Sie ggf. explizit, dass der Code und das Styling in getrennten Dateien erfolgen soll, falls nur eine Datei (index.html?) zu finden ist.
Lassen Sie sich ein NPM Build-Skript generieren, dass 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 NodeJs installieren, dann steht Ihnen auch NPM zur Verfügung.
Hinweis
KI Codegeneratoren referenzieren üblicherweise 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.
Lassen Sie sich Testfälle generieren und führen Sie diese als Teil des Builds aus.
Hinterfragen Sie den generierten, lauffähigen Code. Lassen Sie sich ggf. von der KI beim Verständnis helfen und stellen Sie kritische Fragen.