310 lines
12 KiB
Markdown
310 lines
12 KiB
Markdown
# 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
|
|

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

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

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

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

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

|
|
|
|
### 4 Typen von Produktions-Code
|
|

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

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

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

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

|
|
|
|
#### Beispiel Classic School
|
|

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

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

|
|
|
|
### Unit Tests - Good Practices
|
|
#### Good Practices - Structuring
|
|

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