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