.NET, Community, Tools

Test-Driven-Dojo: Das Einmaleins von TDD

10 October 2009 3 Kommentare

Vergangenen Mittwoch Abend war es wieder einmal soweit, das zweite .NET Coding Dojo für Einsteiger in München wurde veranstaltet. Erstaunlich: es waren bekannte Gesichter aus dem ersten Dojo wiederzufinden! Doch auch neue Teilnehmer fanden den Weg zum Dojo.

Dabei hatte das Münchner Dojo viel “Event-Konkurrenz”: Der Joint-Launch von Microsoft, der Twittwoch und das unnachahmliche Herbstwetter mit 25 Grad!. Um so erfreulicher, das sich die Teilnehmer nicht davon abhalten liessen mit mir zur Feierabendzeit noch eine geistige Trainingseinheit im Dojo hinter sich zu bringen. Ich bin als Master kurzfristig für den erkrankten Pete eingesprungen – an dieser Stelle Gute Besserung! Ich hoffe ich konnte einen adequaten Ersatz darstellen.

Zum Dojo: es gab drei Code Kata’s zur Auswahl: Minesweeper, der im Experten-Dojo gelöste Taschenrechner und das mittlerweile beliebte FizzBuzz, jedoch reinkarniert als Windows-Forms-Anwendung.

Die Teilnehmer entschieden sich für FizzBuzz im Windows-Forms-Gewand. Vorlage für diese Übung war die iPhone-Implementierung. Eine gute und herausfordernde Wahl, wie sich im Nachhinein herausstellen sollte.

Zum “Warmwerden” haben wir zunächst das klassische Kata gelöst. Diesmal ging es etwas zügiger von der Hand als beim ersten Mal, was unter Anderem auch an den Wiederkehrern lag. Anschliessend ging es an die UI.

Test-Driven UI-Entwicklung. Geht das überhaupt?

Doch Halt! Wie soll denn das jetzt mit purem Test-Driven-Development gehen? Die Einsteiger waren ein wenig vor den Kopf gestossen, denn die Frage im Raum war klar: Wie kann ich Test-First eine UI für ein Programm entwickeln? Die Antwort war jedoch alles andere als deutlich.

Tom – der als einziger Experte im Einsteiger-Dojo mit dabei war, was mich besonders gefreut hat – half den Einsteigern ein wenig, indem er sagte: “Eine UI ist nicht ohne Weiteres testbar.” Die Teilnehmer vertrauten der Aussage des Profis und zuckten mit den Schultern, als es darum ging, eine Testklasse für das Windows-Form zu schreiben. Nach der von Tom im Kern korrekten Anspielung auf UI-Tests habe ich versucht, den Fokus etwas zu verschieben.

Ich stellte folgenden provokanten Satz in den Raum: “Die Benutzeroberfläche ist nicht testbar. Gut. Aber ist denn wirklich nichts testbar? Ich meine, wie ist es z.B. mit der Interaktion? Ist die Interaktion testbar?”
FizzBuzz auf dem iPhone / iPod
Dazu habe ich nochmal die Referenz-Anwendung gestartet und gezeigt, mit der Bitte, sich die Interaktionselemente der Anwendung – vielmehr die Interaktion mit der Benutzeroberfläche – nochmals genau anzusehen.

Im Wesentlichen besteht das “Spiel” aus drei Eingabefeldern (Buttons). In der Mitte wird die Zahl dargestellt, für die es die “FizzBuzz-Frage” zu beantworten gilt. Klickt man auf die Zahl, ist die Zahl die Antwort. Oben drüber und unten drunter werden die Antwortmöglichkeiten “Fizz” und “Buzz” dargestellt. Klickt man darauf, so ist selbiges die Antwort.

Abstraktion der Benutzeroberfläche zu Interaktionsanforderungen

Am Anfang war deutlich zu spüren, dass es schwer ist, eine konrektes User Interface soweit zu abstrahieren, dass man nur noch in “Interaktionen” zwischen dem Benutzer und dem Programm denkt. Auf die Frage wie denn die erste Testmethode heissen soll, “verirrten” sich einige Male ein paar Teilnehmer wieder in die konrekte UI (z.B. mit Antworten wie “ClickOnFizzButtonWhenThreeIsDisplayed”).

Doch mit ein paar kleinen Hilfen und gemeinschaftlicher Denkleistung konnten die Teilnehmer aus o.g. Interaktionsanforderungen heraus tatsächlich Tests formulieren. Genau diese Momente waren es, der dem Dojo die richtige Würze in Sachen Lerneffekt gab. So schreibten wir ohne die UI oder etwas anderes implementiert zu haben z.B. folgende Tests:

        [Test]
        public void IncrementsNumberWhenAnswered()
        {
            FizzBuzzGame fizzBuzzGame = new FizzBuzzGame();

            fizzBuzzGame.Start();
            fizzBuzzGame.Answer("1");

            Assert.AreEqual(2, fizzBuzzGame.CurrentNumber);
        }

        [Test]
        public void CorrectAnswerReturnsTrue()
        {
            FizzBuzzGame fizzBuzzGame = new FizzBuzzGame();

            fizzBuzzGame.Start();
            bool isCorrect = fizzBuzzGame.Answer("1");

            Assert.AreEqual(true, isCorrect);
        }

Deutlich erkennbar ist natürlich, dass nach wie vor die UI ansich (also das WinForm, die Buttons etc) natürlich nicht testbar sind. Aber das ist ja auch nicht der Sinn der Sache! Denn das Ziel und der Zweck von Test-Driven-Development ist nicht die Verifikation, sondern die eigentliche Entwicklung des Codes! Schnell fand die Gruppe heraus, dass die Implementierung der FizzBuzzGame-Klasse sich um ganz andere Verantwortlichkeiten kümmert, als die FizzBuzz-Klasse (der eigentliche Algorithmus).

Die Abstraktion der Interaktionen führte zur Implementierung einer Klasse mit der Eigenschaft CurrentNumber sowie den Methoden Start und Answer. Schnell waren die wichtigen Tests geschrieben und die Klasse implementiert, so dass man sich der Verknüpfung der “Interaktionsklasse” zur eigentlichen UI widmen konnte. Das Verbinden erwies sich als wunderbar einfach:

        private void buttonFizz_Click(object sender, EventArgs e)
        {
            bool isCorrect = this.game.Answer("Fizz");

            if (!isCorrect)
            {
                MessageBox.Show("Wrong answer", "Ooops!", MessageBoxButtons.OK);
            }

            this.buttonNumber.Text = this.game.CurrentNumber.ToString();
        }

        private void buttonBuzz_Click(object sender, EventArgs e)
        {
            bool isCorrect = this.game.Answer("Buzz");

            if (!isCorrect)
            {
                MessageBox.Show("Wrong answer", "Ooops!", MessageBoxButtons.OK);
            }

            this.buttonNumber.Text = this.game.CurrentNumber.ToString();
        }

Natürlich kann man hier noch wunderbar refaktorisieren und den überschüssigen doppelten Code eliminieren. Aber für’s erste soll das Mal genügen, denn jeder wollte ja sehen, ob das Spiel denn wirklich auf Anhieb spielbar war. Also schnell kompiliert und gestartet.
Fizz Buzz Windows Forms Game
Tatsächlich! Es funktioniert! Die Begeisterung der Teilnehmer war förmlich aus den Gesichtern zu lesen, als wir fröhlich das FizzBuzz-Spiel Zahl für Zahl spielten… “Zwölf ist Fizz”… “Dreizehn ist Dreizehn”… “Vierzehn ist Vierzehn”… Oh! Halt. Für die Fünfzehn muss ich ja “FizzBuzz” antworten, wie soll denn das gehen?

Schnell wurde wieder die IPhone-Referenz-Anwendung gezückt und durchgespielt… “Zwölf ist Fizz”… “Dreizehn ist Dreizehn”… “Vierzehn ist Vierzehn”… und “Fünfzehn ist Fizz und Buzz”. Aha! Die “FizzBuzz” Antwort besteht also aus einem Klick auf “Fizz” gefolgt von einem Klick auf “Buzz”! Klar. Doch wie lässt sich das in unserer Anwendung lösen?

Verwirrung machte sich breit: “Wieso ist uns das beim ersten Mal nicht aufgefallen!?!” fragte man sich. Nun sei’s drum, die Beantwortung mit Hilfe von zwei aufeinanderfolgenden Klicks ist das derzeitige Problem das es zu lösen gilt. Unter anderem kam der Vorschlag, man müsse einen Timer im Form einführen, der bei dem Klick auf “Fizz” gestartet wird und auf den nächsten Klick wartet, so dass man die “FizzBuzz”-Antwort abgeben kann. Viele Teilnehmer konzentrierten sich darauf, diese Besonderheit in der UI bzw. im Code-Behind des Forms zu lösen.

Test-Driven-Development als Wegweiser

Nach einigen Minuten angeregter Diskussion kam ein Einwand von Kai: “Wir haben für die ersten Interaktionen doch Tests geschrieben, dann sollten wir das doch für diesen Fall auch können oder?”. Der Vorschlag fand sofortige Zustimmung. Also auf in die Testklasse und kräftig überlegt, wie man diese Interaktion testen kann. Das Ergebnis waren eine Reihe von Tests, die sich um den “zweistufigen” Antwort-Prozess kümmerten. Auszug:

        [Test]
        public void FizzBuzzIsAnsweredInTwoSteps()
        {
            FizzBuzz fizzBuzz = new FizzBuzz();
            Dictionary values = fizzBuzz.Count();

            FizzBuzzGame fizzBuzzGame = new FizzBuzzGame();
            fizzBuzzGame.Start();

            for (int i = 1; i <= 14; i++)
            {
                fizzBuzzGame.Answer(values[i]);
            }

            fizzBuzzGame.Answer("Fizz");
            fizzBuzzGame.Answer("Buzz");

            Assert.AreEqual(16, fizzBuzzGame.CurrentNumber);
        }

Es folgte die Implementierung der Anforderung in der FizzBuzzGame-Klasse - so wie es davor auch der Fall war. Die Lösung der Problemstellung war an und für sich trivial: Es wurde in der Klasse ein Antwort-Status eingeführt, mit dessen Hilfe man erkennen konnte, ob eine zweistufige Antwort notwendig ist und natürlich ob die zweistufige Antwort korrekt gegeben wurde.

Doch das entscheidende an dieser Herausforderung war nicht die Implementierung selbst, sondern wiederum die Erkenntnis, das uns TDD wieder einmal den Weg in die richtige Richtung gezeigt hat. Statt der ad-hoc Vorschläge, einen Timer im Form zu haben oder irgendwie den Status innerhalb des Codebehinds des Forms zu implementieren, half uns TDD, die Anforderung gekapselt in der FizzBuzzForm-Klasse umzusetzen.

Das Resultat: Keine einzige Änderung im Codebehind des Forms! Und trotzdem eine Änderung des Interaktions-Verhaltens des Spiels! Ergo: eine rundum saubere Sache: klare Verantwortlichkeiten, eine jederzeit austauschbare View (das Form), ein vollends getesteter Presenter (die FizzBuzzGame-Klasse) sowie das ebenfalls in TDD entwickelte Model (die FizzBuzz-Algorithmus-Klasse). TDD hat es den Einsteigern (wohlgemerkt keine langjährigen Entwicklungsprofis!) ermöglicht, ein gängiges und äußerst hilfreiches Pattern in der Windows-Forms-Entwicklung anzuwenden, ohne jegliche Kenntnis vom Design Pattern zu haben.

Fazit: Ein Super Dojo! Ich war wirklich begeistert, dass es uns im Einsteiger-Dojo gelungen ist, das Fundament von TDD zu vermitteln: Nicht testen um zu verifizieren, sondern testen um zu entwickeln! Gleichzeitig war das Windows-Forms-Kata ein Paradebeispiel für Seperation of Concerns und der "intuitiven" Entwicklung der Grundzüge des MVP-Patterns. Vielen Dank an alle Teilnehmer, es war Klasse!

Ich freue mich schon heute auf das nächste .NET Coding Dojo für Experten am 21. Oktober!. Das wird bestimmt wieder ein besonderer Spass. Seid dabei!

3 Kommentare

  • Buzzyteam :

    If I understand this well, you took our app as a kata example for a test-driven development exercise ?? :) :) :) That’s really neat ! We were actually wondering who our fourth user might be ;)

  • Tom :

    Der der Vollständigkeit halber möchte ich erwähnen, dass ich mit der Aussage, “dass UI nicht ohne weiteres testbar sei” meinte, dass man in Richtung MVP arbeite müsste um seinen Code testen zu können. Was dann mit einem “FizzBuzz-Core” in die richtige Richtung führte. :)

    LG Tom

  • GMBSG: TDD ist keine Wissenschaft! at .NET Stories: Digitale Erfahrungen :

    [...] statusbasierte Tests sind ok, vor allem in Richtung DB oder UI. Ein schönes Beispiel dafür ist KataFizzBuzz als WinForms-Anwendung oder KataBlog. Natürlich ist interaktionsbasiertes Testen “schöner”, fördert die [...]

Ihre Meinung ist gefragt!

Sie können folgende Tags verwenden:
<a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>

Dieser Blogs unterstützt Gravatare.
Falls Sie noch keines haben, können Sie Ihren persönlichen Avatar bei Gravatar erstellen.