Foundations for Professionals .NET Professionals im Profil guide to C# guidgen.de

Blog

Nutze den Augenblick
und teile der Welt mit, was Du zu sagen hast.

Privaten Code testen

Samstag, 7. August 2010, 00:40 Uhr
Permalink | Kommentare (9) | Kommentare als RSSRSS

Vor einigen Tagen habe ich den Blogeintrag Wie viel kosten Unittests? geschrieben, um zu zeigen, dass der Einsatz von Unittests nicht aufwändiger und teurer ist als der Verzicht auf diese. Mein Fazit lautete:

Da ich – bevor ich die Entwicklung einer Komponente beginne – auf Grund von EBCs und TDD ohnehin detailliert plane, wie deren API aussehen und wie sich diese API für einen Verwender anfühlen wird, erfordert die Planung häufig deutlich mehr Zeit als die eigentliche Implementierung.

Der Aufwand, die Implementierung im Sinne von TDD noch mit Unittests auszustatten, geht daher gegen Null.

Auf die Idee für diesen Blogeintrag haben mich der Blogeintrag TDD in der (meiner) Praxis – Wunsch und Wirklichkeit von Thomas Bandt und der Thread Tatsächlicher Nutzen von Unit-Tests? auf myCSharp.de gebracht, die beide ebendiesen Aspekt in Frage gestellt haben.

In besagtem Thread auf myCSharp.de hat sich zwischenzeitlich noch eine weitere interessante Diskussion ergeben: Die aktuell dort diskutierte Frage lautet, inwiefern privater Code mit eigenen Unittests ausgestattet werden sollte.

Hierzu empfiehlt es sich, zunächst die verschiedenen Varianten zu evaluieren, wie dies überhaupt geschehen kann – schließlich ist der Zugriff auf privaten Code von außen per se zunächst nicht möglich.

  • Reflection: Die einfachste Variante, privaten Code von außen zugreifbar zu machen, liegt darin, per Reflection auf ihn zuzugreifen. Dies ist auch die Variante, die Visual Studio von Haus aus im Rahmen der automatischen generierten Zugriffsklassen für Tests bietet. Die Nachteile liegen auf der Hand: Da der Zugriff mit Hilfe von Reflection naturgemäß nicht typisiert erfolgen kann, muss auf Magic Strings zurückgegriffen werden – die spätestens bei umfangreichen Refaktorisierungen für Probleme und Unannehmlichkeiten sorgen.
  • Vererbung: Als zweite Möglichkeit bietet sich an, den Zugriffsmodifizierer private durch den Zugriffsmodifizierer protected zu ersetzen – auf diese Art wird privater Code zumindest für abgeleitete Klassen zugreifbar. Da prinzipiell nichts dagegen spricht, auch eine Testklasse von der zu testenden Klasse abzuleiten, kann der Zugriff auf diese Art hergestellt werden. Doch private und protected weisen eine unterschiedliche Semantik auf und sind daher nicht bedingungslos gegeneinander austauschbar. Zudem funktioniert dieser Trick nicht bei versiegelten Klassen, von denen nicht abgeleitet werden kann.
  • InternalsVisibleTo: Als weitere Möglichkeit kann der Zugriffsmodifizierer private auch durch den Zugriffsmodifizierer internal ersetzt werden. Wird zusätzlich das Attribut InternalsVisibleTo verwendet, fühlt sich der Zugriff auf privaten Code aus Unittests de facto so an wie der Zugriff auf nicht-privaten Code. Allerdings trifft der Nachteil der zweiten Variante hier ebenfalls zu – sogar in gravierenderem Ausmaß, da internal noch freigiebiger ist als protected.
  • Zustand als Abhängigkeit: Die vierte Variante schließlich wurde von Ralf Westphal in seinem sehr lesenswerten Blogeintrag Zustand als Abhängigkeit – IoC konsequent gedacht beschrieben. Diese Variante ist deutlich aufwändiger, packt das Problem jedoch an der Wurzel an und stellt – zumindest in meinen Augen – die beste der hier vorgestellten vier Varianten dar. Allerdings verfügt sie neben dem hohen Aufwand noch über einen weiteren Nachteil: Sie erfordert die Einführung eines zusätzlichen, nur für Tests gedachten Konstruktors – die Tests verwenden also nicht mehr den gleichen Konstruktor, der auch im Produktivcode zur Anwendung kommt.

Alles in allem ist das Testen von privatem Code also nicht einfach und wirft etliche Probleme auf. In TDD gilt die Regel, dass schwer testbarer Code in der Regel eine Schwäche in seiner Architektur aufweist. Nimmt man beides zusammen, folgt aus dem Bedürfnis, privaten Code testen zu wollen, dass die zu Grunde liegende Architektur des zu testenden Codes eine Schwachstelle aufweist.

Bernd Hengelein schreibt dazu in seinem Blogeintrag TDD – ein Garant für heiße Diskussionen:

Wenn man den Drang verspürt eine private Methode testen zu wollen, ist das ein Code Smell der auf eine Umstrukturierung der Klasse hindeutet.

Auf stackoverflow.com wird in How do you unit test private methods? ähnlich argumentiert:

If you want to unit test a private method, something may be wrong. Unit tests are (generally speaking) meant to test the interface of a class, meaning its public (and protected) methods.

Auch Wikipedia geht in dem Abschnitt Eigenschaften von Modultests auf ähnliche Art auf die Frage ein:

Modultests sollen gemäß dem Design by contract Prinzip möglichst nicht die Interna einer Methode testen, sondern nur ihre externen Auswirkungen (Rückgabewerte, Ausgaben, Zustandsänderungen, Zusicherungen). Werden auch interne Details der Methode geprüft (dies wird als White-Box-Testing bezeichnet), droht der Test fragil zu werden, er könnte auch fehlschlagen, obwohl sich die externen Auswirkungen nicht geändert haben. Daher wird in der Regel das sogenannte Black-Box-Testing empfohlen, bei dem man sich auf das Prüfen der externen Auswirkungen beschränkt.

Das Buch Pragmatic Unit Testing äußert sich ebenfalls ähnlich:

In general, we don’t want to break any encapsulation for the sake of testing (or as Mom used to say, “Don’t expose your privates!”).

Kurzum: Die Fachwelt ist sich einig, dass man privaten Code nicht testen sollte. Doch warum ist dies so? Wie liegt die architektonische Schwachstelle, deren Existenz man aus der bloßen Schwierigkeit, einen Test zu formulieren, ableiten kann?

Die Schwachstelle liegt im Kontrakt der zu testenden Klasse. Jeder Code weist einen Kontrakt auf – implizit oder explizit als dedizierte Schnittstelle – der für die Verwender dieses Codes gedacht ist: Auf das, was mir öffentlich zugesichert wird, kann ich mich verlassen. Dies gilt dabei nicht nur im Hinblick auf die Syntax, sondern explizit auch im Hinblick auf die Semantik.

Alles, was nicht im Kontrakt enthalten ist, wird mir als Verwender auch nicht explizit zugesichert. Anders formuliert: Alles, was nicht im Kontrakt enthalten, sondern private ist, zählt zu den Interna – die mich als Verwender nichts angehen. Das ist das Prinzip einer Blackbox.

Was ist nun der Sinn eines Unittests? Der Unittest soll dem Entwickler einer Klasse garantieren, dass er diese derart refaktorisieren kann, dass er ihre innere Struktur ändert, ohne zugleich den nach außen zugesicherten Kontrakt zu ändern. Ein Unittest ermöglicht dem Entwickler, das Innere der Blackbox nach Belieben zu verändern, sofern ihre Funktionsweise nach außen erhalten bleibt.

Würde man nun beginnen, auch das innere der Blackbox mit Unittests abzusichern, so würde man dem Entwickler diese Freiheit nehmen und in seinem Revier wildern. Eine interne, durchaus sinnvolle, aber ausgesprochen große Refaktorisierung würde hiermit unmöglich gemacht – schließlich könnten potenziell alle Unittests, die auf das Innere der Blackbox abzielen, fehlschlagen.

Die Unittests müssten also angepasst werden – was aber der ursprünglichen Absicht widerspricht, mit den Unittests sicherzustellen, dass das Verhalten vor und nach der Refaktorisierung identisch ist. Kurzum – das Testen von privatem Code ist nicht nur umständlich, sondern geht auch noch an der eigentlichen Absicht der Tests vorbei.

Die einzig verbleibende Frage lautet nun noch, wie mit privatem Code umgegangen werden sollte – wenn er sich schon als ungeeignet für Unittests erweist?

Die Antwort auf diese Frage ist einfach: Zunächst einmal gilt, dass auch privater Code getestet wird – nur eben indirekt. Ruft eine öffentliche Methode privaten Code auf, so wird dieser implizit ebenfalls getestet – ist dies nicht der Fall, handelt es sich eventuell um Code, der gar nicht benötigt wird, oder um einen Hinweis darauf, dass noch nicht alle potenziellen Fälle per Test überprüft werden.

Für private Methoden gibt es jedoch noch einen weiteren Ansatz, wie mit ihnen umgegangen werden kann: Häufig enthalten private Methoden nämlich Code, der gar nichts mit dem eigentlichen Zweck der sie enthaltenden Klasse gemeinsam hat – streng genommen handelt es sich also sogar um eine Verletzung des Single Resposibility-Prinzips (SRP).

Die Lösung hierfür lautet: Auslagern des privaten Codes in eine eigene Klasse mit dedizierter Verantwortlichkeit. Diese weitere Einführung zusätzlicher Klassen oder Komponenten mit eigener Verantwortlichkeit führt damit nicht nur zu besserer Testbarkeit, sondern automatisch auch zu einer Verbesserung der Architektur – und kann in nahezu allen Fällen dieser Art angewandt werden.

Am Anfang mag sich dies ein wenig ungewohnt anfühlen – doch wenn man darüber nachdenkt, findet man beinahe in jedem Fall entsprechende Verantwortlichkeiten, die vermischt wurden. Diese aufzulösen, ist dann ein Leichtes.

Abschließend kann noch festgestellt werden, dass das Problem des Testens von privatem Code hauptsächlich Entwickler betrifft, die nicht nach TDD vorgehen. Dies ist jedoch nicht verwunderlich, denn TDD-affine Entwickler schreiben zuerst den Test und dann erst den Code: Privater Code kann hier also nur durch Refaktorisieren von bereits bestehendem und getestetem Code entstehen.

TDD sorgt also auch in diesem Fall für eine bessere Architektur – für mich persönlich, ein weiterer Grund, den mit TDD eingeschlagenen Weg weiter zu verfolgen.

Wie viel kosten Unittests?

Dienstag, 3. August 2010, 10:03 Uhr
Permalink | Kommentare (3) | Kommentare als RSSRSS

Vor rund zwei Wochen hat Thomas Bandt in seinem Blogeintrag TDD in der (meiner) Praxis – Wunsch und Wirklichkeit die Frage aufgeworfen, wie sehr Theorie und Praxis bei TDD auseinanderklaffen. Prinzipiell gesteht er TDD zunächst einen gewissen Charme zu:

Test Driven Development, wozu ich insbesondere „test first“ zähle, hat für mich einen gewissen Charme. Es ist zweifelsfrei so, dass dadurch die Architektur der eigenen Anwendung verbessert wird und viele kleine Fehler vermieden werden können.

TDD betrachtet er also prinzipiell – rein akademisch gesehen – als erstrebenswert. Doch dann äußert er seine Zweifel:

Die Frage ist nur: zu welchem Preis?

Er ist jedoch nicht der einzige, der sich mit dieser Frage beschäftigt: Hagen Siegel hat auf myCSharp.de den Thread Tatsächlicher Nutzen von Unit-Tests? begonnen. Die für ihn essenzielle Frage lautet:

Ich selbst habe Zeifel am Nutzen von Unit-Tests, wenn man den Aufand ins Verhältnis setzt. Führt der sachgemäße Einsatz von Unit-Tests tatsächlich zu qualitativ hochwertiger Software?

So wohl Thomas wie auch Hagen führen also den Preis beziehungsweise den Aufwand als Argument an und fragen sich, ob das Verhältnis zwischen diesem und dem Nutzen im richtigen Verhältnis steht.

Dieser Frage begegnet man in der Diskussion mit Entwicklern, die TDD noch nicht oder noch nicht durchgängig einsetzen, immer wieder. Reduziert auf ihre kürzeste Form lautet sie:

Wie viel kosten Unittests?

Häufig wird als Antwort impliziert, dass Unittests im Allgemeinen und TDD im Speziellen zu teuer seien, um es durchgängig und konsequent anzuwenden. Mich erinnert das an meine Argumentation vom vergangenen November, als auch ich mich gefragt habe: Wie viel Sinn machen Unittests?. Mein Fazit damals lautete:

Die Frage ist also nicht, ob eine 100%ige Testabdeckung wünschenswert ist – dies gilt rein von der Theorie her ohne jegliche Zweifel – sondern ob eine 100%ige Testabdeckung mit vernünftigem und vertretbaren Aufwand erreicht werden kann: Diese Frage kann durchaus mit Nein beantwortet werden.

Wie stehe ich heute zu dieser Antwort?

Für mein heutiges Empfinden sind all diese Fragen falsch gestellt. Die grundlegende Frage nach den Kosten sollte nämlich nicht

Wie viel kosten Unittests?

lauten, sondern vielmehr:

Wie viel kostet es, auf Unittests zu verzichten?

Ich will diese Frage an Hand eines aktuellen Beispiels beantworten. Derzeit arbeite ich an einem EBC-basierten Prototypen einer bestehenden Anwendung. Primär dient dieser Prototyp dazu, die Fähigkeiten von EBCs auszuloten – für mich ist es jedoch zusätzlich das erste Projekt, in dem ich konsequent und strikt nach 4-Step TDD vorgehe.

Gestern Abend habe ich bemerkt, dass ein Kontrakt nicht so entworfen war, wie er hätte sein sollen: Weder hat die Vererbungshierarchie noch hat die Terminologie gepasst. Da ich die Pfadfinderregel der Clean Code Developer-Initiative

Hinterlasse einen Ort immer in einem besseren Zustand als du ihn vorgefunden hast.

sehr schätze, habe ich beschlossen, den Kontrakt zu refaktorisieren. Da dieser Kontrakt eine sehr grundlegende Rolle in der Anwendung spielt und daher in vielen Projekten der Solution genutzt wird, hatte meine Refaktorisierung Auswirkungen auf nahezu die Hälfte der Projekte.

Nach rund 90 Minuten war meine Refaktorisierung so weit gediehen, dass der Compiler die Anwendung wieder übersetzen konnte. Früher, das heißt, in den Zeiten, in denen ich noch kein TDD angewendet habe, hätte ich die Anwendung nun vermutlich gestartet, um die wichtigsten Funktionen sicherheitshalber noch einmal zu testen: Funktioniert alles noch so, wie ursprünglich vorgesehen?

Dass Fehler bei einem derartigen Test potenziell unentdeckt bleiben, liegt auf der Hand – es ist schlicht und ergreifend nicht möglich, alle denkbaren Fälle durchzugehen. Dies ist weder zeitlich noch logistisch zu meistern, erst recht nicht nach jeder Änderung, Erweiterung oder Refaktorisierung.

Daher hätte ich wohl oder übel darauf hoffen müssen, dass die Anwendung noch so funktioniert wie vorher.

Vielleicht hätte sie das getan – gestern jedoch tat sie es nicht.

Die Ausführung aller Tests dauert derzeit knapp 45 Sekunden. Neun davon sind gestern Abend fehlgeschlagen. Ich wusste also nach weniger als 45 Sekunden, dass meine Anwendung nicht mehr so funktionierte wie vorher. Die Fehler zu finden, war leicht. Die Fehler zu beheben, war ebenfalls leicht. Nach rund 10 Minuten liefen alle Tests wieder erfolgreich durch.

Was bedeutet das nun?

Das Refaktorisieren hätte auch ohne Tests 45 Minuten gedauert, denn die Tests waren von der Refaktorisierung nicht betroffen. Doch ich wage zu bezweifeln, dass mich das Entdecken, Erkennen, Finden und Beheben der Fehler nur 10 Minuten gekostet hätte, wenn ich die Anwendung ohne Tests per Hand hätte überprüfen müssen – wenn ich die Fehler dann überhaupt entdeckt hätte.

Natürlich ist nicht garantiert, dass die Tests jeden denkbaren Fall abdecken – doch auf das, was getestet wird, kann ich mich verlassen. Jederzeit.

Was verbleibt, ist die zum ursprünglichen Schreiben der Tests benötigte Zeit. Diese fällt ausgesprochen gering aus, da ich jede EBC isoliert für sich testen kann und daher keine Zeit in aufwändiges Mocking stecken muss. In Welchen Nutzen bieten EBCs? habe ich diesen Vorteil zumindest implizit bereits thematisiert:

Damit gewinnt man Topologieunabhängigkeit, denn jede EBC ist von ihrer Umgebung entkoppelt und kann entweder für sich alleine oder transparent im Verbund mit anderen Komponenten genutzt werden. Dass sich die Funktionalität von EBCs damit perfekt per Unittest validieren lässt, liegt auf der Hand. Daher eignen sich EBCs ausgezeichnet auch für die Entwicklung mit 4-Step TDD.

Da ich – bevor ich die Entwicklung einer Komponente beginne – auf Grund von EBCs und TDD ohnehin detailliert plane, wie deren API aussehen und wie sich diese API für einen Verwender anfühlen wird, erfordert die Planung häufig deutlich mehr Zeit als die eigentliche Implementierung.

Der Aufwand, die Implementierung im Sinne von TDD noch mit Unittests auszustatten, geht daher gegen Null.

Erster Eindruck von Event-Based Components

Mittwoch, 28. Juli 2010, 15:47 Uhr
Permalink | Kommentare (0) | Kommentare als RSSRSS

Vor ungefähr fünf Monaten hat Ralf Westphal in seinem Blog die Frage aufgeworfen, ob nachrichtenorientierte Komponenten den nächsten Schritt der Komponentenorientierung darstellen.

Als entsprechendes Architekturmodell hat die sogenannten Event-Based Components (EBC) vorgestellt: Ein einfaches Komponentenmodell mit einigen kompakten Regeln, dessen wesentliches Merkmal des Einsatz von Events als Kommunikationsmedium an Stelle klassischer Methodenaufrufe ist.

Im Prinzip ähnelt das Konzept der EBCs dem von Chips und Platinen: Ähnlich wie diese können auch EBCs zusammengesteckt werden und größere Komponenten bilden. Der Vorteil des Ganzen liegt in einer ausgesprochen geringen Kopplung – effektiv sind die einzelnen EBCs nicht mehr voneinander, sondern nur noch von gemeinsamen Typen abhängig.

Die funktionale Abhängigkeit ist aufgelöst. Die Vorteile liegen auf der Hand: EBCs sind einfacher zu instanziieren, einfach zu komponieren, einfacher zu isolieren und daher auch einfacher zu testen.

So weit, so gut. Die Frage ist, inwieweit dieses theoretisch interessante Konzept in der Praxis funktioniert.

In den vergangenen Tagen hatte ich die Gelegenheit, einen EBC-basierten Prototypen einer bestehenden Anwendung zu erstellen: Die zu erzielende Funktionalität war also vorgegeben, die einzige Aufgabe war also tatsächlich, das Konzept der EBCs in die Praxis umzusetzen.

Inzwischen ist dieser Prototyp so weit gediehen, dass er die ersten funktionalen Anforderungen der ursprünglichen Anwendung erfüllt. Zeit, im Rahmen einer kleinen Retrospektive zurückzublicken und ein erstes Fazit zu ziehen. Die aus meiner Sicht zwei wesentlichsten Aspekte sind:

  • EBCs sind effizient: EBCs ersparen einem die Mühe, eine aufwändige Architektur zu entwickeln – sie implizieren eine kompakte Form. Diese kann in technischer Hinsicht auch leicht umgesetzt werden – auch oder erst recht mit TDD. Die geringe Kopplung wirkt sich hierfür enorm positiv aus. Die Erweiterbarkeit des gesamten Systems ist beeindruckend, und auch die Wart- und Evolvierbarkeit des Codes können sich sehen lassen. Noch mehr als von TDD alleine wird man durch den Einsatz von EBCs mit TDD gezwungen, sich zuvor fundierte Gedanken über das Design der Komponenten zu machen.
  • EBCs sind ungewohnt: Der größte Makel von EBCs ist – für mich persönlich – derzeit ihre Andersartigkeit. Sie fühlen sich schlicht und ergreifend ungewohnt an, denn die Kommunikation zwischen den Komponenten folgt nicht mehr dem klassischen und zur Genüge gewohnten Aktion-Reaktion-Schema. Statt dessen modellieren EBCs einen Fluss von Daten durch die Anwendung. Die größte Herausforderung in den vergangenen Tagen war, gedanklich immer wieder zu diesem Fluss-basierten Modell zurückzukehren und sich zu überlegen, wie Funktionalität als Fluss implementiert werden kann.

Natürlich ist der zweite Punkt reine Gewohnheits- und Übungssache. Je öfter man EBCs modelliert, desto leichter gelingt dies. Der Einstieg in EBCs ähnelt daher dem Einstieg in TDD in gewissem Sinne – es ist gar nicht so sehr das rein technische Vorgehen, das kann kompakt und übersichtlich veranschaulicht werden, sondern es sind das fehlende Gefühl für EBCs und die fehlenden Best Practices.

Während das Gefühl nur jeder für sich entwickeln kann, indem er sich mit EBCs beschäftigt und die Arbeit mit ihnen ausprobiert, können Best Practices vermittelt werden – falls diese bereits bekannt sind. Hierzu ist es notwendig, einen Konsens oder zumindest verschiedene, fundierte und argumentativ belegte Meinungen zu finden.

Ralf hat zu EBCs seine Sicht der Dinge – ich habe meine. Aus diesem Grund werde ich in den kommenden Tagen viel über meine Erfahrungen und Erkenntnisse bezüglich EBCs schreiben.

Wenn ich eine andere Meinung vertrete oder EBCs anders nutze als Ralf, werde ich dies begründen – nicht, um Ralfs Position zu schwächen, sondern schlichtweg, um einen zweiten Standpunkt darzulegen.

Vielleicht ergeben sich in der Synergie aus Ralfs Sichtweise, meiner Sichtweise und der Sichtweise von allen anderen Verwendern von EBCs ja Best Practices, die es wert sind, weitergetragen zu werden.

EBCs an sich sind dies allemal wert – sie sind ein großartiges Konzept, das mir sehr gut gefällt. Der langfristige Nutzen wird sich noch beweisen müssen, aber so weit ich EBCs bislang beurteilen kann, sind sie definitiv ein wichtiger Schritt in die richtige Richtung für die moderne Softwareentwicklung.

Agile Day an der Universität Augsburg

Freitag, 23. Juli 2010, 09:24 Uhr
Permalink | Kommentare (0) | Kommentare als RSSRSS

Am 8. Juli 2010 fand in der Universität Augsburg der erste Agile Day statt, zu dem ich als Referent eingeladen war.

Nach einer allgemeinen Einführung wurde zunächst Scrum vorgestellt, wobei der Praxis ein sehr großer Stellenwert eingeräumt wurde: Die ungefähr 20 Teilnehmer erhielten auf diese Art die Gelegenheit, diverse Schritte selbst durchzuführen.

Mir persönlich fällt bei Scrum immer wieder auf, dass die Schere zwischen Theorie und Praxis extrem auseinanderklafft: Einerseits hat man Scrum durchaus in fünf Minuten auf einer Serviette erklärt. Andererseits stößt man während der praktischen Umsetzung auf dermaßen viele Probleme, dass man gut beraten ist, jemanden mit Scrum-Erfahrung in verfügbarer Nähe zu haben.

Im Anschluss an diese sehr dynamische und interaktive – und auch sehr gut gemachte – Einführung in Scrum habe ich in meiner Session einen kurzen Überblick zu Unittests im Allgemeinen und Test-Driven Development (TDD) im Speziellen gegeben.

Auch ich habe neben der Theorie großen Wert auf die Praxis gelegt, weshalb ich den Teilnehmern in einer Art Dojo die Aufgabe gestellt habe, die Kata FizzBuzz zu lösen – auch wenn wir dies in der gegebenen Zeit nicht geschafft haben, war der Lerneffekt im Hinblick auf TDD meines Erachtens sehr groß.

Zum Abschluss des Agile Days wurde eine sogenannte Extreme Hour angeboten, in der eine vorgegebene Aufgabe mit den Praktiken von Extreme Programming (XP) erledigt werden sollte – inklusive kurzer Iterationen und ähnlichem.

Durch das große Interesse, die vielen Fragen und die ungewöhnlich hohe Bereitschaft der Teilnehmer, aktiv mitzumachen, war nicht nur die Extreme Hour, sondern auch der gesamte Agile Day ein großer Erfolg, der – nicht nur mir – sehr viel Spaß gemacht hat.

Der nächste Agile Day wird am 29. Oktober 2010 stattfinden.

Vier Schritte in TDD

Mittwoch, 21. Juli 2010, 17:52 Uhr
Permalink | Kommentare (7) | Kommentare als RSSRSS

Den Teilnehmern des dotnetpro.powerdays C# für Profis wurde im Anschluss an die eigentliche Veranstaltung die Teilnahme an einem .NET Coding Dojo angeboten, das von Ilker Cetinkaya durchgeführt wurde.

Im Rahmen dieses Dojos wurde die Frage diskutiert, wie der übliche TDD-Cycle Red – Green – Refactor auszulegen sei. Stein des Anstoßes war, dass einige Teilnehmer den Schritt Refactor als optional angesehen haben und ihn daher im konkreten Fall außen vor lassen wollten – was bei anderen Teilnehmern wiederum für Unmut sorgte.

Ich selbst habe die Meinung vertreten, dass der Schritt Refactoring keineswegs optional sei, denn stellt man diesen in Frage, öffnet man seinem gänzlichen Ausschluss Tür und Tor – dem verführerischen Weg zur Brownfield-Anwendung wird damit Vortrieb geleistet.

Einige Teilnehmer des Dojos – allen voran Stefan Lieser – haben argumentiert, dass ein Refactoring am Ende des TDD-Cycles keinen Sinn machen würde – sondern dass es vielmehr an den Beginn dieses Zyklusses verschoben werden sollte, schließlich sei Refactoring auf ein Ziel und somit auf einen Plan angewiesen – sonst wisse man nicht, in welche Richtung das Refactoring zu treiben sei.

Stefan hat dies in seinen Blogeinträgen Refaktorisieren im TDD Prozess und TDD 2.0 dargelegt – allerdings bin ich davon nicht überzeugt. Ein Refactoring zu Beginn des TDD-Cycles macht keinen Sinn: Erstens aus dem bereits genannten Grund, der Verschmutzung von Code Vortrieb zu leisten, zweitens weil ich direkt zu Beginn eines Projektes kein Refactoring betreiben kann.

Auch wenn es sich hierbei nur um eine einzige Ausnahme handelt, in der die von ihm vorgeschlagene Regel nicht greift – es ist eine Ausnahme, was ich persönlich nicht schön finde.

Für mein Empfinden gibt es zwei Arten von Refactorings: Einerseits kleine, kosmetische Korrekturen, andererseits grundlegende, Architektur-betreffende Änderungen. Stefan beschreibt diese Aufteilung ebenfalls – zieht für mein Empfinden aber die falschen Schlüsse.

Natürlich ist es – wenn überhaupt möglich – schwierig, grundlegende, Architektur-betreffende Änderungen durchzuführen, wenn man noch nicht weiß, in welche Richtung die Software weiterentwickelt werden soll.

Diese Art von Änderungen ist jedoch gar nicht das, was in der Regel von Refactoring betroffen ist: Ob ich Code in eine private Methode auslagere, um DRY umzusetzen, ist unabhängig von der Architektur. Ebenso, ob ich ein Literal in eine Konstante auslagere oder nicht.

Diese Art von Refactorings macht den bedeutend größeren Anteil aus – es wäre auch tragisch, wenn Entwickler und Architekten nach jedem Test beständig damit beschäftigt wären, die Architektur grundlegend anzupassen.

Es liegt auf der Hand, dass diese Art von kosmetischen Korrekturen direkt nach dem Schritt Green ausgeführt werden sollten – ganz so, wie TDD es in seiner Urform vorschreibt: Red – Green – Refactor.

Die Architektur-bezogenen Änderungen werden in diesem Schema aus drei Schritten also gar nicht berücksichtigt: Die logische Konsequenz daraus ist die Einführung eines vierten Schrittes, der getrennt von Refactor stattfinden kann.

Da dieser vierte Schritt wesentliche Änderungen behandelt, die das architektonische Grundgerüst des Codes betreffen, nenne ich diesen Schritt Rearrange – schließlich werden die einzelnen Komponenten und deren Verdrahtung zueinander in diesem Schritt neu arrangiert und komponiert.

Dieser vierte Schritt ist – naturgegeben – erst dann sinnvoll, wenn bekannt ist, in welche Richtung die Entwicklung laufen soll. Ihn aber direkt in den TDD-Cycle aufzunehmen, halte ich für unglücklich – schließlich wird er nicht nach jedem Durchlauf angewendet. Daher plädiere ich für zwei Zyklen, die einander enthalten:

TDD = Red – Green – Refactor

als klassischer TDD-Cycle für kosmetische Korrekturen und

TDD erweitert = TDD – TDD – … – TDD – Rearrange

als erweiterter TDD-Cycle für architektur-bezogene Anpassungen, Änderungen und Erweiterungen.

Den Schritt Rearrange habe ich bewusst am Ende des erweiterten TDD-Cycles untergebracht, denn vor der ersten Ausführung von TDD ergibt die Durchführung von Rearrange keinen Sinn – und da eine Software nie “fertig” ist und es daher stets einen weiteren TDD-Cycle geben wird, habe ich keinerlei Bedenken, Rearrange an die letzte Position zu schieben.

Der erweiterte TDD-Cycle hat in diesem Sinne nämlich keinen Anfang und kein Ende mehr, sondern stellt eher einen Fluss dar – welche Vorbedingungen für den jeweils nächsten Schritt erfüllt sein müssen, darüber wird keine explizite Aussage gemacht. Implizit jedoch erfordert Rearrange Planung, womit auch Stefans Wunsch nach mehr Planung berücksichtigt wäre.

Freiberuflicher Wissensvermittler und Technologieberater

Samstag, 3. April 2010, 20:24 Uhr
Permalink | Kommentare (4) | Kommentare als RSSRSS

Am 1. Juni 2010 wird es so weit sein: Ich mache den Schritt in die endgültige und vollständige Selbständigkeit. Ab diesem Tag werde ich auf freiberuflicher Basis als Wissensvermittler und Technologieberater für .NET, Codequalität und agile Methoden arbeiten.

Im Rahmen von .NET habe ich mich dabei auf die Sprache C# und die Architektur von Webanwendungen spezialisiert. Meine Auszeichnung als Microsoft Most Valuable Professional (MVP) für C# am 1. April 2010 wie auch meine zweifache Zertifizierung als Microsoft Certified Professional (MCP), unter anderem für die Entwicklung .NET-basierter Webanwendungen, unterstützen diese Schwerpunkte.

Besonderes Augenmerk richte ich bei der Entwicklung einer Webanwendung auf eine auch in der Zukunft tragfähige Architektur wie auch auf die Erreichung einer sehr guten Wartbarkeit und Evolvierbarkeit des Codes. Zu diesem Zweck lege ich besonderen Wert auf hohe Codequalität, von der Einhaltung etablierter Coderichtlinien über testgetriebene Entwicklung bis hin zu Codeanalyse und -reviews.

Dabei unterstützen mich mein akkurates, präzises und reflektiertes Vorgehen wie auch ausgeprägte analytische Fähigkeiten: Ich plane, entwerfe und entwickle mit Liebe zum Detail und achte stets auch auf Feinheiten. Als bekennender Clean Code Developer trete ich zudem für Professionalität in der Softwareentwicklung ein.

Meine Arbeitsweise richtet sich dabei nach den agilen Werten, die durch das Agile Manifest definiert werden, zu dessen Unterzeichnern ich gehöre. Inbesondere schätze ich Extreme Programming (XP) und Scrum als einander ergänzende agile Methoden zur Durchführung von Projekten.

Zu all diesen Themen vermittle ich Wissen und berate Firmen, die auf Basis von .NET Softwareentwicklung durchführen, bei der Evaluierung, Erforschung und Verwendung geeigneter Technologien und Methoden, wobei mir meine langjährige Erfahrung als Softwarearchitekt und Technologieberater wie auch als Autor, Referent und Trainer zu Gute kommt.

Darüber hinaus werde ich auch zukünftig journalistisch für Fachzeitschriften wie auch als Referent und Content Manager für Konferenzen tätig sein. .NET-affinen Usergroups stehe ich als INETA-registrierter Referent ebenfalls zur Verfügung. Weiterhin werde ich außerdem Webcasts für die deutschsprachige MSDN erstellen.

Wenn Sie meine Unterstützung bezüglich Beratung oder Training zu den Themen .NET, Codequalität und agilen Methoden anfordern möchten, können Sie mich gerne jederzeit per E-Mail oder telefonisch kontaktieren. Ich freue mich auf Ihre Anfrage!

Unit- vs Integrationtests

Freitag, 2. April 2010, 12:53 Uhr
Permalink | Kommentare (5) | Kommentare als RSSRSS

Warum der Einsatz von Unittests generell Sinn ergibt, habe ich in vor einigen Tagen in meinem Blogeintrag Vom Saulus zum Paulus beschrieben. Doch was ist überhaupt ein Unittest? Wie grenzt er sich von anderen Arten von Tests ab?

Häufig werden insbesondere Unit- und Integrationtests vermischt – doch um überhaupt eine Trennung erreichen zu können, ist zunächst eine Klärung des Begriffs Unit nötig, ist sie schließlich das individuelle Merkmal eines Unittests. Es gilt also, zwei Fragen zu beantworten:

  • Wie wird der Begriff Unit definiert?
  • Welcher Unterschied besteht zwischen Unit- und Integrationtests?

Einen ersten Anhaltspunkt liefert die deutschsprachige Wikipedia auf ihrer Seite zum Thema Unittest, werden Unittests dort nämlich als

Modultest[s] (auch Komponententest[s])

bezeichnet. Ein Unittest testet also eine Komponente. Dieser Begriff hat nicht zuletzt dank der zunehmenden Verbreitung des Entwurfsmusters Inversion of Control und entsprechenden Dependency Injection-Containern wie beispielsweise LightCore eine gewisse Verbreitung erfahren.

Eine Komponente wird in diesem Zusammenhang in der Regel als ein Softwarepart definiert, der folgende Anforderungen erfüllt:

  • Unabhängig: Eine Komponente ist unabhängig von einem konkreten Projekt, sondern kann flexibel in verschiedenen Projekten genutzt und nach Bedarf hinzugefügt werden. Komponenten dürfen dabei natürlich Abhängigkeiten auf andere Komponenten aufweisen, wobei diese jedoch die gleichen Anforderungen erfüllen müssen.
  • Kontrakt: Eine Komponente verfügt über einen wohldefinierten Kontrakt, der neben der Syntax auch eine gewisse Semantik vorgibt. Der Verwender einer Komponente kennt nur deren Kontrakt, weshalb Komponenten gegen andere Komponenten austauschbar sind, sofern von beiden der gleiche Kontrakt erfüllt wird.
  • Blackbox: Eine Komponente fungiert als Blackbox, das heißt, die Interna – auf welche Art ist welche Funktionalität implementiert – spielen für den Verwender keine Rolle. Da der Verwender eine Komponente nur auf Basis ihres Kontrakts verwendet, interessieren ihn diese Interna in der Regel auch nicht.

Unittests sind zunächst lediglich ein weiterer Verwender von Komponenten. Das heißt, dass die Unittests die an eine Komponente gestellten Anforderungen respektieren und keinen Weg suchen, diese zu umgehen.

Insbesondere bedeutet das, dass ein Unittest nur den Teil einer Komponente testet, der über ihren Kontrakt öffentlich erreichbar ist. Konkrete Implementierung ist gemäß dem Blackbox-Prinzip von Komponenten nicht nach außen – also auch nicht für Unittests – sichtbar.

Außerdem muss ein Unittest eine Komponente unabhängig von anderen Komponenten testen – ansonsten könnten Fehler nicht nur von der Komponente an sich, sondern auch von der Interaktion zwischen verschiedenen Komponenten verursacht werden. Stubs und Mocks sind aus diesem Grund essenziell für die Entwicklung von Unittests.

Zusammengefasst garantiert ein Unittest also, dass eine Komponente ihrem Kontrakt in syntaktischer und semantischer Hinsicht entspricht, Ein Integrationtest hingegen testet das Zusammenspiel verschiedener Komponenten, wobei implizit von der Korrektheit der einzelnen Komponenten ausgegangen wird.

Wann immer also eine Komponente nicht isoliert, sondern im Verbund mit anderen Komponenten getestet wird, handelt es sich bei einem Test um einen Integration- und nicht mehr um einen Unittest. Daraus ergibt sich, dass sich Unit- und Integrationtests ergänzen, aber sauber getrennt werden sollten – schließlich handelt es sich auch um verschiedene Arten von Tests.

Vom Saulus zum Paulus

Mittwoch, 31. März 2010, 19:39 Uhr
Permalink | Kommentare (2) | Kommentare als RSSRSS

Am 1. November 2009 habe ich den Blogeintrag Wie viel Sinn machen Unittests? verfasst, in dem ich mich skeptisch gegenüber dem Einsatz von Unittests geäußert habe – und empörte Reaktionen geerntet habe.

Meine Skepsis gründete sich im Wesentlichen auf drei Kritikpunkte, die im folgenden noch einmal aufgelistet werden:

  • Der bestehende objektorientierte Aufbau ist unter OOP- und stilistischen Aspekten sauber umgesetzt, läuft einer guten Testbarkeit allerdings zuwider.
  • Es bestehen zahlreiche Abhängigkeiten zu externen Komponenten, die sich nicht oder nur mit sehr viel Aufwand simulieren lassen.
  • Es bestehen Abhängigkeiten zur konkreten Laufzeitumgebung, die sich nicht oder nur mit sehr viel Aufwand nachbilden lassen.

Wie sich inzwischen herausgestellt hat, können alle Aspekte zufriedenstellend gelöst werden. Letztlich läuft es im Zweifelsfall in der Regel darauf hinaus, eine zusätzliche Abstraktion einzuführen, um besser entkoppeln zu können – schließlich ermöglicht erst ein gewisses Abstraktionsniveau den sinnvollen Einsatz von Stubs und Mocks.

Doch auch wer – wie ich – helfende Hände gereicht bekommt und es daraufhin geschafft hat, alle technischen Hindernisse aus dem Weg zu räumen, mag noch über den Sinn von Unittests grübeln: Die Frage lautet also nicht, wie viel, sondern warum der Einsatz von Unittests überhaupt Sinn ergibt?

Auf diese Frage möchte ich heute eingehen. Da die Entwicklung einer Anwendung irgendwann bei Null auf der grünen Wiese beginnt, liegt es nahe, sich zunächst über den Einsatz von Unittests bei neu zu schreibendem Code Gedanken zu machen: Hier wirken Unittests häufig als Bremse – schließlich müssen neben dem eigentlichen produktiven Code noch zahlreiche Unittests geschrieben werden.

Wer dieser Aussage in dieser Form ohne Widerspruch zustimmt, tappt in die gleiche Falle wie ich vor einigen Monaten: Er vergisst, dass dieser neu geschriebene produktive Code gründlich getestet werden muss – wenn nicht vom Entwickler, dann hoffentlich von irgendjemandem.

Hier liegt der Hund bereits begraben: Wenn nicht der Entwickler selbst sich um das Testen kümmert, muss es jemand anderes machen – der nicht weiß, was sich der Entwickler im Detail gedacht hat, warum manche Entscheidungen so und nicht anders getroffen wurden, der auch nicht weiß, welche potenziellen Sonderfälle berücksichtigt werden müssen.

Zudem weiß dieser jemand auch nicht, ob er alle potenziellen Codepfade getestet hat – er kennt schließlich die Interna nicht. Er kann nur hoffen, alles abgedeckt zu haben, weiß es aber nie.

Schließlich werden Tests, die erst zu einem relativ späten Zeitpunkt durchgeführt werden, in der Regel nicht automatisiert durchgeführt: Die Durchführung hängt also von der Tagesform des Testers ab und kann – unbeabsichtigt – schwanken und eventuell bei zwei Testläufen in verschiedenen Ergebnisse resultieren.

Kurzum: Die Qualität der Tests ist nicht so hoch, wie sie sein könnte. Die Quantität stimmt gegebenenfalls nicht. Und die Ergebnisse sind weder reproduzierbar noch verlässlich.

Wird hingegen von Anfang an mit Unittests entwickelt, ergeben sich all diese Probleme erst gar nicht: Der Entwickler weiß, was zu testen ist, er kennt die Spezialfälle, er weiß oder kann zumindest herausfinden, wie hoch die Testabdeckung ist, und die Testergebnisse sind – da die Unittests automatisiert ausgeführt werden – jederzeit reproduzierbar.

Außerdem entsteht zwar zusätzlicher Aufwand für das Schreiben der Unittests, aber die Ausführung erfordert kaum noch Aufwand. Da Unittests nur einmal geschrieben, aber immer und immer wieder ausgeführt werden, ergibt sich mittelfristig eine deutliche Zeitersparnis – bei qualitativ besseren Ergebnissen.

Aber es kommt noch besser: Da zumindest bei der testgetriebenen Entwicklung gilt, dass immer nur gerade so viel Code geschrieben werden sollte, wie notwendig ist, um einen Unittest zu erfüllen, wird zum einen nur Code geschrieben, der auch getestet ist, zum anderen wird aber auch nur jener Code geschrieben, der tatsächlich erforderlich ist. Das heißt, der Einsatz von testgetriebener Entwicklung hilft, das YAGNI-Prinzip umzusetzen.

Nun kommt aber in jedem Projekt irgendwann der Zeitpunkt, an dem nicht mehr nur neuer Code hinzugefügt, sondern ab dem auch bestehender Code geändert werden muss. Sei es, weil sich Anforderungen geändert haben oder weil Fehler behoben werden müssen.

In jedem Fall muss bestehender Code geändert werden. Typisch für gewachsene Projekte ist, dass die ursprünglichen Entwickler gar nicht mehr verfügbar sind, oder sich nicht mehr genau an spezielle Implementierungsdetails erinnern können. Da zudem in den meisten Softwareprodukten innere Abhängigkeiten bestehen, findet eine Änderung von Code selten isoliert statt, sondern beeinflusst anderen Code ebenfalls – mal mehr, mal weniger.

Das Problem liegt dann meistens darin, dass Änderungen, die im guten Glauben auf Korrektheit durchgeführt werden, Seiteneffekte haben, die ihrerseits neue Fehler produzieren. Irgendwann werden aus diesem Grund keine Änderungen mehr durchgeführt, sondern vermehrt Workarounds geschrieben – und irgendwann dann Workarounds um die Workarounds, und wieder später Workarounds um die Workarounds um die Workarounds, und so weiter …

Was also fehlt, ist ein Sicherheitsnetz, das den Entwickler beim Auftreten von unerwünschten Seiteneffekten auffängt und ihn darauf hinweist, dass seine Änderung an anderer Stelle ungewollte Auswirkungen hat. Genau dies erfüllen Unittests, wenn sie gepflegt werden und stets sämtlichen Code abdecken.

Das ist meines Erachtens der größte Vorteil von Unittests: Sie erlauben es Entwicklern, Code unbefangen ändern zu können, ohne Befürchtungen wegen Nebenwirkungen haben zu müssen.

Fasst man all diese Vorteile zusammen, so überwiegen die Vorteile ganz deutlich den initialen Aufwand, sich in Unittests einzuarbeiten und die eigene Arbeitsweise auf testgetriebene Entwicklung umzustellen: Unittests vereinfachen die Entwicklung und helfen, sie zielgerichteter und sicherer zu gestalten.

Auch wenn der Anfang schwer ist und eine saubere testgetriebene Entwicklung viel Reflektion und Disziplin erfordert – es lohnt sich.