# 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
  - 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:**
  - 