Follow my new blog

Sonntag, 10. August 2008

Lieber grün freuen als schwarz sehen - Zufriedener mit Unit Tests

Grad habe ich den Code für meinen nächsten dotnetpro-Artikel fertiggestellt. Zuerst wollte ich ihn einfach nur so runterprogrammieren. Zwar bin ich ein Freund solider Planung und automatischer Tests, aber ich hatte den Chor der Entwickler im Ohr, die da singen "Aber muss denn das immer sein?" So hatte ich mich entschieden, auf Contract-First Design zu verzichten, keinen DI-Framework einzusetzen und eigentlich auch keine großartigen Unit Tests anzulegen.

Aber: Selten so gelacht. Diese Vorsätze konnte ich keine Stunde einhalten. Echt. Ich konnte nicht. Ich musste eine Architektur zumindest auf 2-3 Blättern skizzieren und Kontrakte andeuten. Die Last, das alles nur im Kopf zu versuchen, war mir einfach zu groß: ich hätte mich dafür anstrengen müssen und ich wäre in Sackgassen bei der ad hoc Implementierung gelaufen, die mich Mehraufwand gekostet hätten.

Das hat mir schonmal gezeigt, dass Architektur eine Gewohnheitssache ist. Am Anfang kann man ohne, weil man sich ihres Wertes nicht so bewusst ist. Dann macht es Mühe, sich auf sie einzulassen. Es tut weh, seine Gewohnheiten zu ändern. Man muss sich schon dazu zwingen. Aber wenn man am Ende dieses Tals der Tränen herauskommt, dann ist der Prozess ganz natürlich. Dann kann man - oder zumindest ich - nicht mehr anders. Dann ist Architektur ein Muss. Sonst fehlt einfach etwas.

Um den Beispielcode aber nicht mit Details zu überfrachten, habe ich trotzdem die Kontrakte nicht in eigene Assemblies gesteckt und auch kein DI-Framework benutzt. Soviel dann doch als Zugeständnis an den Entwicklerchor ;-)

imageDann das Testen. Eigentlich wollte ich nicht. Aber dann konnte ich doch nicht anders. Die Architektur einfach so zu implementieren und dann durch das kleine Frontend zu testen... nein, das ging nicht. Ich habe es echt versucht. Aber ich hab es dann nicht ausgehalten, so lange auf Feedback zu warten. Um herauszufinden, ob eine Infrastrukturfunktionalität des Beispielcodes funktioniert, hätte ich irgendwie künstlich einen Durchstich vom Frontend dorthin machen müssen. Wasfürein Aufwand! Oder ich hätte einfach weiter programmieren müssen, bis irgendwann man ein Durchstich da ist. Welch frustrierende Wartezeit!

Und so habe ich doch wieder alle Bereiche mit Unit Tests abgedeckt. Schritt für Schritt, eine Methode nach der anderen. Das hat sich einfach gut angefühl. Eine Erleichterung war es, nach ein wenig Programmierung mich beim Testen entspannen zu können.

Zweierlei ist mir dabei aufgefallen:

  1. Proaktives Testen geschieht in einem positiven Gefühl. Ich bin ganz aufgeregt, wenn ich den Test schreibe, weil ich ja sehen möchte, ob meine Idee vom Code richtig ist. Wenn ich hingegen auf einen solchen sofortigen Test verzichte und darauf warte, dass irgendwann mal eine Nutzung durch das Frontend auf einen Fehler stößt, dann ist mein Gefühl negativ. Dann teste ich nicht proaktiv, sondern debugge, d.h. ich befinde mich in einem Problemmodus. Schlechte Laune garantiert.
  2. Ein häufigerer Rollenwechsel zwischen Programmierer und Tester schafft Abstand zu Code. So entsteht Raum für Reflektionen. Das kann nur positiv sein, denn dann sehe ich plötzlich Dinge, die ich beim unausgesetzten Programmieren "im Flow" übersehen würde. Solcher Rollenwechsel entschleunigt auch in gewisser Weise. Auch das ist gut: für die Seele und den Code.

Also mal abgesehen von den Vorteilen automatischer Tests in punkto Refaktorisierbarkeit von Code habe ich für mich gemerkt, dass der Gewinn schon davor in einem guten Gefühl, in Entspannung liegt. Positive Einstellung und Entschleunigung machen einfach Spaß. Ich habe mich sozusagen grün gefreut angesichts der wachsenden Zahl kleiner grüner, also positiver Rückmeldungen vom Unit Testing Framework in ReSharper 4.0 (s. Bild oben).

imageSo richtig zufrieden haben mich die grünen Rückmeldungen aber erst werden lassen, als ich zusätzlich noch auf die Codeabdeckung der Tests geachtet habe. Fehlerfreiheit lässt sich ja nicht nachweisen. Also sagt ein grüner Test nicht, dass Code ohne Fehler ist. Tests überprüfen nur Erwartungen. Um ein gutes Gefühl zum Code zu bekommen, muss ich mehr als eine Erwartung haben; jeder Teil des Codes - jede Verzweigung, jede Methode - sollte meine Erwartungen erfüllen. Also müssen meine Tests möglichst viel Code abdecken. Meine Wunschmarke ist 90% Codeabdeckung. 100% sind natürlich noch besser - wenn auch nicht immer ausreichend -, aber ab und an lasse ich mal den Code für einen exotischen Fehlerfall ungetestet.

Mit der Integration von TestDriven und NCover habe mein gutes, grünes Gefühl dann gefestigt. Wie das nebenstehende Bild zeigt, decken meine Tests 97% der Codebasis ab. Das finde ich für den Zweck des Beispielcodes ausreichend.

Und so habe ich mich denn wirklich gut gefühlt, ganz grün vor Spaß, statt vor lauter Bugs schwarz geärgert. Architektur und Testen sind Gewohnheiten. Wenn man sie sich zum Nutzen der Codequalität einmal angeeignet hat, dann ist´s schwer, sie wieder abzustreifen. Aber es sind ja keine schlechten Gewohnheiten. Und sie haben mir ein gutes Gefühl verschafft. Was will ich mehr?

Kommentare:

Dominik hat gesagt…

Hallo Ralf,
Gratulation, das hört sich ja verdächtig nach "test infected" an (siehe http://c2.com/cgi/wiki?TestInfected)

Auf jeden Fall ein lohnendes Ziel.

Rainer Schuster hat gesagt…

Hallo Ralf,

das Blanke Testen ist für mich nicht mehr alleine ausreichend. Der Nutzen muss klar daraus hervorgehen (ich kenne ihn, aber viele andere nicht). Ich plädiere dafür Tests vor dem eigentlichen Code zu erstellen. Getreut dem Motto Contracts First, sehe die Tests als Spezifikation an, also eben auch ein Contract. Deshalb Specification First.

Ich habe daher vor kurzem angefangen mich mit dem Thema Behaviour Driven Developement (BDD) zu beschäftigen und nach ein, zwei kurzen Einführungsartikeln war für mich auch klar, dass dies die Art und Weise sein wird, wie ich in Zukunft meine Architektur, meine Tests aufbauen will. Den Vorteil, den ich bei BDD sehe, sind konkrete vorgehensweisen. Das ist besonders für Testneulinge vorteilhaft. Zumal habe ich auch noch damit zu kämpfen UnitTests als bewährtes und erfolgreiches Programmierparadigma durchzusetzen. BDD hilft mir den Benefit gut zu erklären, wenn ich mit Kollegen oder Vorgesetzten spreche. Vor allem hilft es mir auch Komponentenorientierung mit einem Container-Framework gut zu etablieren und meine Codebasis test- sowie wartbar zu halten.

Ich bin also nicht nur Test Infected. Ich bin Behaviour Infected.

Aus diesem Grund freue ich mich schon darauf, auf der .NET Open Space mit anderen über dieses Thema zu diskutieren.

Grüße,
Rainer

Ralf Westphal - One Man Think Tank hat gesagt…

@Rainer: Ich stimme da ganz mit dir überein. Allerdings habe ich dann doch von "Tests" gesprochen, weil der Begriff schon etablierter ist als der des Verhaltens (Behaviour). Und am Ende geht es bei aller Definition von Verhalten, d.h. bei der Erwartungsformulierung doch wieder um... Tests, d.h. Überprüfung von Erwartungen.

Gegenüber TDD ist BDD eine Verbesserung, da durch die Umbenennung die eigentliche Absicht von TDD endlich klar wird.

Weiterhin sehe ich aber eben zwei Zwecke, denen die Routinen dienen, die ich dann schreibe: Spezifikation und Test. Wenn ich noch keinen Code habe und mit BDD anfange, dann spezifiziere ich. Und später ist das sogar auch Dokumentation.

Wenn ich aber Code habe und jmd meldet einen Bug, dann Spezifiziere ich nicht mehr, sondern schreibe einfach einen Test, eine Überprüfung, ob Code das leistet, was ich dachte, dass er leisten soll.

Insofern denke ich, dass wir uns von BDD nicht davontragen lassen sollten und den Begriff "Test" nicht mehr benutzen.

BDD ist ja auch "Design". Doch nicht alles, was ich als Entwickler tue, ist designen.

Für Contract-first Design ist BDD aber eine gute Methode. Ohne Frage.

-Ralf