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

  1. Testen zeigt Fehler auf

    beweist aber nicht Fehlerfreiheit

    “Testen zeigt die Anwesenheit von Fehlern, nicht die Abwesenheit”

  1. Vollständiges Testen ist nicht möglich

    Nicht alle Kombinationen an Eingaben, Vorbedingungen, Zuständen testbar (praktikabel zu testen)

    Stattdessen priorisiert nach Risiko testen

  1. Frühzeitig Testen

    Je früher Fehler gefunden werden desto einfacher und günstiger zu beheben

  1. Fehlerhäufungen beachten

    Fehler sind nicht gleichmäßig verteilt - tendenziell dort wo Fehler gefunden auch mehr zu erwarten

  1. Veränderung statt Wiederholung

    Wiederholtes ausführen von den selben Testcases wird keine neuen Fehler finden

  1. Testen ist kontextabhängig
  1. 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

  1. Think

    Auswahl von Anforderungen die implementiert werden sollen.

    Spezifikation von Testfällen

  1. Red

    Implementierung und Ausführung von Testfällen (müssen alle fehlschlagen)

  1. Green

    Implementierung der Komponenten und Klassen und Ausführung der Testfälle

    Falls erfolgreich → Refactor

    Falls fehlgeschlagen → bei Green bleiben

  1. 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:

    addwort hinzufügen

    firsterstes Element liefern

    lastletztes Element liefern

    getElement abrufen

    sizeAnzahl der Wörter liefern

  • Ablauf
    1. Interface/Klasse vorbereiten

      Interface erstellen

    1. Testfall Schreiben

      Auswahl der Anforderung, Spezifikation der Testfälle

      Test muss fehlschlagen

    1. Implementierung

      Testfall muss dann erfolgreich durchlaufen

    1. Refactoring
      • Refactoring der Implementierung
      • Tests müssen weiterhin erfolgreich durchlaufen

      TDD Zyklus mit nächster Anforderung wiederholen

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 ( C0C3\small C_0 - C_3 )

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

  1. Zu testende Funktion bestimmen
  1. Argumente (Eingabevektor, Eingabefaktor) bestimmen (A, B, C, ...)

    explizit: Parameter

    implizit: globale Variablen, Systemzustand, Datenbankzustand, ...

  1. Für jedes Argument die gültigen, ungültigen Äquivalenzklassen finden und daraus Werte wählen.
  1. 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 \small \in A1, z.B. 10

    x \small \in A2, z.B. 35

    x \small \in 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)

    Umkreist: häufiger Grenzfall

  • Autoversicherung

    Anforderung

    Risikoaufschlag von Autoversicherung nach: Alter des Lenkers, PS

    • Lenker ≤ 30 Jahre \rarr 10% Risikoaufschlag
    • > 150 PS \rarr 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 Strings s . 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)

Kontrollflussgraph

C0C_0- 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

C1C_1- Zweigüberdeckung / Branch Coverage

Jede Kante min 1x durchlaufen

Zeigt ob nicht ausführbare Programmzweige existieren

C2C_2- Bedingungsüberdeckung / Condition Coverage

Ziel: Überprüfung zusammengesetzter Entscheidungen und Teilentscheidungen

  1. Einfacher Bedingungsüberdeckungstest

    Alle atomaren Teilentscheidungen gleichzeitig true oder false

  1. 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 Teilaussagen A , B , C , D unabhängig voneinander true oder false sein.

      Dadurch 24=16\small 2^4 = 16 Kombinationen bzw Testfälle.

C3C_3- 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

zBDummyCustomerKlasse implementiertICustomeraber 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

http://site.mockito.org

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?

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

  1. Identifikation Stelle im Code manuell oder mit Tool finden
  1. Testabdeckung sicherstellen, dass Stelle abgedeckt ist
  1. 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