Eine allererste Einführung
1.0
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.
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.
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.
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.
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);
Zu testende Software:
1public double compute (boolean includeTax) {
2double result = 1.3;
3if (includeTax) {
4result *= 1.19;
5}
6return result;
7}
Kontrollflussgraph:
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);
(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
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.[1] 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 |
✓ |
↓ |
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.