# 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](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](01_ImplementingForMaintainability.md#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](01_ImplementingForMaintainability.md#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](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](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](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](#black-box-testing) 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](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](image_582.png)
1. Tests schreiben
   - **Design**
     - Akzeptanzkriterien für den nächsten Arbeitsschritt festlegen
     - Anregung [lose gekoppelte Komponenten](ImplementingForMaintainability.md#loose-coupling) 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](image_583.png)

### 4 Typen von Produktions-Code
![image_584.png](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](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](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](00_Introduction.md#software-development-lifecycle-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](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](#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](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](image_593.png)

#### Beispiel Classic School
![image_594.png](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](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](image_596.png)

### Unit Tests - Good Practices
#### Good Practices - Structuring
![image_597.png](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**
    - bspw: ![image_609.png](image_609.png)
    - ist kein Unit-Test mehr, sondern ein [Integration-Test](#unit-testing-vs-integration-testing)
  - **if-statements**
    - wird schwerer lesbar
    - sollte eine simple Sequenz ohne branches sein

##### Größe der [AAA](#aaa-sequenz) Sections
- **arrange** 
  - meistens am größten
  - falls deutlich größer als act und assert zusammen
    - extrahieren in neue Methode oder separate [Factory-Klasse](DesignPatterns.md#factory-method-virtual-constructor)
- **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](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)
  - aus dem [Controllers-Quadranten](#4-typen-von-produktions-code)
- 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](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](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](DesignPrinciples.md#o-open-closed-principle-ocp) anwenden durch ein Interface_
      - ![image_614.png](image_614.png)
- **Edge-cases mit Unit-Tests**

![image_613.png](image_613.png)

### Integration Testing - Good Practices
- Immer einen festen Platz für das [Domain-Model](#domain-model-algorithms) 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_615.png)
  - ![image_616.png](image_616.png)
- Dopplungen im [AAA](#aaa-sequenz)-Schema
  - teilweise verlockend
    - ![image_617.png](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