zusammenfassungen/Writerside/topics/04/Software Engineering/01_ImplementingForMaintainability.md
David Schirrmeister 1e74934263 update
2025-04-28 19:43:03 +02:00

25 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 {id="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
  • Manchmal benutzt ein Test mehrere AAA Steps
  • if-statements
    • wird schwerer lesbar
    • sollte eine simple Sequenz ohne branches sein
Größe der AAA Sections
  • arrange
    • meistens am größten
    • falls deutlich größer als act und assert zusammen
  • act
    • sollte nicht mehr als eine Zeile sein
  • assert
    • kann mehrere Asserts beinhalten
      • eine unit kann ja auch mehrere Dinge verändern, die überprüft werden müssen
    • zu viele asserts implizieren schlechten Production-Code
      • fehlende Abstraktion bspw.
  • teardown-Phase (alles wieder auf den alten Stand bringen)
    • die meisten unit-Tests brauchen keine
    • ist meistens durch ein anderes Modell gelöst
      • bspw. Mocks oder so

Good Practices - Naming Tests

  • aussagekräftige Namen
  • häufig aber schlecht:
    • [MethodeDieGetestetWird][Szenario][ErwartetesErgebnis]
  • Name in plain Englisch besser lesbar
  • Beispiel
    • Sum_TwoNumbers_ReturnsSum() :(
    • Sum_of_two_numbers() :)

Guideline

  • Keiner starken Benennungspolicy folgen
  • Benenne die Tests als würdest du es einem nicht-Programmierer-Deppen erklären, der aber die Anwendung kennt
  • Separiere Wörter, sodass sie lesbar sind
    • bspw. durch -, _, testTest

Good Practices - Parameterized Tests

Motivation:

  • Ein Test ist meistens nicht genug um eine Verhaltens-Unit zu beschreiben
    • hat div. Komponenten
  • Beispiel:
    • Online-Store mit Lieferfunktion
      • constraint: frühstes Lieferdatum ist übermorgen
        • public void Delivery_with_a_past_date_is_invalid()
          public void Delivery_for_today_is_invalid()
          public void Delivery_for_tomorrow_is_invalid()
          public void The_soonest_delivery_date_is_two_days_from_now()
          
        • das viel zu viel
        • wenn man das mal auf größere Probleme anwendet wirds nicht besser

Parametrisierte Tests: {id="parametrized-tests"}

  • gruppieren Tests
  • meiste xUnit Frameworks haben Funktion dafür
  • Beispiel in .NET:
    • image_610.png
  • Also:
    • weniger Test-Code
    • Aber:
      • schwerer herauszufinden, welche Fakten die Methode repräsentiert
      • je mehr Parameter, desto schwieriger

Integration Testing

  • Verifiziert größeren Teil des Systems (mehrere Units)

  • braucht evtl. länger als ein Unit-Test

  • ist evtl. abhängig von anderen Tests (keine Isolation)

  • Balance zwischen Unit- und Integration-Tests ist wichtig

    • direktes Arbeiten mit out-of-process-Abhängigkeiten macht Integration Tests langsam
      • sind teuer zu warten
      • dafür besserer Schutz gegen Zurückentwicklungen
    • Integration-Tests für den längsten Happy-Path mit den meisten Abhängigkeiten
    • Eck-Szenarien des Business-Szenarios mit Unit-Tests
  • Out-of-Process Abhängigkeiten

    • Managed (Abhängigkeiten über die wir volle Kontrolle haben)
      • nur Zugriff über die Applikation
      • Interaktionen sind für die externe Welt nicht sichtbar
      • nicht mocken
        • wir wollen, dass die tests fehlschlagen, wenn wir was an der DB ändern bspw.
    • Unmanaged (Abhängigkeiten über die wir keine Kontrolle haben)
      • mocken
        • wir wollen volle Kontrolle über die Tests

Integration Testing - Example

Customer Management System
image_611.png

  • alle User sind in einer DB
  • Änderungen werden an angeschlossene Anwendungen über Message-Bus geschickt
  • Business-Rules
    • Falls Email zur Company-Domain gehört → Employer Status, sonst Customer
    • System muss Anzahl der Employer tracken (auch beim Wechseln)
    • Wenn Mail wechselt müssen externe Systeme über MessageBus benachrichtigt werden

image_612.png

  • Domain model, Algorithms

    • Company
      • Attribute: DomainName NumberOfEpmoyees
      • Methoden: ChangeNumberOfEmployees(...) IsEmailCorporate(...)
    • CompanyFactory
      • Erstellt Company-Objekte anhand von DB-Feldern
    • User
      • Attribute: UserId Email UserType EmailChangedEvents[]
      • Methoden: ChangeEmail(...)
    • UserFactory
      • Erstellt User-Objekte anhand von DB-Feldern
  • Controllers

    • UserController
      • Delegiert die Ausführung von Email-Änderungen
      • Logik ist gekapselt in den Domain-Klassen
      • Managed Kommunikation mit out-of-process-Abhängigkeiten
        • hier: DB und messageBus
  • Integration Test für den längsten Happy-Path

    • geht durch alle Abhängigkeiten
      • hier: corporate → non-corporate Mail
    • davor: out-of-process-Abhängigkeiten Kategorisieren
      • was direkt testen, was mocken?
        • hier: DB ist managed → direkt testen
        • hier: messageBus ist unmanaged → mocken
          • OCP anwenden durch ein Interface
        • image_614.png
  • Edge-cases mit Unit-Tests

image_613.png

Integration Testing - Good Practices

  • Immer einen festen Platz für das Domain-Model in der Code-Base haben
    • unit-tests dafür, integration-tests für Controller
    • Abgrenzung kann unterschiedlich vorgenommen werden
      • package, namespace, assembly, ...
  • Möglichst wenig Layer in der Anwendung
    • Abstraktionen und Generalisierungen → mehr Layer
      • wird schwer den Code nachzuvollziehen
      • image_615.png
    • image_616.png
  • Dopplungen im AAA-Schema
    • teilweise verlockend
      • image_617.png
      • Falls einer nicht funktioniert geht der andere nicht
    • Ausnahme:
      • out-of-process-Abhängigkeit, die nur schwer in den gewünschten Zustand kommt

Test Coverage

Test-Abdeckung wird häufig genutzt, um die Qualität einer Test-Suite zu beurteilen

Niedrige Coverage zeigt, dass die Test-Suite nicht ausreichend ist.

Hohe Coverage bedeutet nicht, dass die Test-Suite gut ist

Code Coverage

  • Anteil der Code-Zeilen, die durch die Tests ausgeführt werden
  • Beispiel:
    • image_657.png
      • 80% Coverage
    • image_658.png
      • 100% Coverage
      • nicht besser als der Test oben, der True outcome wird ja trotzdem nicht überprüft

Branch Coverage

  • Anteil der Code-Branches die durch die Tests ausgeführt werden
  • Beispiel:
    • image_659.png
    • 1 / 2 Branches gecovered → 50% Coverage
  • Gibt meist wertvollere Ergebnisse als Code-Coverage

Probleme mit Coverage-Metriken

  • Keine Garantie, dass der Test alle möglichen Ausgänge überprüft
    • um die Garantie zu haben:
      • Alle Codepfäde müssen getestet werden
      • Asserts müssen da sein
        • image_660.png
        • führt zwar alles aus, überprüft aber nicht ob es richtig ist, weil nix asseert
      • ein System kann teilweise mehrere Outcomes haben
        • Alle müssen getestet werden
  • Keine Coverage-Metrik kann Code in externen Libraries abbilden
  • Externe Bibliotheken
    • image_661.png
    • Test-Coverage ist bei 100%
    • Es sind aber trotzdem nicht alle möglichen Outcomes abgedeckt
      • bspw. wenn die Zahl zu groß ist, keine da ist, ...
  • Coverage-Metriken sind nur Indikatoren, nix tatsächlich final festlegendes

Rubber Duck Debugging

Wenn verzweifelt → Den Fehler einer Gummiente erklären

  • Wenn man versucht jemandem das Problem zu erklären ist man gezwingen
    • Das Problem von einer anderen Perspektive anzusehen
    • Dadurch ein tieferes Verständnis vom Problem zu bekommen
    • einfacher eine Lösung zu finden

image_662.png

Pair Programming

  • Zu zweit auf einer Maschine Code schreiben
    • gleichzeitig auch Arbeit planen und diskutieren
  • Idee dahinter:
    • zwei Gehirne und vier Augen sind besser als ein Gehirn und 2 Augen
  • Teams haben ausprobiert und festgestellt
    • geht schneller, da man fokussierter bleibt
    • höhere Code-Qualität

Pairing Styles

Pairing Style: Driver and Navigator

  • Fahrer
    • Person an der Tastatur
    • ist fokussiert das nächste kleine Ziel zu erreichen
      • ignoriert größere Probleme
    • sollte die ganze Zeit mitreden, was er tut
  • Navigator (hoffentlich ohne link rechts Schwäche)
    • ist in der überwachenden Position, während der Fahrer tippt
    • Reviewed den Code durchgängig
      • direktes Feedback
    • Hat auch größere Probleme, Bugs im Kopf
      • Notizen für mögliche nächste Schritte
  • Möglicher Arbeitsablauf:
    • image_663.png

Pairing Style - PingPong

  • Perfekt für einen TDD-Task
    • Ping
      • Dev A schreibt einen fehlschlagenden Test
    • Pong
      • Dev B schreibt Implementierung damit er klappt
    • Ping
      • Dev B schreibt den nächsten Test
  • Bei jedem Pong kann man auch nochmal refactoren

Pairing Style - Strong-Style Pairing

  • Super für Wissenstransfer
  • Regel: "Jede Idee, die von deinem Kopf in den Computer soll, muss durch die Hände von jemand anderem gehen"
  • Navigator
    • erfahrene Person
  • Fahrer
    • Person die was lernen will/soll
    • sollte dem Navigator voll vertrauen
      • Warum?-Fragen und Challenges nach dem Implementieren besprechen

Pairing Style - Research and Explore

  • Überlegen und herausfinden ist häufig notwendig
    • bspw. bei neuen Technologien etc.
  • Wie kann man das im Pair-Modus angehen
    • Liste mit Fragen für eine mögliche Situation machen
    • Aufsplitten für einzelne Zeitslots
      • entweder Fragen aufteilen oder gleichzeitig Antworten auf gleiche suchen
    • Zusammenkommen und diskutieren/teilen was man gefunden hat

Pairing Style - Documentation

  • Je nach Situation und Präferenz
    • Dokumentation zusammen machen
    • Einer schreibt, anderer macht Anmerkungen

Benefits / Challenges

Vorteile Herausforderungen
Wissensaustausch Kann anstrengend sein
Reflektion: hat mans wirklich (richtig) verstanden? Verschiedene Skill Levels: Können zu falschen Erwartungen und Frustration führen
Fokus behalten: Zusammen arbeitet man strukturierter, keine "schnellen" side-quests Power-Dynamics: Chef-Mitarbeiter → kann das ganze schwierig machen
Code Review on-the-go: Pairing erfordert Verletzlichkeit: ist schwer zu sagen, dass man etwas nicht weiß/kann - aber notwendig
Zwei Denk-Modi kombiniert: verschiedene Perspektiven haben Chef überzeugen, dass es gut ist
Gemeinsames Code-Ownership: Höhere Chance, dass jemand sich traut Änderungen vorzuschlagen
Schnelles Onboarding image_665.png
image_664.png image_666.png

Static Code Analysis

  • Code analysieren, ohne ihn auszuführen
  • Tools inspizieren das Programm für ...
    • alle möglichen Verhalten
    • suchen Coding-Fehler, Back-Doors, ...
    • generieren Metriken für den Code die helfen die Qualität zu analysieren
  • Kann Qualität verbessern

Beispiele für die Identifikation von Bekannten Problemen und Bad Practices

  • Generische Beispiele
    • Größe
      • Kann verschieden gemessen werden
        • Code-Zeilen, Klassen, Dateien, Funktionen, ...
    • Kommentare
      • zeigen häufig, dass der Code zu komplex ist
    • Duplikate
      • Dont repeat yourself!
  • Sicherheitsrelevante Issues
    • OWASP Top Ten
      • Sammlung von kritischen Risiken für Anwendungssicherheit
    • SAST Tools
      • finden bekannte Risiken

Simple Metrik für Komplexität: McCabe Metrik

image_667.png

  • Cyclomatische Komplexität
    • Behandelt Programmstuktur als Graphen
  • Wird folgendermaßen kalkuliert:
    • C = E-N + 2P
    • E = Nummer der Ecken
    • N = Nummer der Nodes
    • P = Nummer der verbundenen Komponenten (für eine OO Funktion ist P = 1)
  • Beispiel:
    • C = 9-8+ (2*1) = 3
  • image_668.png

Übung Test Cases, Dependencies and more

image_778.png
image_779.png

  1. :

    • BriefSystem - Controller
    • BriefErstellung - Domain
    • Empfaenger - trivialer Code
    • EmpfaengerInterface - Domain Model
    • EmpfaengerCsvLeser - trivialer Code
    • Brief - trivialer Code
  2. : alle

  3. :

  • wurdeEmpfängerHinzugefügt
    • Empfänger über Interface hinzufügen
    • Brief erstellen und schauen ob Empfänger hinzufügbar ist
      • EmpfängerListe kann sonst nicht abgefragt werden
  1. :