Everything related to testing
Definition
Testen
Analytische QS-Methode
Programm mit der Absicht ausgeführt:
– Fehler zu finden, zu verhindern – Informationen und Vertrauen bezüglich Softwarequalität zu bekommen
Nicht mit der Absicht von:
– Lokalisierung, Entfernung (Debugging) – Nachweis von Fehlerfreiheit (Formaler Korrektheitsbeweis)
Verifikation
Test gegen Spezifikation “Bauen wir das Produkt richtig?”
Validierung
Test gegen Nutzer-Anforderungen “Bauen wir das richtige Produkt?”
Fehler
Abweichung von Spezifikation oder Nutzer-Anforderungen
Prinzipien
ISTQB Testprinzipien
- Testen zeigt Fehler auf
beweist aber nicht Fehlerfreiheit
“Testen zeigt die Anwesenheit von Fehlern, nicht die Abwesenheit”
- Vollständiges Testen ist nicht möglich
Nicht alle Kombinationen an Eingaben, Vorbedingungen, Zuständen testbar (praktikabel zu testen)
Stattdessen priorisiert nach Risiko testen
- Frühzeitig Testen
Je früher Fehler gefunden werden desto einfacher und günstiger zu beheben
- Fehlerhäufungen beachten
Fehler sind nicht gleichmäßig verteilt - tendenziell dort wo Fehler gefunden auch mehr zu erwarten
- Veränderung statt Wiederholung
Wiederholtes ausführen von den selben Testcases wird keine neuen Fehler finden
- Testen ist kontextabhängig
- Fehlerlose Systeme sind nicht unbedingt brauchbar
Fehler finden, beheben garantiert nicht, dass Produkt den Anforderungen und Bedürfnissen vom Benutzer entspricht
Teststufen
Teststufen = Tests auf unterschiedlichen Ebenen des Entwicklungsprozesses
bestehen aus einem oder mehr Test-Typen (bestimmte Art von Tests)
Testtypen = Review, Funktionaler-Test, Massentest, Stresstest, Performancetest.
Akzeptanztests / Abnahmetests
Testen ob System in Produktivumgebung den Abnahmekriterien vom Kunden entspricht (Validierung).
wie Systemtest nur von Kunden, Benutzern ausgeführt
Dient nicht dazu Fehler im System zu finden.
Systemtests
Testen vom integrierten System nach Kunden-Anforderungen (Validierung) und nach Spezifikation (Verifikation).
von unabhängigem Testteam ausgeführt.
Testumgebung sollte wie Produktivumgebung sein (umgebungsspezifische Fehler vermeiden).
Integrationtests
Suche nach Fehlern in Interaktion zwischen Komponenten / Schnittstellen.
Zusammenführung (Integration) von mehreren Komponenten.
von Entwicklern erstellt und ausgeführt
Komponententests / Modultests / Unittests
Testet Komponente isoliert vom restlichen System gegen Spezifikation.
Testet Implementierung: Argumente werden übergeben, Ausgabe wird kontrolliert
von Entwicklern erstellt und ausgeführt
Privater Test
Vom Entwickler vor der Freigabe, undokumentiert
Regressionstests
Vor Durchführung von Veränderungen - Wiederholung von Tests
Test-Entwurfsverfahren
Test driven development TDD / testgetriebene Entwicklung
meistens sind es eher Unit-Tests, automatisiert in CI
- Think
Auswahl von Anforderungen die implementiert werden sollen.
Spezifikation von Testfällen
- Red
Implementierung und Ausführung von Testfällen (müssen alle fehlschlagen)
- Green
Implementierung der Komponenten und Klassen und Ausführung der Testfälle
Falls erfolgreich → Refactor
Falls fehlgeschlagen → bei Green bleiben
- Refactor
Optimierung der Implementierung ohne Änderung der Funktionalität.
Ausführung der Testfälle → dürfen nicht fehlschlagen.
Danach Auswahl der nächsten Anforderung (Schritt 1)
TDD Beispiel: Testen von Wörterbuch
Anforderungen
Keine Duplikate
Operationen:
add
wort hinzufügenfirst
erstes Element liefernlast
letztes Element liefernget
Element abrufensize
Anzahl der Wörter liefern
Ablauf
- Interface/Klasse vorbereiten
Interface erstellen
- Testfall Schreiben
Auswahl der Anforderung, Spezifikation der Testfälle
Test muss fehlschlagen
- Implementierung
Testfall muss dann erfolgreich durchlaufen
- Refactoring
- Refactoring der Implementierung
- Tests müssen weiterhin erfolgreich durchlaufen
TDD Zyklus mit nächster Anforderung wiederholen
- Interface/Klasse vorbereiten
Abnahmetestgetriebene Entwicklung
US enthält Abnahmekriterien → bestimmen Tests
Wiederverwendbare Tests für Regressionserstellung.
Behaviour Driven Development BDD / Verhaltensgetriebene Entwicklung
Entwickler testet erwartetes Verhalten
In manchen Frameworks Gherkin-Format (deklarativ) → automatische Testerstellung.
Blackbox Tests
Spezifikation ohne Wissen über innere Struktur
Daten-getrieben (Input-Output getrieben)
Überdeckung von Anforderungen
Partitionierung der Eingabedaten
Whitebox Tests
Spezifikation mit Wissen über innere Struktur
Logik-getrieben
Überdeckung von Knoten, Kanten, Pfaden
Partitionierung von Eingabedaten für Bedingungsüberdeckung ( )
Erfahrungsbasiertes Testen / Exploratives Testen
zusätzlich zu anderen Testentwurfsverfahren
falls Zeit begrenzt für Datenanalyse und genauen US
basiert auf Intuition, Erfahrung des Testers
zB Erfahrung aus anderen Projekten, Stellen die unter Zeitdruck entstanden sind etc.
muss ausführlich dokumentiert werden
Varianten:
– Erfahrungsbasiertes Verfahren – Riskobasiertes Testen – Anforderungsbasiertes Testen – Modellbasiertes Testen – Regessionsvermeidendes Testen
Test-Automatisierung
Continuous Integration and Test (CI&T Environment)
= ein System, dass täglich oder bei Änderungen im SCM automatisch eine Pipeline an tools nützt:
Aktivitäten:
Konfigurationsmanagement
Statische Codeanalyse
Compiling, linking, executable herstellen (building)
Unittests, Code Coverage
Integration
Installation in Production-Environment und Testumgebung
Integrationstests, Systemtests in Testumgebung, Berechnung von Metriken (zB Test converage)
Deployment (Bereitstellung)
Dashboard anzeigen
Continuous Delivery / Deployment CD
Auslieferung / Deployment / Release erfolgt automatisiert nach Qualitätsüberprüfung.
- Continuous Delivery: Automatisches deployment von Artefakten
- Continuous Deployment: Automatische Installation in Produktion
_________________________________________
Testfälle
Tests
bestehen aus einer Menge an Testfällen
Testfälle (= Testsuite)
bestehen aus einer Menge von (V, E, A, R):
V Vorbedingungen (zB Systemzustand, DB-Zustand)
E Eingabewerte
A Aktionen zur Eingabe der Werte
R Erwartete Ergebnisse (Orakel)
Testfall Typen
NF Normalfall soll bei korrekter Implementierung funktionieren
SF Spezialfall Grenze zwischen Normalfall und Fehlerfall
FF Fehlerfall soll bei korrekter Implementierung Fehlermeldung werfen
Auswahl nach:
Minimale Anzahl, Qualität, Risiko der Systemfunktionalität
Testerfolg
Wenn Fehler gefunden wird
Falls keine Fehler gefunden geben sie Information über Produktqualität durch systematische Testüberdeckung
Äquivalenzklassen, Grenzwerte
Äquivalenzklassen-Zerlegung
Zerlegung von Input oder Output Datenmenge in Äquivalenzklassen (Untermengen):
basierend auf erwartetes Verhalten - äquivalente Ergebnisse und Wirkungen
Jede Klasse muss 1x getestet werden - weniger Test-Aufwand
Schritte
- Zu testende Funktion bestimmen
-
Argumente (Eingabevektor, Eingabefaktor) bestimmen
(A, B, C, ...)
explizit: Parameter
implizit: globale Variablen, Systemzustand, Datenbankzustand, ...
- Für jedes Argument die gültigen, ungültigen Äquivalenzklassen finden und daraus Werte wählen.
-
Werte Kombinieren
die Wahl bestimmt die Intensität
Grenzwertanalyse
für bessere Überdeckung
Grenzwerte aus Klassengrenzen wählen, da dort am häufigsten Fehler (zB falscher Vergleichsoperator, oder Index one-off Fehler) auftreten.
Beispiele
Alter
Anforderungen
18 < Alter ≤ 65
Äquivalenzklassen
A1: Alter ≤ 18 (ungültig)
A2: 18 < Alter ≤ 65 (gültig)
A3: Alter > 65 (ungültig)
Testfälle
x A1, z.B. 10
x A2, z.B. 35
x A3, z.B. 70
Grenzwerte
- 18 (ungültig), 19 (gültig), 65 (gültig), 66 (ungültig)
-
Eventuell mehrere Testfälle je Klassengrenze: Alter ≤ 18
17 (gültig), 18 (gültig), 19 (ungültig)
Alter + BMI
Äquvalenzklassen
A1: Alter ≤ 40, B1: BMI ≤ 25, z.B. (30, 20)
A2: Alter > 40, B1: BMI ≤ 25, z.B. (45, 20)
A1: Alter ≤ 40, B2: BMI > 25, z.B. (35, 27)
A2: Alter > 40, B2: BMI > 25, z.B. (50, 25)
Autoversicherung
Anforderung
Risikoaufschlag von Autoversicherung nach: Alter des Lenkers, PS
- Lenker ≤ 30 Jahre 10% Risikoaufschlag
- > 150 PS weitere 10% Risikoaufschlag
- Nur Kunden ≥ 18 Jahre akzeptiert
Äquivalenzklassen
Klasse 1: Alter
A1: Alter 0 – 17 A2: Alter 18 – 29 A3: Alter 30+
Klasse 2: PS
B1: PS ≤ 150 B2: PS > 150
Beispiel: Porto
Anforderung
- Pakete - 5€
- Briefe < 500g - 0,20€
- Briefe mit Ziel im Inland - 0,20€
- Alle anderen Briefe - 0,50€
Äquivalenzklassen
Klasse 1: Typ
- A1: Paket
- A2: Brief
Klasse 2: Gewicht
- B1: Gewicht < 500g
- B2: Gewicht >= 500g
Klasse 3: Zustellungsort
- C1: Inland
- C2: Ausland
Beispiel: String Methode
Beispiel: Eine Methode
string10(String s)
retourniert die ersten 10 Zeichen des Stringss
. Ist der String kürzer als 10, so wird der String unverändert zurückgegeben.Wie viele Äquivalenzklassen werden mindestens benötigt, um die Methode vollständig zu testen?
Lösung: Tests mit
<10 (zB 9), =10, >10 (zB 11) Zeichen,
Leerstring,
null-Parameter.
3 (+2) Äquivalenzklassen sind notwendig.
Test Abdeckung: Kontrollflussorientierte Testverfahren
Code Coverage
Test-Abdeckung von zb Zeilen, Funktionen, Klassen, …
sieht man in coverage reports
Kontrollflussorienterte Verfahren
= Überdeckungstests (für Code Coverage)
relevant auf Unit Test Ebene (Whitebox Tests)
definieren keine Regeln für Erzeugung von Testdaten
strukturorientiert (basiert auf Kontrollfluss des Programmes)
- Anweisungsüberdeckung / Statement Coverage
Jede Anweisung min 1x durchlaufen
Zeigt ob toter Code existiert (falls sie nie durchlaufen werden können)
Zu schwaches Kriterium für sinnvolle Tests
- Zweigüberdeckung / Branch Coverage
Jede Kante min 1x durchlaufen
Zeigt ob nicht ausführbare Programmzweige existieren
- Bedingungsüberdeckung / Condition Coverage
Ziel: Überprüfung zusammengesetzter Entscheidungen und Teilentscheidungen
-
Einfacher Bedingungsüberdeckungstest
Alle atomaren Teilentscheidungen gleichzeitig true oder false
-
Mehrfach-Bedingungsüberdeckungstest
Aller true/false-Kombinationen der atomaren Teilentscheidungen
Beispiel
Entscheidung:
(((a == 0) || (b < 5)) && (x < 6) || (y == 0)))
((A || B) && (C || D))
sind
a
,b
,x
,y
unabhängig können die TeilaussagenA
,B
,C
,D
unabhängig voneinander true oder false sein.Dadurch Kombinationen bzw Testfälle.
- Pfadüberdeckung / Path Coverage
Abdeckung aller möglichen Pfade (Knotenfolgen)
für komplexe Module nicht geeignet (vor allem wenn Schleifen enthalten)
_________________________________________
Unit Tests, JUnit
Unit Testing - Best Practices
-
Testfälle sind isoliert
keine Abhängigkeiten zueinander: Jeder Testfall bereitet eigene Daten vor, räumt sie auf
-
Keine Logik in Tests
keine Abfragen, Schleifen, try/catch
-
Jeder Testfall testet nur einen Aspekt bzw Zustand
Nur ein Testfall per Methode, keine Zufallsdaten sondern Äquivalenzklassen
-
Namenskonventionen einhalten
Testmethoden einheitlich benannt
(Test)<UseCase/Method>_should<ExcpectedOutcome>
Beispiele
@Test public void testAdd_shouldContainAddedWord() {...}@Test public void testAdd_moreThanMax_shouldReplaceFirstWord() {...}@Test public void testRemove_Null_shouldThrowNullPointerException() {...}
- Verständliche Fehlermeldungen bei Assertions
- Klassen und Testklassen liegen im selben Package
JUnit
https://junit.org/junit5/docs/current/user-guide/
Testframework für Unit Tests, Automatische Ausführung von Komponententests
Lifecycle Annotations
class StandardTest { @BeforeAll static void initAll() {...} @AfterAll static void tearDownAll() {...} @BeforeEach void init() {...} @AfterEach void tearDown() {...} @Test void testAdd_shouldDoSomething() {...} }
@BeforeClass
wird einmal bei der Initialisierung der Klasse ausgeführt.@AfterClass
wird einmal vor verlassen der Klasse ausgeführt@Before
wird vor jeder Test-Methode ausgeführt@After
wird nach jeder Test-Methode ausgefüht
Assertions in JUnit
Zusicherungen nach
org.junit.jupiter.api.Assertions;
Bessere Lesbarkeit wenn statisch importiert:
import static org.junit.jupiter.api.Assertions.assertEquals;
assertEquals(1, person.getId(), “Person ID not as expected");org.opentest4j.AssertionFailedError: Person ID not as expected ==> Expected :1 Actual :0
assertAll( () -> assertTrue(...), () -> assertEquals(...) )
Für Exceptions:
assertThrows(Class<T> expectedType, Executable executable)
Parametrisierte Tests
Eigene dependency:
juni-jupiter-params
@ParameterizedTest
@ValueSource(ints = { 1, 2, 3 })
@EnumSource(TimeUnit.class)
@MethodSource("stringProvider")
@CsvFileSource(resources = "/data.csv")
...
@ParameterizedTest @MethodSource ("palindromeData") void isPalindrome_shouldReturnTrue(String string) { assertThat(isPalindrome (string) ).isTrue(); }private static Stream<String> palindromeData () { return Stream.of("radar", "anna", "otto", "Reittier", "Programmieren"); }
AssertJ
Assertions mit AssertJ
https://joel-costigliola.github.io/assertj/
Eigenes Assertion-Framework
Bessere Lesbarkeit, Nachvollziehbarkeit indem Methoden aufeinanderfolgend aufgerufen werden können.
JUnit: assertEquals(customer.getId(), person.getId()); AssertJ: assertThat(person.getId()).isEqual(customer.getId());
Test Doubles
Test doubles
= Generischer Begriff für Austausch von echten Komponenten durch eine alternative Implementierung für Testzwecke.
Komponenten mit einfacheren “doubles” ersetzen zum Testen.
Komponenten nicht leicht vom restlichen System isolierbar für Komponententests:
Abhängigkeiten: sie interagieren miteinadner, rufen andere Schnittstellen auf wie zB die Datenbank
Nicht Determinismus: sie nutzen nicht deterministische Werte wie zB Systemzeit
Vorteile:
- Isolation - Keine Abhängigkeiten - geringere Komplexität - Reduktion der Ausführungszeit.
Dummy Object
Nicht zum Testen
Nicht zum Verwenden, nicht ausführbar
Quasi “leeres” Argument - Platzhalter ohne Funktionalität
zBDummyCustomer
Klasse implementiertICustomer
aber wirftNotImplementedException
Fake Object
Nicht zum Testen
Zum Verwenden, ausführbare Implementierung
Wenn reale Implementierung zu langsam oder nicht verfügbar
zB simulierte Datenquelle, In-Memory Datenbank - keine Echtdaten
Stub
Zum Testen
Zum Verwenden, ausführbare Implementierung
Liefert vordefinierte Werte, Exceptions, Pfad wird vorbestimmt.
Erlaubt Testen von Pfaden die von außen nicht beeinflusst und durchlaufen werden können.
Beispiel: Asynchrone Abfrage an einem Server
Original
Stub
Mock
Zum Testen
Zum Verwenden, ausführbare Implementierung
Gleich wie Stub, aber die Parameter die an Mock gegeben werden, werden auch im Test überprüft.
Mock erkennt wenn unerwartete Werte erhalten werden - Assertion und Exception.
Fortsetzung Beispiel: Asynchrone Abfrage an einem Server
Mock - Version 1: Ohne Assertions
Mock - Version 2: Mit Assertions
Mockito
https://javadoc.io/doc/org.mockito/mockitocore/latest/org/mockito/Mockito.html
Erzeugung
Erzeugen eines Mocks
mock(Class<T> clazz)
@Mock
Verhalten definieren
Spezifikation des Verhaltens
when(T methodCall)
Welche Methode soll gemockt werden?thenReturn(T value)
Was soll vom Mock returned werden?thenThrow(Throwable t)
Welche Exception soll der Mock werfen?
Überprüfung der Argumente und Aufrufe
Überprüfung des Argumentes in
when()
https://javadoc.io/doc/org.mockito/mockito-core/latest/org/mockito/Mockito.html#argument_matchers
mit “Argument Matcher”
any(…)
anyInt(…)
argThat(somestring -> somestring.length() > 10)
Überprüfung der Aufrufe
https://javadoc.io/doc/org.mockito/mockito-core/latest/org/mockito/Mockito.html#4
Überprüft wie oft Funktion aufgerufen wurde
verify(…)
never() / times() / atLeastOnce() …
Spy
Proxy Objekt - Einzelne Methoden durch andere Implementierung ersetzt.
_________________________________________
Statische Code Analyse
Toolgestützte Code Analysen um Coding-Conventions zu überprüfen
Coding Conventions / Code Guidelines
Einheitlicher Stil im Projekt für jede Programmiersprache
bessere Lesbarkeit, Verständlichkeit
Eigene Regeln für Dokumentation, Einrückung (space/tab), Max Zeilenlänge, Import Reihenfolge, Methoden/Feld Reihenfolg, Modifier/Sichtbarkeit, Veränderbarkeit, etc.
Statische Code Analyse / Fehlermuster
Tools: FindBugs/SpotBugs, SonarLint, SonarQube
Verbessert Code Qualität, Wartbarkeit, reduziert Fehler
Häufige Fehlermuster im Code finden
Code guidelines einhalten.
Regeln eingeteilt in:
Bug Potenzielle Fehler
Vulnerability Sicherheitslücken
Code Smell Unschöner/unwartbarer Code
Commit messages / commit conventions
Gute Messages:
Warum wurde diese Änderung gemacht? Was wurde geändert?
nur eine logische Änderung pro Commit
Idempotent (in sich geschlossen)
Refactoring
Refactoring
“Technische Schuld”: Design vernachlässigt um kurz effizienter zu sein
deshalb “verwahrlost” Design mit der Zeit
Bad Smells zeigen das Refactoring notwendig ist für bessere:
Kapselung, Struktur Lesbarkeit, Verständlichkeit, interne Architektur/Design
Wir wenden Patterns an um diese zu entfernen.
Dadruch:
Leichtere Einarbeitung von neuen Entwicklern
Bessere Wartbarkeit, Anpassbarkeit, Erweiterbarkeit
Geringerer Aufwand beim Testen, Leichtere Identifikation von Fehlern
Refactoring patterns
Schritte
Funktionalität, Performance, Fehler bleiben gleich
- Identifikation Stelle im Code manuell oder mit Tool finden
- Testabdeckung sicherstellen, dass Stelle abgedeckt ist
- Durchführung patterns anwenden, umbauen, testen (Funktionalität bleibt gleich)
_________________________________________
Anforderungen
Klassifikation nach FURPS
Zur Laufzeit messbar:
Functional (Funktional) Features, Fähigkeiten, Sicherheitsvorrichtungen
Usability (Benutzerfreundlich) Human factors, Nützlichkeit, Dokumentation
Reliability (Zuverlässig) Uptime, Verlässlichkeit, Wiederherstellbarkeit
Performance Antwortzeiten, Durchsatz, Genauigkeit
Nicht zur Laufzeit messbar:
Supportability (Wartbarkeit) Modifizierbarkeit, Wartung, Konfiguration
Funktionale Anforderungen
Beschreiben was das System tut, um Mehrwert für die Stakeholder zu liefern
Bilden ein Anwendungs-Szenario: Meistens in der Form “System kann Funktion X ausführen”
Beispiele
– Das System kann gespeicherte Rechnungen als ausdruckbare Datei exportieren.
– Der Administrator kann Bestellungen stornieren
Nichtfunktionale Anforderungen (”Qualitätsanforderungen”)
Charakteristika, nicht-technische Beschreibung, Eigenschaften
Beispiele
– Wenn ein Fehler eintritt, informiert das System den Benutzer und arbeitet in einem verschlechterten(degradierten) Zustand weiter.
– Falls das System die Anzahl der Bestellungen nicht verarbeiten kann, trennt es einige bestehende Verbindungen zu Clients. Die Administratorin wird informiert und das System arbeitet in einem verschlechterten(degradierten) Zustand weiter.
Constraints
Einschränkungen des Systems durch Frameworks bzw. Architekturentscheidungen