zusammenfassungen/Writerside/topics/04/Software Engineering/01_ImplementingForMaintainability.md
David Schirrmeister 87cdebc8a3 update
2025-04-09 10:07:05 +02:00

12 KiB

Implementing for Maintainability

Motivation and Introduction

  • Unit und Integration Testing ist ein bekannter Ansatz um Qualität sicherzustellen
  • Gut-getestete Anwendungen:
    • 1 line of code = 1-3 lines of test code
    • kann auf bis zu 1:10 hochgehen
  • Produkt- und Testcode werden parallel geschrieben
  • guten Code schreiben ist schwer

Black-/White-Box Testing

image_555.png

Black-Box-Testing

Nur das Interface ist bekannt, nicht der Inhalt

  • Methode vom Software-Testing, das die Funktion analysiert, ohne den Mechanismus zu kennen
  • Normalerweise rund um die Spezifikationen und Anforderungen
    • Was soll die Anwendung tun, nicht wie tut sie es

White-Box-Testing

Das Interface und alle Mechanismen sind bekannt

  • Testmethodik, die verifiziert, wie die Anwendung arbeitet
  • Tests sind am Sourcecode ausgerichtet, nicht an den Anforderungen

Pros / Cons

  • White-Box-Testing ist systematischer und anspruchsvoller
    • Analyse des Codes kann zu Fehlerentdeckungen führen, die zuvor übersehen wurden
    • Testergebnisse sind oft spröde
      • Sind sehr verknüpft mit der Implementierung des Codes
      • Solche Tests produzieren viele false-positives und sind nicht gut für die Metrik der Resistenz gegen Refactoring
    • Können häufig nicht rückgeschlossen werden zu einem Verhalten, dass wichtig ist für eine Business-Person
      • Starkes Zeichen, dass die Tests nicht viel Wert hinzufügen
  • Black-Box-Testing hat gegensätzliche Vor-/Nachteile

Black-/White-Box-Testing sind Konzepte, die auf verschiedene Test-Typen angewendet werden können

Testing Quadrants Matrix

image_556.png

Quadrant 1: Technologie-fokussierte Tests, die das Development leiten

  • Developer Tests:

    • Unit tests
      • Verifizieren Funktionalität eines kleinen Subsets des Systems
      • Unit Tests sind die essenzielle Basis einer guten Test-Suite
        • Verglichen mit anderen, sind sie einfach zu erstellen und warten
        • Viele Unit-Tests :)
    • Component-/Integration Tests:
      • Verifizieren Verhalten eines größeren Teils
  • Tests sind nicht für den Kunden

Goals of Testing during Implementation

Aktiviere nachhaltiges Wachstum des Software-Projekts

  • image_579.png
  • Nachhaltigkeit ist wichtig
    • Projektwachstum ist am Anfang einfach
    • Das Wachstum zu halten ist schwer
  • Tests können das nachhaltige Wachstum fördern
    • Aber: erfordern initialen, teilweise signifikanten Einsatz
  • Schlechte Tests bringen nichts
    • verlangsamen am Anfang schlechten Code
    • langfristig trotzdem ungünstig
  • Test Code ist Teil der Codebase
    • Teil, der ein spezifisches Problem behandelt - Anwendungsrichtigkeit sicherstellen
  • Kosten eines Tests
    • Wert
    • anstehende Kosten
      • Refactoring des Tests, wenn der Code refactored wird
      • Test bei jeder Codeänderung ausführen
      • Mit Fehlalarmen durch den Test umgehen
      • Zeit für das Verstehen des Tests, wenn man den zu testenden Code verstehen möchte

General Testing Strategy

Testing Automation Pyramid

image_580.png

  • Software Tests in 3 Kategorien aufteilen
    • UI-Tests
      • Testen die Anwendung durch Interaktion mit der UI
      • sehr high-level
    • Service Tests
      • Black-Box testen von größeren Softwareteilen
        • bspw. Komponenten, Services
    • Unit Tests
      • werden während Entwicklung von Developern geschrieben
  • Gibt Idee, wie viele Tests pro Kategorie in der Test-Suite sein sollten

Testing Ice Cream Cone

image_581.png

  • Style einer Test-Suite, die häufig in der Industrie verwendet wird
    • hohe Anzahl manueller, high-level Tests
    • end-to-end Tests (auch an UI), die automatisch ausgeführt werden können
    • nur wenige integration/unit tests
  • Tests Suite mit dieser Strategie ist nicht gut wartbar
    • manuelle Tests sind teuer und langwierig
    • automatisierte high-level Tests gehen häufig kaputt, sobald Änderungen in der Anwendung auftreten

Test Driven Development (TDD)

image_582.png

  1. Tests schreiben
    • Design
      • Akzeptanzkriterien für den nächsten Arbeitsschritt festlegen
      • Anregung lose gekoppelte Komponenten zu entwerfen
        • einfache Testbarkeit, dann verbinden
      • Ausführbare Beschreibung von dem was der Code tut
    • Implementierung
      • Vervollständigen einer regressiven Test-Suite
  2. Tests ausführen
    • Implementierung
      • Error erkennen, während der Kontext noch frisch ist
    • Design
      • Bewusstmachung, wann die Implementierung vollständig ist

Golden Rule of TDD: schreibe niemals neue Funktionalitäten ohne einen fehlschlagenden Test

Vorteile des TDD

  • signifikante Reduktion der Defekt-Rate
    • auf Kosten eines moderaten Anstiegs im initialen Development-Prozesses
  • Empirische Untersuchungen haben das noch nicht bestätigt
    • TDD hat aber zu Qualitätssteigerung des Codes geführt

Häufige Fehler beim TDD

  • Individuelle Fehler
    • Vergessen die Tests regelmäßig auszuführen
    • Zu viele Tests auf einmal schreiben
    • Zu große/grobe Tests schreiben
    • zu triviale Tests schreiben, die eh funktionieren
  • Team-Fehler
    • Nur partieller Einsatz
    • Schlechte Wartung der Test-Suite
    • Verlassene Test-Suite (nie ausgeführt)

Unit-Testing vs. Integration Testing

Unit Integration
kleiner Teil eines Verhaltens größere Portion Code

image_583.png

4 Typen von Produktions-Code

image_584.png

Dimensionen

Komplexität und Domain-Signifikanz
  • Code Komplexität
    • Definiert durch Nummer der Branching-Punkte im Code
    • if-statements, Polymorphismus
  • Domain-Signifikanz
    • Wie signifikant ist der Code für die problematische Domain
    • normalerweise Verbindung Domain-Layer-Code zu End-User-Ziele
      • Hohe Domain-Signifikanz
    • Bsp. für niedrige Relevanz: Utility Code
Nummer der Kollaborateure
  • Kollaborateur = Abhängigkeit, die veränderlich /& außerhalb des Prozesses ist
    • veränderlich
      • nicht nur read-only
    • außerhalb des Prozesses
      • häufig geteilt
      • hindert Tests an unabhängiger Ausführung
  • Code mit vielen Kollaborateuren ist schwer zu testen

Typen

Domain model & algorithms
  • Domain Code, wenige Kollaborateure
  • komplexer Code häufig Teil des Domain-Models
  • wenige Kollaborateure → Testbarkeit
  • sollte NIEMALS Abhängigkeiten außerhalb des Prozesses haben
Controllers
  • Wenig Domain Code, viele Kollaborateure
  • Koordination der Ausführung von Use-Cases für Domain-Klassen und externen Anwendungen
  • Keine komplexe / Business-kritische Arbeit selbst/allein machen
  • wenig Komplexität, wenig Domain-Signifikanz
  • Viele Abhängigkeiten außerhalb des Projekts
Overcomplicated Code
  • Viel Domain Code, viele Kollaborateure
  • ist aber auch komplex und wichtig
Trivialer Code
  • wenig Domain-Code, wenig Kollaborateure

Separierung von Controllers & DomainModel, Algorithmen

image_585.png

  • Separiert komplexen Code von Code, der Orchestrierung macht
    • Domain Code hat tiefe Implementierung in Business Logik
    • Controller haben breite Orchestrierung aber enge Komplexität
  • Erhöht Wartbarkeit und Testbarkeit

Wie testet man die 4 Typen?

image_587.png

  • trivialer Code muss nicht getestet werden

Unit Testing

  • Beim Unit Testing gehts nicht nur um den technischen Aspekt
    • möglichst wenig Zeit Input
    • möglichst viel Benefits

Gute Test-Suite

  1. integriert in SDLC
    • Tests bringen nur was, wenn man sie ständig benutzt
      • am besten alle automatisiert bei jeder Änderung
  2. testet nur die wichtigsten Teile der Code-Base
    • Business-Logik
    • Systemkritische Teile
      • auch Abhängigkeiten nach außen
    • Rest nur indirekt / wenig testen
  3. gibt maximalen Wert mit minimalem Wartungsaufwand
    • Auch automatisierte Tests müssen ggf. nach Änderungen angepasst werden
      • Nur Tests behalten, die wirklich sinnvoll sind

Was ist ein Unit-Test

  1. Verifiziert einen kleinen Teil des Codes (unit)

  2. macht es schnell

  3. macht es isoliert

London School Interpretation

  • Isolation bedeutet, dass jede Klasse ihren eigenen Test bekommt
    • Auch wenn sie von gleicher Klasse erben
Testing Doubles

image_591.png

  • Objekt, dass gleiches Verhalten und Aussehen, wie Gegenstück hat
  • Vereinfachte Version, die Komplexität verringert
Vorteile London School Interpretation
  • Wenn ein Test fehlschlägt, ist klar, was kaputt ist
  • Gibt Fähigkeit den Objektgraphen aufzusplitten
    • Jede Klasse hat ihre eigenen Abhängigkeiten / Vererbungen
    • Schwer zu testen ohne Testing Doubles
    • Es müssen nicht die Abhängigkeiten von Abhängigkeiten beachtet werden
      • haben ja eigene Tests
  • Projektweite Guideline:
    • Nur eine Klasse auf einmal
    • image_592.png

Classic School Interpretation

  • Isolation bedeutet, dass jeder Test in Isolation läuft
    • Mehrere Klassen auf einmal ist okay
      • Solange sie alle auf ihrem eigenen Speicher laufen
      • kein geteilter Zustand
    • Verhindert Kommunikation /Beeinflussung zwischen Tests
      • Scheiß auf Reihenfolge oder Ergebnis von anderen Tests
Geteilte, private, Out-Of-Process Abhängigkeiten
  • Geteilte Abhängigkeiten
    • Können sich gegenseitig beeinflussen
      • Müssen ersetzt werden
      • bspw. geteilte DB → neue/bearbeitete Daten können Tests beeinflussen
  • Private Abhängigkeiten → :)
  • Out-Of-Process Abhängigkeiten
    • Meistens ähnlich wie geteilte Abh.
      • DB = geteilt und out-of-process
    • read-only-DB ist fine
    • also: kommt drauf an ob gut oder nicht
      image_593.png

Beispiel Classic School

image_594.png

  • Enough inventory → purchase geht durch, inventory amount geht runter

  • not enough product → kein purchase, keine änderungen

  • Typische AAA-Sequenz

    • arrange, act, assert
      • alle Abhängigkeiten und System vorbereiten
      • Verhalten ausführen, das verifiziert werden soll
      • Erwartete Ergebnisse überprüfen
  • Outcome:

    • Customer und Store werden verifiziert, nicht nur Store
    • Jeder Bug in Store lässt die Tests fehlschlagen
      • auch wenn Customer komplett richtig ist

Beispiel London School

image_595.png

  • Gleiche Tests, aber Store wird mit test-doubles ersetzt

    • "fake dependency" = "Mock"
  • AAA Sequenz

    • Arrange:
      • Store nicht modifizieren, stattdessen festlegen, wie er auf hasEnoughInventory() reagieren soll
    • Act
    • Assert
      • Interaktion zwischen Store und Customer wird genauer untersucht

Vergleich Classic / London

Isolation of A unit is Uses test doubles for
London School Units A class All but immutable dependencies
Classic School Unit tests A class or a set of classes shared dependencies

image_596.png

Unit Tests - Good Practices

Good Practices - Structuring

image_597.png

  • Offensichtliche Struktur ist wichtig

    • Code wird häufiger gelesen als geschrieben
  • AAA-Pattern splittet Tests in 3 Teile

    • Alternative: Give-When-Then Pattern
      • Einziger Unterschied: besser lesbar für nicht-Programmierer
  • Zu vermeidende Dinge: