Schlagwort

refactoring

23.6.2012

Mein Buch zum Download

Wie die Zeit vergeht… Testgetriebene Entwicklung mit JUnit und FIT ist nunmehr sieben Jahre alt. Ich habe damals versucht, die TDD-Techniken so zeitlos wie möglich zu beschreiben. Doch natürlich haben sich seitdem eine Reihe neuer Werkzeuge und Ideen hinzugesellt. Im Ruby-Umfeld beispielsweise sind BDD und rSpec mehr oder weniger die Norm. Open Source ohne Unit Tests ist selten, wenn nicht undenkbar geworden. Wie zu erwarten war, sind jedoch nicht alle unsere Ideen kleben geblieben. Aber ein paar schon. Vielleicht die wichtigsten.

Auch sieben Jahre später bin ich mit meinem Text immer noch überraschend einverstanden. Einzig wünschte ich mir, ich hätte den Kontext der Techniken im Buch damals besser herausgestellt. Test-Driven Development sind Best Practices. Best Practices existieren in einem wohl definierten Kontext. Der Untertitel "Wie Software änderbar bleibt" und insbesondere das letzte Buchkapitel "Änderbare Software" geben einen Hinweis, aber der meiner Meinung nach wichtigste Trade-off bleibt, langlebige Software zu entwickeln. In Open Source ist diese Voraussetzung stets gegeben. Im Startup dagegen kann der Kontext ein gänzlich anderer sein. Bei Rivva sind meine Tests zum Beispiel viel mehr risikogetrieben. Und natürlich hätte das Buch für eine andere Programmiersprache auch eine andere Gestalt angenommen. In Ruby entwickle ich fast ausschließlich REPL-getrieben.

Buchcover

Das Buch ist mittlerweile vergriffen und nicht mehr im Druck. Deshalb freue ich mich, dass der dpunkt.verlag mir jetzt sein Einverständnis gegeben hat, das Buch als kostenloses eBook veröffentlichen zu können. Mein besonderer Dank dafür gilt meiner Lektorin Christa Preisendanz.

Download: Testgetriebene Entwicklung mit JUnit & FIT (PDF)

Happy 100th Birthday, Mr. Alan Turing (1912-1954).

17.11.2005

Testgetriebene Entwicklung
mit JUnit und FIT

Buchcover

Frank Westphal
Testgetriebene Entwicklung
mit JUnit & FIT:

Wie Software änderbar bleibt

dpunkt.verlag
346 Seiten
November 2005
ISBN 3-89864-220-8

Bei Amazon ansehen

Beschreibung

Testgetriebene Entwicklung geht von einem fehlschlagenden Test aus. Software wird in kleinen sicheren Schritten entwickelt, die abwechselnd darauf abzielen, eine neue Anforderung zu implementieren (den fehlschlagenden Test also zu erfüllen) und das Design zu verbessern (und dabei weiterhin alle Tests zu bestehen).

  • Wenn frühes und häufiges Testen wichtig ist, warum schreiben wir nicht für jedes neue Feature zuerst einen automatisierten Test? So können wir während der Entwicklung jederzeit unsere Tests ausführen und lernen, ob unser Code wie gewünscht funktioniert.
  • Wenn Design wichtig ist, warum investieren wir dann nicht Tag für Tag darin? So können wir dafür sorgen, dass es möglichst einfach bleibt und nicht mit der Zeit zunehmend degeneriert.
  • Wenn Anforderungsdefinition wichtig ist, warum ermöglichen wir unseren Kunden dann nicht, in einem ausführbaren Anforderungsdokument Testfälle für konkrete Anwendungsbeispiele zu spezifizieren? So können wir dokumentieren, welche Funktionalität tatsächlich gefordert ist, und anschließend verifizieren, ob die Erwartungen des Kunden erfüllt werden.

Das Buch führt mit praktischen Beispielen in die Testgetriebene Entwicklung mit den Open-Source-Werkzeugen JUnit und FIT ein.

Frank Westphal has turned his extensive experience in using and teaching developer testing with JUnit and FIT into a thorough and usable guide for programmers and testers, including the first published guide to JUnit 4. What I was most struck by in reading this book was the combination of philosophical background and detailed, practical advice. – Kent Beck, Three Rivers Institute

Amazon-Rezensionen

ganz aus dem häuschen – Thomas Steinbach

Warum sind wir darauf nicht schon eher gekommen? – H. Mausolf

Grandios !! – Bernd Will

Lockere Lektüre für flotte Erfolge – Gernot Starke

Motivation für die Praxis – Frankmartin Wiethüchter

Seit langem kein Buch mehr so schnell durchgearbeitet! – Ulrich Storck

Rundum gelungen – Jens Uwe Pipka

Übers Entwickeln und Entwerfen - auf testgetriebene Art – Christoph Steindl

Ideal für professionelle Softwareentwickler – Dierk König

Testen als Mittel zum Zweck – Stefan Roock

Buchbesprechungen

Inhaltsverzeichnis

Geleitwort von Johannes Link (PDF)

Kapitel 1: Einleitung (PDF)

  • Was ist Testgetriebene Entwicklung?
  • Warum Testgetriebene Entwicklung?
  • Über dieses Buch
  • Merci beaucoup

Kapitel 2: Testgetriebene Entwicklung, über die Schulter geschaut

  • Eine Programmierepisode
  • Testgetriebenes Programmieren
  • Möglichst einfaches Design
  • Ein wenig Testen, ein wenig Programmieren ...
  • Evolutionäres Design
  • Natürlicher Abschluss einer Programmierepisode
  • Refactoring
  • Abschließende Reflexion
  • Häufige Integration
  • Rückblende
  • "Aus dem Bauch" von Sabine Embacher

Kapitel 3: Unit Tests mit JUnit (PDF)

  • Download und Installation
  • Ein erstes Beispiel
  • Anatomie eines Testfalls
  • Test-First
  • JUnit in Eclipse
  • Das JUnit-Framework von innen
  • »Assert«
  • »AssertionFailedError«
  • »TestCase«
  • Lebenszyklus eines Testfalls
  • »TestSuite«
  • »TestRunner«
  • Zwei Methoden, die das Testen vereinfachen
  • Testen von Exceptions
  • Unerwartete Exceptions
  • "Woran erkennt man, dass etwas testgetrieben entwickelt wurde?" von Johannes Link
  • JUnit 4

Kapitel 4: Testgetriebene Programmierung

  • Die erste Direktive
  • Der Testgetriebene Entwicklungszyklus
  • Die Programmierzüge
  • Beginn einer Testepisode
  • Ein einfacher Testplan
  • Erst ein neuer Test ...
  • ... dann den Test fehlschlagen sehen
  • ... schließlich den Test erfüllen
  • Zusammenspiel von Test- und Programmcode
  • Ausnahmebehandlung
  • Ein unerwarteter Erfolg
  • Ein unerwarteter Fehlschlag
  • "Rückschritt für den Fortschritt" von Tammo Freese
  • Vorprogrammierte Schwierigkeiten
  • "Zwei offene Tests sind einer zu viel" von Tammo Freese
  • Kleine Schritte gehen
  • "Halten Sie Ihre Füße trocken" von Michael Feathers

Kapitel 5: Refactoring

  • Die zweite Direktive
  • Die Refactoringzüge
  • Von übel riechendem Code ...
  • ... über den Refactoringkatalog
  • ... zur Einfachen Form
  • Überlegungen zur Refactoringroute
  • Substitution einer Implementierung
  • Evolution einer Schnittstelle
  • "Coding Standards bewusst verletzen" von Tammo Freese
  • Teilen von Klassen
  • Verschieben von Tests
  • Abstraktion statt Duplikation
  • Die letzte Durchsicht
  • Ist Design tot?
  • "Durch zerbrochene Fenster dringen Gerüche ein" von Dave Thomas & Andy Hunt
  • Richtungswechsel ...
  • ... und der wegweisende Test
  • Fake it ('til you make it)
  • Vom Bekannten zum Unbekannten
  • Retrospektive
  • Tour de Design évolutionnaire
  • Durchbrüche erleben

Kapitel 6: Häufige Integration

  • Die dritte Direktive
  • Die Integrationszüge
  • Änderungen mehrmals täglich zusammenführen ...
  • "Taxi implements Throwable" von Olaf Kock
  • ... das System von Grund auf neu bauen
  • ... und ausliefern
  • Versionsverwaltung (mit CVS oder Subversion)
  • Build-Skript mit Ant
  • Build-Prozess-Tuning
  • Integrationsserver mit CruiseControl
  • Aufbau einer Staging-Umgebung
  • Teamübergreifende Integration
  • Gesund bleiben
  • "Eine Geschichte über die Häufige Integration" von Lasse Koskela

Kapitel 7: Testfälle schreiben, von A bis Z

  • Aufbau von Testfällen
  • Benennung von Testfällen
  • Buchführung auf dem Notizblock
  • Der erste Testfall
  • Der nächste Testfall
  • Erinnerungstests
  • Ergebnisse im Test festschreiben, nicht berechnen
  • Erst die Zusicherung schreiben
  • Features testen, nicht Methoden
  • Finden von Testfällen
  • Generierung von Testdaten
  • Implementierungsunabhängige Tests
  • Kostspielige Setups
  • Lange Assert-Ketten oder mehrere Testfälle?
  • Lerntests
  • Minimale Fixture!
  • "Einfache Tests - einfaches Design" von Dierk König
  • Negativtests
  • Organisation von Testfällen
  • Orthogonale Testfälle
  • Parameterisierbare Testfälle
  • Qualität der Testsuite
  • Refactoring von Testcode
  • Reihenfolgeunabhängigkeit der Tests
  • Selbsterklärende Testfälle
  • String-Parameter von Zusicherungen
  • Szenarientests
  • Testexemplare
  • Testsprachen
  • Umgang mit Defekten
  • "Eine Frage der (Test-)Kultur" von Christian Junghans & Olaf Kock
  • Umgang mit externem Code
  • Was wird getestet? Was nicht?
  • Zufälle und Zeitabhängigkeiten
  • "Der zeitlose Weg des Testens" von Lasse Koskela

Kapitel 8: Isoliertes Testen, durch Stubs und Mocks

  • Verflixte Abhängigkeiten!
  • Was ist die Unit im Unit Test?
  • Mikrointegrationstest versus strikter Unit Test
  • Vertrauenswürdige Komponenten
  • "Vertrauen - und Tests" von Bastiaan Harmsen
  • Austauschbarkeit von Objekten
  • Stub-Objekte
  • Größere Unabhängigkeit
  • Testen durch Indirektion
  • Stub-Variationen
  • Testen von Mittelsmännern
  • Self-Shunt
  • Testen von innen
  • Möglichst frühzeitiger Fehlschlag
  • Erwartungen entwickeln
  • Gebrauchsfertige Erwartungsklassen
  • Testen von Protokollen
  • Mock-Objekte
  • Wann verwende ich welches Testmuster?
  • "Wo Mock-Objekte herkommen" von Tim Mackinnon & Ivan Moore & Steve Freeman
  • Crashtest-Dummies
  • Dynamische Mocks mit EasyMock
  • Stubs via Record/Replay
  • Überspezifizierte Tests
  • Überstrapazierte Mocks
  • Systemgrenzen im Test
  • "Mock-Objekte machen glücklich" von Moritz Petersen

Kapitel 9: Entwicklung mit Mock-Objekten

  • Tell, don't ask
  • Von außen nach innen
  • Wer verifiziert wen?
  • Schnittstellen finden auf natürlichem Weg
  • Komponierte Methoden
  • Vom Mock lernen für die Implementierung
  • Viele schmale Schnittstellen
  • Kleine fokussierte Klassen
  • Tell und Ask unterscheiden
  • nereimmargorP sträwkcüR
  • Schüchterner Code und das Gesetz von Demeter
  • Fassaden und Mediatoren als Abstraktionsebene
  • Rekonstruktion
  • "Meister, ..." von Dierk König

Kapitel 10: Akzeptanztests mit FIT (Framework for Integrated Test)

  • Von einer ausführbaren Spezifikation ...
  • Download Now
  • Schritt für Schritt für Schritt
  • ... zum ausführbaren Anforderungsdokument
  • Die drei Basis-Fixtures
  • »ActionFixture«
  • Richtung peilen, Fortschritt erzielen
  • Fixture wachsen lassen, dann Struktur extrahieren
  • Nichts als Fassade
  • Die Fixture als zusätzlicher Klient
  • Aktion: Neue Aktion
  • »ColumnFixture«
  • Fixture-Interkommunikation
  • Negativbeispiele
  • Transformation: Action -> Column
  • »RowFixture«
  • Einfacher Schlüssel
  • Mehrfacher Schlüssel
  • Abfragemethoden einspannen
  • »Summary«
  • "Warum drei Arten von Fixtures?" von Ward Cunningham
  • »ExampleTests«
  • »AllFiles«
  • Setup- und Teardown-Fixtures
  • Das FIT-Framework von innen
  • »FileRunner«
  • »Parse«
  • »Fixture«
  • Annotationsmöglichkeiten in Dokumenten
  • »TypeAdapter«
  • »ScientificDouble«
  • Domänenspezifische Grammatiken
  • »ArrayAdapter«
  • »PrimitiveFixture«
  • Domänenspezifische Fixtures
  • Anschluss finden
  • Stichproben reiner Geschäftslogik
  • Integrationstests gegen Fassaden und Services
  • "Survival of the FIT Test" von Steffen Künzel & Tammo Freese
  • Oberflächentests
  • Kundenfreundliche Namen
  • FitNesse
  • FitLibrary
  • Akzeptanztesten aus Projektsicht

Kapitel 11: Änderbare Software

  • "Harte Prozesse führen zu harten Produkten" von Dierk König
  • Konstantes Entwicklungstempo
  • "Alten Code testgetrieben weiterentwickeln" von Juan Altmayer Pizzorno & Robert Wenner
  • "Die Latte liegt jetzt höher" von Michael Feathers
  • Kurze Zykluszeiten
  • Neue Geschäftsmodelle
  • "Bug-Trap-Linien" von Michael Hill

Behandelte Werkzeuge

Quellcode

Die Beispiele aus den Kapiteln 2, 3, 4, 5, 6, 7, 8 können Sie sich auch hier herunterladen.

26.8.2001

Extreme Programming

Extreme Programming (XP) ist ein agiler Softwareentwicklungsprozess für kleine Teams. Der Prozess ermöglicht, langlebige Software zu erstellen und während der Entwicklung auf vage und sich rasch ändernde Anforderungen zu reagieren. XP-Projekte schaffen ab Tag eins Geschäftswert für den Kunden und lassen sich fortlaufend und außergewöhnlich stark durch den Kunden steuern.

Im Kern beruht XP auf den Werten Kommunikation, Einfachheit, Feedback, Mut, Lernen, Qualität und Respekt. XP erfordert Disziplin und Prinzipientreue. Die beschriebenen XP-Techniken sind jedoch wirklich nur die Startlinie. Das Ziel ist es, den Entwicklungsprozess an die örtlichen Begebenheiten anzupassen und fortlaufend zu verbessern.

Techniken für ein XP-Team

Ein XP-Team besteht aus zwei bis etwa zwölf Programmierern, einem Kunden oder mehreren direkten Anprechpartnern auf Kundenseite und dem Management. Ferner erfordert der Prozess die Rollen des Trainers und Verfolgers. Der Trainer bespricht mit dem Team die diszipliniert einzuhaltenden Techniken und erinnert das Team, wenn es die selbstgewählten Regeln verletzt. Der Verfolger nimmt regelmässig den aktuellen Status und die geleisteten Programmieraufwände auf, um so zuverlässige Geschichtsdaten über das Projekt zu erhalten. Zu beachten ist, daß der Kunde in der Regel weder den Geldgeber noch den wirklichen Endanwender darstellt.

Offene Arbeitsumgebung

Das Team arbeitet zusammen in einem größeren Raum oder eng aneinander grenzenden Räumen. Typischerweise ist der "Kriegsraum" mit Wandtafeln und unzähligen Flipcharts ausgestattet. Die Arbeitstische stehen meist dicht beieinander im Kreis mit den Monitoren nach außen gerichtet und sind so gestaltet, daß zwei Programmierer zusammen bequem an einem Computer arbeiten können.

Kurze Iterationen

Die Entwicklung erfolgt in Perioden von ein bis drei Wochen. Am Ende jeder Iteration steht ein funktionsfähiges, getestetes System mit neuer, für den Kunden wertvoller Funktionalität.

Gemeinsame Sprache

Das Team entwickelt in seiner Arbeit ein gemeinsames Vokabular, um über die Arbeitsweisen und das zu erstellende System diskutieren zu können. Die Kommunikation im Team erfolgt stets offen und ehrlich.

Retrospektiven

Jede Iteration endet damit, in einem Rückblick über die eigenen Arbeitsweisen kritisch zu reflektieren und im Team zu diskutieren, was gut lief und was in Zukunft anders angegangen werden muß. Typischerweise werden aus den Dingen, die während dieser Team-Reviews zur Oberfläche kommen, Regeln generiert, vom Team akzeptiert, auf Poster geschrieben und im Projektraum zur Erinnerung an die Wand geheftet. Ein- oder zweimal jährlich macht das Team für zwei Tage einen gemeinsamen Ausflug, um in einem Offsite-Meeting formal vor- und zurückzublicken.

Tägliches Standup-Meeting

Der Tag beginnt mit einem Meeting, das im Stehen gehalten wird, damit es kurz und lebendig bleibt. Jedes Teammitglied berichtet reihum, an welcher Aufgabe er gestern gearbeitet hat und was er heute machen wird. Probleme werden genannt aber nicht gelöst. Die meisten Teams treffen sich vor der Wandtafel ihrer Iterationsplanung.

Techniken für die Kunden

Benutzergeschichten

Die Kunden halten ihre Anforderungen in Form einfacher Geschichten auf gewöhnlichen Karteikarten fest. Jeder geschriebenen Story-Karte kommt das Versprechen nach, den genauen Funktionsumfang zum rechten Zeitpunkt im Dialog mit den Programmierern zu verfeinern und zu verhandeln.

Iterationsplanung

Jede Iteration beginnt mit einem Planungsmeeting, in dem das Kundenteam seine Geschichten erzählt und mit dem Programmierteam diskutiert. Die Programmierer schätzen den Aufwand grob ab, den sie zur Entwicklung jeder einzelnen Geschichte benötigen werden. Die Kunden wählen in Abhängigkeit der Aufwandsschätzungen den Kartenumfang für die Iteration aus, der ihren Geschäftsgegenwert maximieren würde. Die Programmierer zerlegen die geplanten Geschichten am Flipchart in technische Aufgaben, übernehmen Verantwortung für einzelne Aufgaben und schätzen deren Aufwände vergleichend zu früher erledigten Aufgaben. Aufgrund der genaueren Schätzung der kleinen Aufgaben verpflichten sich die Programmierer auf genau soviele Geschichten, wie sie in der vorhergehenden Iteration entwickeln konnten. Diese Planungsspiele schaffen eine sichere Umgebung, in welcher geschäftliche und technische Verantwortung zuverlässig voneinander getrennt werden.

Anforderungsdefinition im Dialog

Das für die anstehenden Programmieraufgaben nötige Verständnis der Anforderungen wird fortlaufend in der Konversation mit den Kunden geprüft und vertieft. In kurzen Designsessions wird unter Umständen auf eine der Wandtafeln ein wenig UML gemalt oder es werden Szenarien mit Hilfe von CRC-Karten durchgespielt. Während der gesamten Entwicklung dienen die Kunden als direkte Ansprechpartner zur Bewältigung fachlicher Fragen. Die verbleibende Zeit verbringen die Kunden mit dem Schreiben und Ergründen neuer Benutzergeschichten und Akzeptanztests.

Akzeptanztests

Die Kunden spezifizieren während der Iteration funktionale Abnahmekriterien. Typischerweise entwickeln die Programmierer ein kleines Werkzeug, um diese Tests zu kodieren und automatisch auszuführen. Spätestens zum Ende der Iteration müssen die Tests erfüllt sein, um die gewünschte Funktion des Systems zu sichern.

Kurze Releasezyklen

Nach ein bis drei Monaten wird das System an die wirklichen Endanwender ausgeliefert, damit das Kundenteam wichtiges Feedback für die Weiterentwicklung erhält.

Techniken für die Entwicklung

Programmieren in Paaren

Die Programmierer arbeiten stets zu zweit am Code und diskutieren während der Entwicklung intensiv über Entwurfsalternativen. Sie wechseln sich minütlich an der Tastatur ab und rotieren stündlich ihre Programmierpartner. Das Ergebnis ist eine höhere Codequalität, grössere Produktivität und bessere Wissensverbreitung.

Gemeinsame Verantwortlichkeit

Der gesamte Code gehört dem Team. Jedes Paar soll jede Möglichkeit zur Codeverbesserung jederzeit wahrnehmen. Das ist kein Recht sondern eine Pflicht.

Erst Testen

Gewöhnlich wird jede Zeile Code durch einen Testfall motiviert, der zunächst fehlschlägt. Die Unit Tests werden gesammelt, gepflegt und nach jedem Kompilieren ausgeführt.

Design für heute

Jeder Testfall wird auf die einfachst denkbare Weise erfüllt. Es wird keine unnötig komplexe Funktionalität programmiert, die momentan nicht gefordert ist.

Refactoring

Das Design des Systems wird fortlaufend in kleinen, funktionserhaltenden Schritten verbessert. Finden zwei Programmierer Codeteile, die schwer verständlich sind oder unnötig kompliziert erscheinen, verbessern und vereinfachen sie den Code. Sie tun dies in disziplinierter Weise und führen nach jedem Schritt die Unit Tests aus, um keine bestehende Funktion zu zerstören.

Fortlaufende Integration

Das System wird mehrmals täglich durch einen automatisierten Build-Prozess neu gebaut. Der entwickelte Code wird in kleinen Inkrementen und spätestens am Ende des Tages in die Versionsverwaltung eingecheckt und ins bestehende System integriert. Die Unit Tests müssen zur erfolgreichen Integration zu 100% laufen.

Techniken für das Management

Akzeptierte Verantwortung

Das Management schreibt einem XP-Team niemals vor, was es zu tun hat. Stattdessen zeigt der Manager lediglich Probleme auf und läßt die Kunden und Programmierer selbst entscheiden, was zu tun gilt. Dies ist eine große, neue Herausforderung für das Management.

Information durch Metriken

Eine der Hauptaufgaben des Managements ist es, dem Team den Spiegel vorzuhalten und zu zeigen, wo es steht. Dazu gehört unter anderem das Erstellen einfacher Metriken, die den Fortschritt des Teams oder zu lösende Probleme aufzeigen. Es gehört auch dazu, den Teammitgliedern regelmässig in die Augen zu schauen und herauszufinden, wo Hilfe von Nöten ist.

Ausdauerndes Tempo

Softwareprojekte gleichen mehr einem Marathon als einem Sprint. Viele Teams werden immer langsamer bei dem Versuch, schneller zu entwickeln. Überstunden sind keine Lösung für zuviel Arbeit. Wenn Refactorings und Akzeptanztests aufgeschoben werden, muß der Manager dem Team stärker den Rücken freihalten. Wenn Teammitglieder müde und zerschlagen sind, muß der Manager sie nach Hause schicken.

Links

Bücher

Mailinglisten

User's Groups

19.2.2001

XP über die Schulter geschaut

Ein erster Einblick

Wie sieht ein Tag mit Extreme Programming aus? Nun, Sie werden wie gewohnt programmieren. Obwohl, nicht ganz. Sie programmieren mit einem Partner. Sie arbeiten zu zweit an einer kleinen überschaubaren Aufgabe. Die Zerlegung in Aufgaben haben Sie kurz zuvor im Team in einem kurzen Design-Meeting mit dem Kunden diskutiert und geplant. Sie haben die Verantwortlichkeit für eine Reihe von Aufgaben akzeptiert und suchen sich für deren kurze Dauer jeweils einen Programmierpartner.

Bevor Sie gemeinsam zu programmieren beginnen, analysieren Sie und Ihr Partner zunächst, was Ihr Auftraggeber überhaupt an Anforderungen stellt. Dazu möchten Sie unter Umständen direkt mit Ihrem Kunden sprechen. Er arbeitet wenige Meter von Ihnen beiden entfernt im gleichen Büro, damit Fragen des Entwicklungsteams unmittelbar diskutiert werden können.

Jetzt wissen Sie beide, was verlangt wird, und Sie können endlich losprogrammieren. Einen Augenblick. Bevor Sie losprogrammieren, überlegen Sie und Ihr Partner sich zunächst ein Design dafür, wie die Anforderungen überhaupt umgesetzt werden sollen. Dazu schreiben Sie eine Reihe von Testfällen. Anschliessend schreiben Sie den Code, der diese Tests erfüllt. Dabei streben Sie stets die einfachste Lösung an. Testfall für Testfall erhalten Sie grünes Licht und gehen weiter. Die Aufgabe gilt dann als erledigt, wenn Ihnen beiden keine weiteren Tests mehr einfallen, die sich für die aktuellen Anforderungen sinnvoll schreiben liessen.

Wenn alle Ihre Tests erfüllt sind, verlangt XP von Ihnen, den geschriebenen Code noch möglichst zu vereinfachen und sein Design zu verbessern. Der Code darf unter anderem keine duplizierte Logik enthalten und muß jede Intention ausdrücken, die für das Verständnis des Programms notwendig erscheint. Nachdem Sie den Code in Form gebracht haben, lassen Sie Ihre Arbeit durch die Suite aller im Team gesammelten Tests kontrollieren und integrieren schließlich Ihren geschriebenen Code mit den Änderungen Ihrer Teamkollegen. Anschliessend besorgen Sie sich zunächst einmal frischen Kaffee und helfen eventuell direkt Ihrem Partner bei seiner nächsten Programmieraufgabe.

Ein XP-Tag geht schnell vorüber, da Sie den ganzen Tag lang intensiv mit Ihren Kollegen programmiert haben. Das bedeutet nicht etwa, daß Sie Ihrem Partner bei der Arbeit über die Schulter schauen. Im Gegenteil. Im Paar zu programmieren bedeutet aufmerksam in die Programmierepisode involviert zu sein. Sie unterhalten sich über den Code, den sie gemeinsam schreiben. Regelmässig übergeben Sie Ihrem Partner die Tastatur und lassen ihn "fahren". Am Ende eines solchen Tages sind Sie erschöpft und, glauben Sie mir, Sie gehen pünktlich nach Hause, weil Sie acht Stunden lang fokussiert gearbeitet haben und die geschriebenen Tests Ihnen Vertrauen in die geleistete Arbeit geben.

Zugegeben, der Name "Extreme Programming" läßt einen extrem einseitigen Ansatz der Softwareentwicklung vermuten. Auf den zweiten Blick jedoch widmet sich XP sehr viel intensiver der Analyse, dem Design und dem Test als schwergewichtige Methoden. XP bringt uns zurück zu den Wurzeln des Handwerks guter Programmierung und der Fragestellung, was wirklich zählt, wenn wir hochqualitative Software erstellen wollen.

Ich möchte Sie dazu einladen, das Handwerk von XP und das Lebensgefühl eines leichtgewichtigen Prozesses kennenzulernen. Begleiten Sie mich auf den folgenden Seiten durch ein XP-Projekt und schauen Sie dem Team bei der täglichen Arbeit über die Schulter und in die Karten.

Programmieren in Paaren

Die Regel lautet: Wer um Hilfe bittet, dem wird Hilfe geboten. Tatsächlich wird keine Zeile Produktionscode geschrieben, ohne daß zwei Paar Augen auf den Bildschirm gerichtet sind. Das bedeutet, Sie programmieren zu zweit an einem Rechnern mit einer Tastatur und einer Maus. Sie sitzen nebeneinander, führen ein intensives Gespräch über den entstehenden Code und wechseln sich regelmässig an der Tastatur ab. Sie dürfen dabei sogar Spaß haben, denn Programmieren soll Spaß bringen. Sie wechseln häufiger Ihre Programmierpartner und am Ende der Woche haben Sie idealerweise mal mit jedem Ihrer Kollegen zusammengearbeitet, damit sich das aufgebaute Wissen über das gesamte Team verbreitet.

Felix und Ulrich, Dienstag, 1.Iteration, 11.03 Uhr

Ulrich: Was ist unsere Aufgabe?

Felix: Wir müssen die Miete für ausgeliehene DVDs berechnen.

Ulrich: Wie berechnen wir das?

Felix: Der Preis ist abhängig davon, wie lange eine DVD ausgeliehen wird.

Ulrich: Und wie?

Felix: Laß mich mal sehen... Auf unserer Karte steht, reguläre Filme kosten für zwei Tage 2 Euro. Ab dem dritten Ausleihtag dann 1.5 Euro pro Tag.

Ulrich: Okay, wie machen wir das also?

Felix: Mmm, ich schlage vor, wir merken uns zunächst mal, welche Filme ein Kunde ausleiht und berechnen später die Miete anhand der Ausleihtage.

Ulrich: Warum berechnen wir die Kosten nicht auf der Stelle?

Felix: Das ist später vielleicht notwendig, aber momentan ist das keine Anforderung.

Ulrich: Schön, wie soll unsere erste Klasse heissen?

Felix: Spannende Frage... Die Verantwortlichkeiten klingen fast danach, als würde sich die Klasse selbst Customer nennen wollen.

Ulrich: Fangen wir damit mal an.

Felix: Stop mal! Laß uns gleich einen Test dafür schreiben...

Ulrich: Gut, dazu muß unsere Testklasse von TestCase aus dem Test-Framework ableiten.

Felix: Und dann bekommt sie noch einen Konstruktor für den Namen des Testfalls.


public class CustomerTest extends junit.framework.TestCase {
  public CustomerTest(String name) {
    super(name);
  }
}

Testgetriebenes Programmieren

Die anzustrebende Geisteshaltung muß sein, daß eine Funktion solange nicht existiert, bis sie einen automatisierten Test besitzt. Tatsächlich entsteht kein Produktionscode, bevor es nicht einen entsprechenden Testfall gibt, der fehlschlägt. Das bedeutet, Sie schreiben einen Test noch bevor Sie den Code schreiben, der diesen Test erfüllt. Sie erstellen inkrementell eine umfassende Suite von Unit Tests, die das gesamte Programm in isolierten Einheiten auf die erwartete Funktion hin überprüft. Sie verwenden ein Test-Framework, das Ihnen dabei hilft, automatische Tests zu schreiben und auszuführen. Sie sammeln und pflegen diese Tests, damit Sie nach jeder Änderung sicherstellen können, daß die Testsuite zu 100% läuft.

Ulrich: Was ist der erste Testfall?

Felix: Das einfachste wäre, wir fangen mit dem Ausleihen eines Films an.

Ulrich: Und wie?

Felix: Es ist so, daß wir für jede Methode, die funktionieren soll, Zusicherungen schreiben wollen, um die Funktion abzuprüfen.

Ulrich: Dafür ist die assertTrue Methode gedacht, die wir aus TestCase erben.

Felix: Genau. Als Parameter erwartet sie eine Bedingung, die true ergeben muß, damit der Testfall als erfüllt gilt. Den Rest erledigt das Framework.

Ulrich: Und wohin schreiben wir nun den Testcode?

Felix: Am besten schreiben wir dafür eine Testfallmethode testRentingOneMovie, die dann die Mietkosten für den Film testet. Das Framework findet automatisch alle Methoden, die mit test beginnen, und führt sie aus.

Ulrich: Gut, schreiben wir mal auf, was wir bis jetzt wissen. Wir benötigen zunächst mal ein Customer Exemplar. Und dann muß ich so tun, als gäbe es einfach alle Methoden schon, die ich mir wünsche.

Felix: Richtig. Wir leihen eine DVD für einen Tag aus und es soll 2 Euro kosten.

Ulrich: Das ist einfach.


public class CustomerTest...
  public void testRentingOneMovie() {
    Customer customer = new Customer();
    customer.rentMovie(1);
    assertTrue(customer.getTotalCharge() == 2);
  }
}

Möglichst einfaches Design

Die Designstrategie sieht vor, mit einem schlichten Design zu starten und dieses fortlaufend zu verbessern. Tatsächlich werden Designelemente, die komplizierter sind, als momentan unbedingt notwendig wäre, aufgeschoben, selbst wenn nur für wenige Minuten. Das bedeutet, Sie wählen von vielen verschiedenen Lösungswegen denjenigen, der am einfachsten erscheint, um einen Testfall zu erfüllen. Sie programmieren nur, was Sie jetzt tatsächlich benötigen, nicht, was Sie später vielleicht benötigen. Sie gehen sogar soweit, daß Sie unnötige Flexibilität wieder aus dem Code entfernen. Sie treten den Beweis dafür an, daß die aktuelle Lösung zu einfach ist, indem Sie einen Testfall schreiben, der ein komplexeres Design rechtfertigt.

Ulrich: Also gut. Du willst, daß ich nur den Test zum Laufen bringe und alles andere für einen Moment vergesse.

Felix: Ganz genau. Was würdest Du tun, wenn Du nur diesen einen Test implementieren müsstest?

Ulrich: Hah, auch das ist einfach.


public class Customer {
  public void rentMovie(int daysRented) {
  }

  public int getTotalCharge() {
    return 2;
  }
}

Felix: Wie extrem! Aber gut...

Ein wenig Testen, ein wenig Programmieren...

Das Zusammenspiel von testgetriebenem Programmieren und einfachem Design ergibt den Zyklus des minutenweisen Programmierens. Tatsächlich wird nie länger als zehn Minuten programmiert, ohne die Feedbackschleife unmittelbar durch konkrete Tests zu schliessen. Das bedeutet, Sie schreiben neuen Code in so winzigen Schritten, daß Ihr Code gerade mal den aktuellen Testfall erfüllt. Sie testen ein wenig, Sie programmieren ein wenig. Dann testen Sie wieder und programmieren... Minute für Minute feiern Sie einen kleinen Erfolg. Sie schreiben keine ganze Klasse in einem Rutsch. Vielmehr schreiben Sie nur ein paar Zeilen Code, maximal eine Methode auf einmal.

Ulrich: Als nächstes möchte ich zwei und drei ausgeliehene DVDs testen.

Felix: Immer langsam... Schreib erst mal den Test für zwei Filme. Der Zweite wird für zwei Tage entliehen. Die Summe soll 4 Euro betragen. Laß uns dazu die assertEquals Methode benutzen. Als Parameter erhält sie den erwarteten Wert und das tatsächliche Resultat.


public class CustomerTest...
  public void testRentingTwoMovies() {
    Customer customer = new Customer();
    customer.rentMovie(1);
    customer.rentMovie(2);
    assertEquals(4, customer.getTotalCharge());
  }
}

Ulrich: Okay, dieser Test wird nicht laufen.

Felix: Woher weisst Du das? Schau nach, Du weisst nie!

Ulrich: Sagen wir einfach, ich bin mir ziemlich sicher.

Felix: Gut, wenn Du es also weisst und nehmen wir an, der Test zeigt grün, würde das demnach bedeuten, daß entweder unser Test falsch ist oder aber der Code Dinge tut, die er nicht machen darf, richtig? Mach den Test!

Ulrich: Also gut... Der Test zeigt rot.

Felix: Diesmal kommst Du auch nicht mehr so einfach davon...


public class Customer {
  private int totalCharge = 0;

  public void rentMovie(int daysRented) {
    totalCharge += 2;
  }

  public int getTotalCharge() {
    return totalCharge;
  }
}

Evolutionäres Design

Organisches Wachstum scheint eine gute Strategie zu sein, um auf Veränderung und Ungewissheit reagieren zu können. Tatsächlich werden Anforderungsänderungen als Chance und nicht als Problem betrachtet. Das bedeutet, Sie verhalten sich im Design so, als wüssten Sie wirklich nicht, was die nächsten Anforderungen sein würden. Sie entwerfen stets die einfachste Lösung und bringen Ihren Code anschliessend in die einfachste Form. Sie vertrauen auf die Tatsache, daß sauber strukturierter Code in jede Richtung mitziehen kann und daß der Code, den Sie gestern geschrieben haben, Sie heute und morgen dabei unterstützen wird, weiterhin Code zu schreiben, und Sie nicht zunehmend daran hindert.

Ulrich: Was ist der nächste Testfall?

Felix: Ein dritter Film, der drei Tage entliehen wird.

Ulrich: Wieviel kostet der Film, wenn er erst nach drei Tagen zurückgegeben wird?

Felix: Jeder weitere Tag kommt auf 1.5 Euro.

Ulrich: Also 3.5 Euro am Tag drei. Macht zusammen 7.5 Euro. Ausserdem können wir Fließkommazahlen nicht mit unendlicher Genauigkeit vergleichen.


public class CustomerTest...
  public void testRentingThreeMovies() {
    Customer customer = new Customer();
    customer.rentMovie(1);
    customer.rentMovie(2);
    customer.rentMovie(3);
    assertEquals(7.5, customer.getTotalCharge(), 1e-3);
  }
}

Felix: Ab jetzt müssen wir 1.5 Euro draufschlagen.

Ulrich: Das bedeutet auch, daß totalCharge ab sofort Fließkommazahl sein möchte.


public class Customer {
  private double totalCharge = 0;

  public void rentMovie(int daysRented) {
    totalCharge += 2;
    if (daysRented > 2) {
      totalCharge += 1.5;
    }
  }

  public double getTotalCharge() {
    return totalCharge;
  }
}

Felix: Nun meckert aber der Compiler rum... Wir müssen wohl auch unseren vorherigen Testfall zum Vergleich von Fließkommazahlen bringen.


public class CustomerTest...
  public void testRentingTwoMovies() {
    Customer customer = new Customer();
    customer.rentMovie(1);
    customer.rentMovie(2);
    assertEquals(4, customer.getTotalCharge(), 1e-3);
  }
}

Natürlicher Abschluß einer Programmierepisode

Bewußt aufhören zu können ist einer der stärksten Programmierzüge. Tatsächlich ist eine Aufgabe fertig, wenn alle Randbedingungen getestet sind, die dazu führen können, daß etwas schief geht. Das bedeutet, Sie schreiben nicht für jede Methode einen Test, sondern nur für solche, die unter Umständen fehlschlagen könnten. Sie halten Ihr Wissen über den Code fest, während Sie Tests dafür schreiben. Sie wissen, daß Sie erreicht haben, was Sie sich vorgenommen hatten, wenn alle Ihre Tests erfüllt sind. Zum Abschluß sei es Ihnen gegönnt, über Ihre Programmierepisode zu reflektieren. Sie wollen ja schließlich jeden Tag etwas dazulernen.

Ulrich: Ein Film, der vier Tage entliehen wird?

Felix: Kostet 5 Euro und macht dann insgesamt 12.5 Euro.


public class CustomerTest...
  public void testRentingFourMovies() {
    Customer customer = new Customer();
    customer.rentMovie(1);
    customer.rentMovie(2);
    customer.rentMovie(3);
    customer.rentMovie(4);
    assertEquals(12.5, customer.getTotalCharge(), 1e-3);
  }
}

Felix: Laß mich mal tippen!

Ulrich: Okay!


public class Customer...
  public void rentMovie(int daysRented) {
    totalCharge += 2;
    if (daysRented > 2) {
      totalCharge += (daysRented - 2) * 1.5;
    }
  }
}

Felix: So, das hätten wir. Haben wir irgendwelche Tests vergessen?

Ulrich: Müsste mit dem Teufel zugehen...

Refactoring

Gutes Design ist nie einfach und niemand bekommt die Dinge im ersten Versuch in den Griff. Tatsächlich entsteht ein Design durch schrittweises Wachstum und ständige Überarbeitung. Das bedeutet, daß Sie alle Erfahrungen in das Design zurückfliessen lassen und das Design verbessern, nachdem der Code geschrieben wurde. Sie refaktorisieren, um Ihren Code so einfach und so verständlich wie möglich zu machen und jede Art von Redundanz zu beseitigen.

Ulrich: Wenn ich mir unseren Code ansehe, frage ich mich, was die vielen Zahlen bedeuten. Wir sollten denen Namen geben!

Felix: Vorschläge?


public class Customer...
  static final double BASE_PRICE = 2;       // Euro
  static final double PRICE_PER_DAY = 1.5;  // Euro
  static final int DAYS_DISCOUNTED = 2;
}

Ulrich: Eigentlich finde ich die Änderung ja zu klein, um nach jedem Schritt zu testen...

Felix: Du hast Mut, aber wir haben ja noch die Tests...


public class Customer...
  public void rentMovie(int daysRented) {
    totalCharge += BASE_PRICE;
    if (daysRented > DAYS_DISCOUNTED) {
      totalCharge += (daysRented - DAYS_DISCOUNTED) * PRICE_PER_DAY;
    }
  }
}

Ulrich: Irgendwie passen die Konstantennamen nun aber doch nicht mehr zu der Klasse.

Felix: Stimmt, das hat jetzt schon sehr viel mit der eigentlichen Preisberechnung für den Film zu tun.

Ulrich: Ziehen wir eine neue Klasse Movie raus, um dem Ausdruck zu verleihen!?

Felix: Einverstanden, aber erst die Tests...


public class MovieTest extends junit.framework.TestCase {
  public MovieTest(String name) {
    super(name);
  }

  public void testGetCharge() {
    assertEquals(2.0, Movie.getCharge(1), 1e-3);
    assertEquals(2.0, Movie.getCharge(2), 1e-3);
    assertEquals(3.5, Movie.getCharge(3), 1e-3);
    assertEquals(5.0, Movie.getCharge(4), 1e-3);
  }
}

Felix: Die Preisberechnung verschieben wir einfach auf diese Klasse...


public class Movie {
  static final double BASE_PRICE = 2;       // Euro
  static final double PRICE_PER_DAY = 1.5;  // Euro
  static final int DAYS_DISCOUNTED = 2;

  public static double getCharge(int daysRented) {
    double result = BASE_PRICE;
    if (daysRented > DAYS_DISCOUNTED) {
      result += (daysRented - DAYS_DISCOUNTED) * PRICE_PER_DAY;
    }
    return result;
  }
}

Felix: Unser Customer wird dadurch wieder ganz schlank...


public class Customer...
  public void rentMovie(int daysRented) {
    totalCharge += Movie.getCharge(daysRented);
  }
}

Fortlaufende Integration

Neuer Code wird schnellstmöglich in die neueste Version integriert. Tatsächlich wird mehrmals täglich ein getesteter Build des gesamten Programms erstellt. Das bedeutet, daß Sie am Ende jeder Programmierepisode und wenigstens einmal am Tag Ihren geschriebenen Code in das Programm integrieren und versionieren. Sie laden Ihre Änderungen auf einen dedizierten Integrationsrechner, integrieren Ihren Code und beseitigen entstehende Konflikte. Sie führen die Tests aus und geben Ihren Code frei, sobald alle Tests erfolgreich ausgeführt werden. Die gesamte Integrationsprozedur dauert gerade so lange, daß Sie sich eine frische Tasse Kaffee kochen und sie austrinken können.

Felix: Laß uns unseren Code integrieren und Mittag machen!

Ulrich: Milchkaffee oder Chinese?

Nächster Artikel in dieser Serie: Unit Tests mit JUnit

Danksagungen

Leah Striker, Michael Schürig, Meike Budweg, Tammo Freese, Ulrike Jürgens, Hans Wegener, Marko Schulz, Antonín Andert, Manfred Lange und Julian Mack haben das Beispiel entstehen sehen und nützliche Verbesserungsvorschläge gemacht.

Der Ehrentitel eines Meisterrezensenten sei an Rolf F. Katzenberger verliehen.