Peter Bucher Ralf Westphal

Blog

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

Review von Pragmatic Unit Testing

Samstag, 26. Dezember 2009, 12:26 Uhr
Permalink | Kommentare (1) | Kommentare als RSSRSS

Knapp mehr als drei Monate sind seit meinem letzten Review vergangen, in dem ich das Buch Painless Project Management with FogBugz besprochen habe. Viel ist seither geschehen – insbesondere habe ich die Interviewreihe .NET Professionals im Profil begonnen, für die inzwischen auch eine dedizierte Webseite besteht, auf der sämtliche Interviews verfügbar sind.

Am 1. November 2009 haben Peter Bucher und ich uns dann in unserem monatlichen Streitgespräch die Frage gestellt: Wie viel Sinn machen Unittests? Als Reaktion auf meinen Beitrag gab es zahlreiche Kommentare, wodurch ich mich bewogen sah, das Thema noch einmal zu überdenken.

Inzwischen stehe ich Unittests gänzlich anders gegenüber – meine Erfahrungen habe ich in den Blogeinträgen Auch Kirchen haben Kragsteine und Ein Ordnungssystem für Unittests beschrieben. Doch das war nur der öffentliche, nach außen sichtbare Teil meiner Beschäftigung mit Unittests.

Auf Empfehlung von David Tielke habe ich mich mit dem Buch Pragmatic Unit Testing von Andrew Hunt und David Thomas befasst.

Nachdem mich das Buch The Art of Unit Testing von Roy Osherove sehr enttäuscht hatte, war ich auf Davids Tipp mehr als gespannt. Um es kurz zu machen: Pragmatic Unit Testing ist wirklich empfehlenswert: Kompakt, direkt, pragmatisch und dennoch durchdacht erklärt es auf rund 190 Seiten nicht nur das Handwerkszeug, sondern auch – und das finde ich besonders wertvoll – zahlreiche Best Practices.

So wird zwar in den ersten drei Kapiteln darauf eingegangen, wie NUnit an und für sich funktioniert – danach ist dieses Thema jedoch abgehandelt und es geht in den folgenden sieben Kapiteln um die eigentlich relevanten Aspekte des Unittestings:

  • Welche Aspekte von Code erfordern besonderes Augenmerk beim Testen?
  • Welche Grenzwerte müssen gesondert beachtet werden?
  • Wie werden Stubs und Mocks eingesetzt?
  • Was zeichnet gute Tests aus?
  • Was muss beim Schreiben von Tests im Hinblick auf Teamentwicklung beachtet werden?
  • Wie wird Legacycode sinnvoll getestet?
  • Welche Rolle spielen Architektur und Refactoring für die Entwicklung von Tests?
  • Wie können UIs getestet werden?

All diese Themen sind ausgesprochen wichtig und werden kompakt, aber dennoch gut verständlich erläutert. Ein Anhang, der auf typische Stolperfallen und deren Vermeidung hinweist, rundet die zehn vorangegangenen Kapitel ab.

Alles in allem ist Pragmatic Unit Testing ohne Einschränkung empfehlenswert und so wohl für Einsteiger wie auch für Fortgeschrittene gleichermaßen geeignet. Wer sich also für den Einsatz von Unittests interessiert und noch auf der Suche nach einer geeigneten Lektüre ist, dem sei dieses Buch ans Herz gelegt.

.NET Professionals im Profil: herbivore

Dienstag, 15. Dezember 2009, 09:37 Uhr
Permalink | Kommentare (2) | Kommentare als RSSRSS

herbivore arbeitet als freiberuflicher Informatiker und ist Administrator von myCSharp.de. Nach ersten Schritten auf programmierbaren Taschenrechnern bekam er über das Informatikstudium Zugang zur strukturierten Programmierung. Inzwischen entwickelt er schwerpunktmäßig mit C++ und C#. Sie erreichen ihn über myCSharp.de.

Golo Roden: herbivore [tatsächlicher Name ist der Redaktion bekannt], wie bist Du zur Softwareentwicklung gekommen? Wie und wann hast Du angefangen?

herbivore: Bei mir hat alles mit einem programmierbaren Taschenrechner angefangen. Andere für den Privathaushalt erschwingliche Computer gab es zu diesem Zeitpunkt noch nicht. Ich hatte mir den Taschenrechner aber gar nicht gewünscht, weil man ihn programmieren konnte, sondern weil er zehn Speicherregister hatte. Meine Berechnungen damals hatten immer viele Nebenrechnungen, und ich kam mit dem einen Speicherregister meines alten Taschenrechners nicht mehr hin.

Der neue Taschenrechner war eine Offenbarung für mich. Ich stellte dann schnell fest, dass er 49 Programmschritte speichern konnte. Allerdings gingen diese nach dem Ausschalten verloren, so dass die Programme deshalb jedes Mal neu eingegeben werden mussten. Eine andere Speichermöglichkeit als einen Stift und ein Blatt Papier gab es nicht. Jedenfalls war die erste Programmiersprache, die ich lernte, die Programmiersprache dieses Taschenrechners.

Mein erster „echter“ Rechner – nur ein oder zwei Jahre später – hatte 32KB Hauptspeicher und man konnte einen handelsüblichen Kassettenrekorder als Hintergrundspeicher anschließen. 32KB Hauptspeicher waren damals unglaublich viel. Das Basismodell wurde mit 8KB Speicher ausgeliefert. Eine ausführliche Beschreibung des Hardware-Aspekts meiner ersten Schritte findet sich unter Eure ersten Rechner / Schritte mit Rechnern.

Auf diesem Rechner konnte man mit einem sehr einfachen Basic programmieren. Das habe ich mir selbst mittels des beigelegten Handbuchs beigebracht. Im Nachhinein bin ich glücklich, dass sich der dadurch verursachte Schaden in Grenzen gehalten hat – denn strukturierte Programmierung war das nicht. Ich wusste zu diesem Zeitpunkt aber noch nicht einmal, dass es so etwas überhaupt gibt.

Das lernte ich erst im Informatikstudium, das ich zwei Jahre später begonnen habe. Wenn man so will, war das Studium für mich der eigentliche Beginn dessen, was ich als Softwareentwicklung bezeichnen würde. Für mich standen ab dieser Zeit gute und saubere Lösungen im Vordergrund. Dass ein Programm einfach nur das machte, was es sollte, war mir nicht mehr genug.

Golo Roden: Du bist bei myCSharp.de der mit Abstand aktivste Benutzer, und zugleich jemand, der unglaublich kompetent ist. Wie bist Du zu diesem breitgefächerten Wissen und zu myCSharp.de im Speziellen gekommen?

herbivore: Mit dem Studium habe ich mein Hobby zum Beruf gemacht. Und jeder weiß, wieviel Energie man in eine Sache stecken kann, die einen wirklich interessiert und einem wirklich Spaß macht. Darum habe ich dankbar alle Möglichkeiten angenommen, mein Wissen zu erweitern. Mich haben viele unterschiedliche Themen interessiert, also habe ich mich auch mit sehr vielen verschiedenen Themen beschäftigt.

Das Lernen war bei mir immer ein Dreiklang aus Aufnehmen, Ausprobieren und Weitergeben. Natürlich muss man zu einem Thema erst einmal etwas lesen oder hören, um überhaupt eine Basis zu haben. Aber das allein genügt nicht – man muss das Gelernte auch ausprobieren und anwenden. Ich habe in meinem Leben bestimmt schon einige tausend Mini-Testprogramme geschrieben, um irgendwelche Zusammenhänge untersuchen zu können. Und zu guter Letzt trägt es zur Vertiefung und Verbreiterung des Wissen entscheidend bei, das Gelernte anderen zu erklären.

Um Wissen weiterzugeben, muss man es so formulieren, dass andere es verstehen. Dabei merkt man dann auch schnell, wenn einem selbst etwas noch nicht ganz klar ist, und man kann die Lücke schließen. Außerdem profitiert man von den Nachfragen, die andere stellen, weil man dadurch auf neue Aspekte aufmerksam wird.

Das ist auch der Grund, warum ich auf myCSharp.de lieber Fragen beantworte als Fragen zu stellen. Man lernt einfach mehr dadurch.

Dennoch bin ich ganz profan durch eine eigene Frage zu myCSharp.de gekommen, auf die ich eine gute Antwort gesucht und bekommen habe. Das hat mich motiviert, mich zu revanchieren, und auch ein paar Fragen zu beantworten. Auf myCSharp.de habe ich in Was bewegt euch zum Helfen im Forum? beschrieben, was mich zum Helfen motiviert.

Dabei war es inbesondere zu Anfang so, dass ich viele Antworten, die ich gegeben habe, selbst nicht kannte, bevor ich mich mit der Frage beschäftigt hatte. Hier kommen dann wieder die Mini-Testprogramme, aber auch das Stöbern und Nachschlagen in der Dokumentation und eigenes Nachdenken über die Frage ins Spiel.

Golo Roden: Ein wesentliches Merkmal der IT ist, dass man beständig mit neuen Entwicklungen konfrontiert wird, und diesen folgen muss. Woher nimmst Du die Motivation, Dich quasi jeden Tag weiterzubilden und mit Neuem zu beschäftigen?

herbivore: Die Motivation kommt aus der Einsicht in die Notwendigkeit. Wenn man nichts Neues lernt, fällt man zurück. myCSharp.de ist mein Weg, in dieser Hinsicht auf dem Laufenden zu bleiben. myCSharp.de macht mir zum Glück nach all der Zeit immer noch Spaß und ich bin täglich dort aktiv. Es ist ja nicht so wichtig zu wissen, dass es eine neue Entwicklung gibt, denn es gibt viele neue Entwicklungen und die meisten verschwinden scheller als sie gekommen sind – wenn aber wirklich etwas beginnt, sich durchzusetzen, häufen sich auf myCSharp.de die Fragen dazu. Dann weiß ich, dass es Zeit ist, mir dieses Thema genauer anzuschauen.

Die kurze Halbwertszeit des Wissens sollte es übrigens jedem leicht machen, sein Wissen mit anderen zu teilen. Ich hatte nie die Befürchtung, dass ich meinen Wissensvorsprung dadurch verlieren könnte, dass ich mein Wissen mit anderen teile, weil ich genau wusste, dass immer wieder neues Wissen dazu kommt. Und das hat sich auch in der ganzen Zeit, in der ich mich mit Informatik beschäftige, bewahrheitet. Man sollte also nicht auf seinem Wissen hocken, bis es wertlos geworden ist, sondern sein Wissen vorher weitergeben.

Golo Roden: Wenn sich ein Anfänger heute mit dem Thema Softwareentwicklung befassen will - welche Voraussetzungen sollte er Deiner Meinung nach dafür mitbringen, und was siehst Du als No-Go an?

herbivore: Ein Anfänger sollte Eigeninitiative und Experimentierfreude mitbringen. Konkretes Wissen ist weniger wichtig. Man braucht eher die Fähigkeit, sich Wissen zu erwerben. Man sollte sich nicht scheuen, Bücher gründlich durchzuarbeiten. Durchlesen alleine hilft nicht. Auch und gerade wenn es in dem Buch keine vorgegebenen Arbeitsaufgaben gibt, sollte man versuchen, das Gelesene sofort umzusetzen und sei es in Form der schon genannten Mini-Testprogramme. Und das auch nicht erst am Ende des Kapitels, sondern möglichst Seite für Seite.

Natürlich kann man durch Ausprobieren nicht alles erschlagen. Gerade bei Architekturfragen zählen abstraktes Denken und vor allem Erfahrung, die man sich natürlich erst nach und nach erwerben kann.

Golo Roden: Bei der Vielzahl an Technologien, die es heute gibt: Womit sollte ein Anfänger heutzutage anzufangen?

herbivore: Man sollte nicht gleich zu hoch hinaus wollen und sich nicht zu schade sein, ganz zu Anfang auch wirklich einfache Programme zu schreiben. Lieber in kleinen Schritten alles gründlich verstehen, als zu versuchen, durch unreflektiertes Zusammenkopieren von Code ein möglichst eindrucksvolles Programm zu Stande zu bringen. Insofern sind einfache Kommandozeilenprogramme für mich auch heute noch das Mittel der Wahl für den Einstieg. Allerdings darf man dabei nicht zu sehr in die imperative Programmierung verfallen.

Es ist wichtig, sich von Anfang an mit der Objektorientierung zu beschäftigen. Das bedeutet unter anderem, den Code an Hand von Klassen und nicht an Hand von Funktionen zu strukturieren. Funktionen in dem Sinne gibt es gar nicht mehr. Bei aller Ähnlichkeit zu einem Funktionsaufruf ist ein Methodenaufruf konzeptuell etwas ganz anderes, nämlich das Senden einer Nachricht an ein Objekt. Die Implementierung einer Methode ist dementsprechend die Vorschrift, wie ein Objekt auf eine solche Nachricht reagiert.

Im Prinzip ist der Begriff „objektorientiertes Programm“ ein Widerspruch in sich. Eigentlich gibt es in einer objektorientierten Anwendung nur eine Menge von Objekten, die miteinander kommunizieren. Der Anstoß, was die Objekte machen, kommt nicht mehr von einem fest vorgegebenen Programm, sondern aus den Nachrichten, die der Benutzer durch seine Eingaben an die Objekte schickt. Das nennt sich dann ereignisgesteuerte Programmierung und ergänzt die objektorientierte Programmierung ideal.

Wenn man Interesse, Spaß, Ausdauer und Beharrlichkeit mitbringt, wüsste ich keinen Grund, warum man keinen Erfolg in der Software-Entwicklung haben sollte. Ich persönlich finde das Vorhandensein eines mathematischem Hintergrunds hilfreich, aber es gibt auch viele, die ohne diesen gute Informatiker geworden sind.

Dependency Injection ist keine Silberkugel

Montag, 7. Dezember 2009, 07:01 Uhr
Permalink | Kommentare (3) | Kommentare als RSSRSS

Das Inversion of Control-Paradigma folgt in der Regel einer von zwei Varianten: Entweder wird ein dedizierter Service Locator in Form eines Microkernels verwendet, oder es wird auf ein geeignetes Dependency Injection-Framework samt entsprechendem Container zurückgegriffen.

Der wesentliche Unterschied zwischen diesen Varianten besteht in unterschiedlichen Graden der Explikation beziehungsweise Implikation, und in der Abhängigkeit von der Infrastruktur:

  • Während der Service Locator eine generische Factory darstellt und explizit beauftragt werden muss, eine Instanz zu einem gegebenen Kontrakt zu erzeugen, geschieht dies beim Einsatz eines Dependency Injection-Containers implizit.
  • Da der Aufruf des Service Locators explizit geschieht, bedeutet dies in Konsequenz, dass eine Referenz auf den Service Locator vorhanden sein muss – diese Referenz entfällt beim Einsatz eines Dependency Injection-Containers.

Aus diesen grundlegenden Unterschieden ergeben sich einige spezifische Vor- und Nachteile, interessant ist jedoch, dass der Service Locator in der Regel von Einsteigern, denen das Inversion of Control-Paradigma noch nicht vertraut ist, bevorzugt wird. Die Gründe hierfür liegen auf der Hand: Der explizite Aufruf einer Factory zur Erzeugung einer Instanz ist näher am klassischen Aufruf von new.

Je ausgiebiger sich ein Entwickler danach mit Inversion of Control auseinandersetzt, desto eher lernt er die Vorzüge von Dependency Injection schätzen. Dazu zählen unter anderem:

  • Es ist keine Referenz auf einen Service Locator oder ein ähnliches Konstrukt notwendig – der Dependency Injection-Container bleibt verborgen, so dass die Abhängigkeit von externen Komponenten vermindert wird.
  • Da die einzelnen Abhängigkeiten von außen über den Konstruktor oder entsprechende Eigenschaften aufgelöst werden können, ist es ein Leichtes, innerhalb eines Unittests passende Mockobjekte in eine Komponente hineinzureichen. Darüber hinaus kann in Unittests gänzlich auf den Dependency Injection-Container verzichtet werden, was den Aufwand zur Bereitstellung einer passenden Infrastruktur deutlich senkt.
  • Da die gesamte Verdrahtung der Anwendung implizit geschieht, sinkt der Aufwand für den einzelnen Entwickler deutlich.

Vor allem auf Basis dieser Gründe erscheint Dependency Injection dann häufig als die besser durchdachte Alternative zu einem Service Locator, so dass man als Entwickler versucht ist, sämtliche Abhängigkeiten mit Hilfe von Dependency Injection aufzulösen.

Doch Dependency Injection ist keine Silberkugel. Anders als häufig behauptet, verfügt Dependency Injection nämlich auch über einige Nachteile:

  • Zum einen ist es mit Dependency Injection nicht möglich, lokale Variablen zu instanziieren: Zugriff besteht – entweder über den Konstruktor oder über passende Eigenschaften – nur auf Felder. Der vermeintliche Ausweg, jede lokale Variable in ein Feld zu überführen, erweist sich als Irrweg: Schließlich weisen lokale Variablen und Felder eine unterschiedliche Semantik auf und sind nicht per se gegeneinander austauschbar.
  • Zum anderen ist die dynamische Erzeugung von Instanzen per Dependency Injection nicht möglich: Der Lebenszyklus einer injizierten Komponente ist weitestgehend an den der enthaltenden Komponente gebunden. Für dynamische Instanziierung – beispielsweise in Abhängigkeit von anderem Code – eignet sich Dependency Injection somit nicht.

Das heißt, dass immer dann, wenn lokale Variablen gefüllt oder mehrfach vorhandene Abhängigkeiten dynamisch aufgelöst werden sollen, Dependency Injection an ihre Grenzen stößt: In diesem Fall bleibt nichts anderes, als auf einen Service Locator oder ein ähnliches Konstrukt zu setzen.

Dies bedeutet jedoch, dass Dependency Injection nicht alle Probleme auf einen Schlag löst, ebenso wenig wie ein Service Locator dies macht. Vielmehr liegt die ideale Lösung darin, beide Ansätze miteinander zu vereinen, um das Beste aus beiden Welten nutzen zu können.

Kurzum: Dependency Injection ist kein Allheilmittel. Die Nutzung empfiehlt sich immer dann, wenn genau eine einzige Komponente verwendet werden soll, deren Lebenszyklus dem des enthaltenden Objekts gleicht. Für alle anderen Fälle eignet sich ein Service Locator besser.

Die beiden Konzepte konkurrieren also nicht exklusiv miteinander, sondern ergänzen sich gegenseitig: Dies kann sogar so weit gehen, dass der Service Locator mit Hilfe von Dependency Injection in die entsprechenden Komponenten injiziert wird, um die sonst vorhandene Abhängigkeit zu vermeiden.

Reflection – Fluch oder Segen?

Dienstag, 1. Dezember 2009, 09:37 Uhr
Permalink | Kommentare (1) | Kommentare als RSSRSS

Am 13. Oktober 2008 haben Peter Bucher und ich unter dem Titel Noch Fragen, Bucher? Ja, Roden! angekündigt, jeweils zum ersten eines jeden Monats einen Kommentar zu einem vorab gemeinsam gewählten Thema verfassen zu wollen. Bisher sind in dieser Reihe folgende Kommentare erschienen:

Heute, am 1. Dezember 2009, ist es nun wieder so weit, und unser Thema für diesen Monat lautet:

Reflection – Fluch oder Segen?

So wohl Peter wie auch ich haben uns unabhängig voneinander im Vorfeld unsere Gedanken gemacht, wie wir diesem Thema gegenüberstehen. Peters Kommentar findet sich zeitgleich in seinem Blog, folgend nun mein Kommentar zu diesem Thema:

Reflection ist ein zentrales Element in .NET: Die Tatsache, dass in MSIL eben nicht nur der eigentliche Code, sondern auch die dazugehörigen Metadaten enthalten sind, zeigt dies. Und – das ist auch gut so.

Denn Reflection bildet die Grundlage für zahlreiche Szenarien, die von vielen Entwicklern tagtäglich genutzt werden: Sei es der Einsatz eines Microkernels und damit verbunden das dynamische Laden und späte Instanziieren von Typen, sei es der Einsatz einer dynamischen Sprache oder der dynamischen Sprachfeatures von C# 4.0. All dies basiert letztlich auf Reflection.

Auch die Verwendung von Attributen zur Laufzeit wäre ohne Reflection nicht möglich, ebensowenig wie die Analyse von Assemblies, um an Hand der enthaltenen Typen und deren Methoden, Eigenschaften und Feldern gewisse Entscheidungen zu treffen.

Reflection ist also fraglos ein Segen – ohne sie wären all die genannten Punkte in .NET nicht oder zumindest nicht mit dermaßen geringem Aufwand möglich. Doch woher stammt dann der schlechte Ruf, der in Gesprächen über Reflection zugegebenermaßen häufig den Unterton bestimmt?

Prinzipiell sind gleich zwei Gründe für den schlechten Ruf von Reflection verantwortlich, wobei einer der beiden systemimmanent ist, der zweite jedoch zumindest teilweise nur die konkrete Implementierung in .NET betrifft:

  • Reflection ermöglicht den wahlfreien Zugriff auf Code, auch auf geschützte Elemente wie beispielsweise Variablen, die als private gekennzeichnet wurden.
  • Reflection ist langsam – so wohl die Analyse von Code wie auch dessen Ausführung betreffend.

Dass Reflection den wahlfreien Zugriff auf beliebigen Code ermöglicht, ist Fluch und Segen zugleich: Auf der einen Seite wird auf diese Art sämtliche objektorientierte Programmierung ausgehebelt, auf der anderen Seite kann es ungemein nützlich sein, von außen auch auf private Elemente zugreifen zu können – man denke nur an diverse Unittests.

Nebenbei bemerkt: Einen interessanten Ansatz, wie Unittests ohne den Einsatz von Reflection auskommen und dennoch Interna testen können, hat Ralf Westphal in seinem Blogeintrag Zustand als Abhängigkeit beschrieben.

Generell gilt für den Einsatz von Reflection zum Zugriff auf geschützte Elemente also die gängige Empfehlung: So viel wie nötig und so wenig wie möglich – mit Betonung auf zweiterem.

Doch all dies wohnt Reflection bereits vom Ansatz her inne: Dabei spielt es keine Rolle, ob es sich um Reflection auf Basis von .NET, Java oder einer beliebigen anderen Plattform handelt: Diese Nachteile und die damit einhergehenden Überlegungen gelten generell.

Mit dem Umstand, dass Reflection unter .NET ausgesprochen langsam ist, sieht es jedoch ein wenig anders aus: Reflection an sich ist immer langsamer als statisch kompilierter Code – das ist logisch, wenn man weiß, wie beispielsweise Aufrufe von Funktionen in beiden Fällen funktionieren: Der Lookup per Reflection kostet schlicht und ergreifend mehr Zeit.

Doch Reflection kann auch dazu genutzt werden, um Code zu analyiseren – und hier ist es tatsächlich die Implementierung von .NET, welche die Schuld dafür trägt. Zudem zeichnet sich die Reflection von .NET in diesem Zusammenhang auch dadurch aus, wenig flexibel zu sein.

So ist es beispielsweise nicht möglich, aus einer auf dem Desktop ausgeführten .NET-Anwendung die Assembly mscorlib.dll von Silverlight zu öffnen – es kann nämlich jeweils nur eine Instanz der mscorlib.dll in einen Prozessraum geladen werden, was die Analyse einer anderen als der eigenen mscorlib.dll unter .NET immer scheitern wird.

Abhilfe – so wohl für die Geschwindigkeit wie auch für die Flexibilität – schafft der Einsatz einer anderen Komponente für Reflection, wie beispielsweise Mono Cecil, die unter anderem auch von Werkzeugen wie dem Mono Migration Analyzer oder dem Mono Debugger eingesetzt wird.

Zusammenfassend kann man also sagen, dass Reflection per se so wohl Fluch wie auch Segen ist – und dass es vom konkreten Kontext abhängt: Für Latebinding, dynamische Sprachen und Analyse von Code ist Reflection unverzichtbar, doch für den darüber hinausgehenden Einsatz von Reflection sollte man sehr gute Gründe haben.