Freitag, 27. Mai 2011

Shy coding

Schüchterner Quellcode minimiert die Abhängigkeiten zwischen Objekten. Die Reduzierung der Abhängigkeiten führt zu loser Kopplung, die wiederum isolierte testbare Softwarebausteine hervorbringt. Testbare Softwarebausteine mit ihren spezifischen Eigenschaften (gekapselt, kohäsiv, Mock freundlich, etc.), fördern die Evolvierbarkeit einer Software-Applikation.

Das „Law of Demeter (LoD)" definiert Regeln für schüchternen Quellcode. Das klassische Law of Demeter basiert auf einer Veröffentlichung von Karl Lieberherr und Ian Holland. Das Law of Demeter wird  mit dem "Principle of Least Knowledge" gleichgesetzt. Dieses Prinzip sagt aus, dass Objekte nur mit eng befreundeten Objekten sprechen sollen. Die Interaktionen zwischen Objekten sollen durch Einhaltung des Prinzips reduziert werden, sodass lose gekoppelte Software-Architekturen entstehen. Wird das Prinzip häufig verletzt, entstehen zerbrechliche Systeme, die schwer zu warten und zu verstehen sind.

Anfällig für die Verletzung des Prinzips sind Facaden, die eine einfachere Sicht auf ein komplexes Teilsystem bieten und Aggregatsobjekte (Kompositionen, Kontextobjekte, etc.), die eine Menge an Funktionalitäten bündeln. Vorsicht ist auch bei Mediatoren geboten, die komplexere Interaktionen zwischen Objekten vereinfachen sollen.

Entwicklungsumgebungen wie Eclipse unterstützen die Suche von Objekten durch ausgefeilte Kontextfunktionen. Dennoch ist es ratsam Interaktionen zwischen Objekten zu minimieren, sodass eine Suche nach Objekten nicht nötig ist. Die transitive Navigation führt dabei besonders zu unübersichtlichen Objektvernetzungen, die nur schwer aufgelöst werden können.

Beispiel für eine transitive Navigation:

long count = order.getProduct().getItems().countItems();

Besser ist:

long count = order.countItems();

Beispiel für einen Verstoß gegen das Law of Demeter:

Constructor(final Context context) {


     this.view = context.getFrame().getView();
}

Korrekt ist:

Constructor(final View view) {


     this.view = view;
}

Merkregeln, rufe nur Methoden auf von...
  1. dir selbst (normalerweise private Methoden),
  2. Objektreferenzen, die als Methodenargument übergeben wurden,
  3. jedem selbst erzeugten Objekt (in Methoden der Klasse erzeugten Objekten),
  4. in Assoziation stehenden Klassen (Attribute der Klasse)

Sonntag, 15. Mai 2011

Testing for Clean Agility

Softwareentwicklungsmethoden nutzen Tests zur Verifikation und Sicherstellung der Qualität einer Softwareanwendung. Im klassischen Wasserfallmodell erfolgt der Testzyklus nach der Implementierungsphase in Form einer Verifikation der Implementierung. Wurde die Verifikation erfolgreich abgeschlossen, erfolgt das Deployment der Applikation in die Kundenumgebung. In der Kundenumgebung finden im Rahmen der Abnahmeprozesse die Akzeptanztests statt.

Das Wasserfallmodell funktioniert sofern der Kunde zuvor eine Detailspezifikation abgenommen hat und keine größeren Änderungen im Projektverlauf fordert. Häufig ist sich der Kunde im Vorfeld eines Projektes aber unschlüssig und kennt noch nicht alle seine Anforderungen im Detail. Das Wasserfallmodell blockiert das Lernen des Kunden und das Finden der geeigneten Anforderungen im Projektverlauf.

Im Wasserfallmodell sind die einzelnen Phasen strikt voneinander getrennt, sodass das Design und die Akzeptanztests weit auseinander liegen. Die Distanz zwischen der Implementierungsphase und der Verifikation der Software durch Testzyklen im Anschluss an die Implementierung ist darüber hinaus zu weitläufig.

Wasserfallmodell

Agile Methoden wie Scrum nutzen überschaubare Planungs- und Implementierungsphasen von max. 30 Tagen. Tests stehen bei den agilen Methoden im Vordergrund. Agile Softwareentwicklung nutzt kurze Entwicklungszyklen und zeitnahes Feedback. Das zeitnahe Feedback wird durch automatisierte Testläufe und die kontinuierliche Integration erreicht.

Automatisierte Testläufe, die zyklisch ausgeführt werden, sind eine wesentliche Voraussetzung für Refaktorisierungen zur Verbesserung der Softwarequalität. Die kontinuierliche  Verbesserung der Softwarequalität ist fest in den agilen Methoden  verankert. In Scrum ist normalerweise kein eigener Raum für Refaktorisierungen vorhanden. Refaktorisierungen und die Fehlerbehebung werden im Sprint bearbeitet. Nur in Ausnahmefällen werden größere Refaktorisierungen in ein technisches Backlog übernommen und in die Sprint-Planung miteinbezogen.

Laut der „Definition of Done“ erstellen Scrum Teams lieferbare User Stories in hoher Qualität. Fehlende Qualitätssicherung und mangelnde Refaktorisierungen würden sich in Sprints in der Folge negativ auf die Velocity eines Scrum Teams auswirken. Bei CCD sind Refaktorisierungen und automatisierte Tests ebenfalls tief verwurzelt. Die Pfadfinderregel propagiert geradezu Refaktorisierungen und damit die Beseitigung von „Code Smells“.

Im agilen Lager gibt es dennoch unterschiedliche Meinungen wann und wie Testzyklen durchgeführt werden sollen. Es ist kein Streitpunkt mehr, dass automatisierte Tests zur Erzielung hoher Softwarequalität notwendig sind. Das schnelle Feedback und die mit den automatisierten Tests einhergehenden Vorteile sind ebenfalls unstrittig und anerkannt. Testbare Softwarebausteine sind in der Regel durch die lose Kopplung isoliert von ihrer Umgebung, weisen eine hohe Kohäsion (SRP = Single Responsibility Principle) auf und sind gut gekapselt (IHP = Information Hiding Principle). Die Nutzung von Testattrappen (Mocks) erlaubt es unabhängig von den externen Abhängigkeiten einer komplexen Softwarearchitektur testbare Softwarebausteine zu erstellen.Dies sind alles Vorteile, welche Tests attraktiv machen.

Rückt der Test First und Test Driven Design/Development (TDD) Ansatz die Unit-Tests zunächst in den Mittelpunkt verfolgen andere Ansätze, wie beispielsweise DDT (Design Driven Testing), das Ziel, ausgehend von einem stabilen Design, testbaren Quellcode zu erstellen. Bei diesem Ansatz sind Implementierungsregeln einzuhalten, damit testbare Softwarebausteine entstehen können.

Beispiele für DDT Implementierungsregeln:
  1. Verwende die Vererbung mit Bedacht (FCoI = Favour Composition over Inheritance)
  2. Beachte das Single Level of Abstraction (SLA) Prinzip
  3. Vermeide Singletons, statische Initialisierungen, statische Methoden und Variablen
  4. Nutze die lose Kopplung
  5. Trenne Oberflächen- und Geschäftslogik
  6. Entferne Initialisierungen aus dem Konstruktor
Testbare Softwarebausteine werden bei DDT aus den Anwendungsfällen der Analysephase abgeleitet und entsprechen den Akzeptanztests. DDT geht Top-Down von der Benutzersicht aus. TDD hingegen ist eher als Bottom-Up Methode anzusehen, bei dem der Verwender eines Softwarebausteins eine zentrale Rolle spielt. Der Verwender ist allerdings nicht unbedingt der Benutzer des Softwarebausteins, sondern möglicherweise ein anderer Softwarebaustein.

Bei TDD entstehen sehr viele kleine Unit-Tests. Die Gegner von TDD argumentieren deshalb gerne, dass TDD zu viele  Testfälle hervorbringt, die nicht den gleichen Stellenwert haben wie die Kerntestfälle der Benutzersicht. Dennoch sind Unit-Tests für die Qualität von Software wesentlich, weil Unit-Tests grundsätzlich den Quellcode verbessern. Die Begründung für diese Aussage ist einfach: Ein Unit-Test ist ein Verwender des Quellcodes, sodass der Quellcode (insbesondere die Schnittstellen) im Zuge der einhergehenden Refaktorisierungen stetig besser wird. Übermäßiger Fleiß beim Schreiben der Unit-Tests kann allerdings zu viel des Guten sein. Eine hohe Testabdeckung durch Unit-Tests ist nämlich nicht immer ein Indikator für Softwarequalität. Code Coverage Tools, welche die Testabdeckung messen nutzen den Quellcode einer Applikation zur Analyse der Testabdeckung nicht aber alle möglichen Permutationen, die semantisches Wissen beinhalten.

Frei nach dem Motto „Design muss sein!“ gehen deshalb neuere Ansätze zur Entwicklung von Software davon aus, dass die Akzeptanztests an erster Stelle stehen und die Unit-Tests ausschließlich ihren Platz in der Implementierungsphase haben. Demzufolge steht an erster Stelle ein Design in unterschiedlich starker Ausprägung. Ein Design drückt sich durch die Modellierung von Softwarebausteinen und das Nachdenken über Anforderungen, Schnittstellen und Randbedingungen aus. In der Summe gute Voraussetzungen für Softwarequalität ohne einen großen Graben wie beim Wasserfallmodell überwinden zu müssen.

In TDD spricht man heute von einer inneren und äußeren Feedback Loop. Die äußere Feedback Loop wird durch einen Akzeptanztest (End-To-End Test) gebildet und die innere durch eine Reihe von Unit-Tests.

TDD (innere/äußere Feedback Loops)

Bei Scrum spricht man von "Acceptance Test Driven Development (ATDD)”. Bei diesem Ansatz werden die funktionalen Anforderungen einer User Story als automatisierte Tests vor der Implementierung der Funktionalität geschrieben. Fehlgeschlagene Tests bringt man bei der inkrementellen Umsetzung einer User Story zum Laufen. Diese Tests werden für gewöhnlich vom Product Owner durchgeführt, der ein starkes Interesse an dem Wert einer User Story aus Sicht des Geschäftsvorteils hat. Scrum schreibt einem Team nicht vor, wie es im Sprint zu arbeiten hat. Unit-Tests, Refaktorisierungen und CCD-Prinzipien sind dennoch essentiell für das Erreichen eines Sprint-Ziels und der „Definition of Done“ lieferbare User Stories zu erstellen.

ATDD-Kreislauf

Die bisher diskutierten Methoden nutzen Feedbacks und Testlevels. Die Einhaltung der Testlevels und häufiges zeitnahes Feedback haben sich in der Praxis bewährt und sind fest mit dem agilen Gedanken verbunden.

Testlevels beantworten folgende Fragen:
  1. Akzeptanztests: Funktioniert eine Kette von Softwarebausteinen und das Gesamtsystem?
  2. Integrationstests: Funktioniert der entwickelte Quellcode im Kontext externer Schnittstellen und Systeme, die wir nicht ändern können?
  3. Unit-Tests: Erledigen die Objekte den Job richtig und sind die Objektschnittstellen einfach zu verwenden?
Im Java-Umfeld sind für jeden Testlevel einfach zu nutzende Werkzeuge und Frameworks zur automatisierten Testdurchführung vorhanden. Für Akzeptanztests können Fitnesse, Selenium und JSFUnit verwendet werden. Integrationstests lassen sich mit Buildsystemen (Continuous Integration) wie Maven, Ant und Continuous Integration Servern wie Hudson, CruiseControl, Apache Gump sowie Apache Continuum durchführen. Unit-Tests sind die Domäne von JUnit, dem populären Framework für automatisierte Entwicklertests.

Eine „Best Practice“ ist es, ausgehend von den Anforderungen, Akzeptanztests zu definieren/auszuführen, in der Implementierungsphase Unit-Tests konform dem TDD Mantra einzusetzen und zyklisch automatisierte Integrationstests beim „Nightly Build“ durch ein Continuous Integration (CI) System durchführen zu lassen. Durch diese "Best Practice" können Applikationen sehr viel schneller und in höherer Qualität entwickelt und regelmäßig mit geringer Reibung ausgeliefert werden. Wie wichtig automatisierte Tests und die kontinuierliche Integration für die agile Bewegung sind, verdeutlicht die Definition der „Balanced Agility“ von Mike Beedle. Diese Definition setzt minimale Voraussetzungen für den agilen Entwicklungsprozess voraus.

Definition für "Balanced Agility":
  1. Scrum als Vorgehensmodell
  2. Grundlegende Engineering Practices
    • Kontinuierliche Integration
    • Automatisierte Testdurchführung auf unterschiedlichen Testlevels
    • Release Management
Die Definition von Mike Beedle zu adaptieren und um die CCD-Prinzipien zu erweitern ergibt das Synonym „Clean Agility".