Testgetriebene Entwicklung nach der Münchner Schule

Seite 2: Ein Praxisbeispiel zur Münchner Schule

Inhaltsverzeichnis

Zur Verdeutlichung der Münchner Schule dient im Folgenden die Diamond-Kata. Dabei handelt es sich um eine bekannte Programmierübung, um TDD zu demonstrieren. Die Aufgabenstellung lautet folgendermaßen:

Ziel: Zu einem gegebenen Buchstaben wird eine Raute ausgegeben, die mit 'A' beginnt und den angegebenen Buchstaben an der breitesten Stelle hat.

diamond('B') erzeugt

.A.
B.B
.A.

diamond('C') gibt Folgendes aus:

..A..
.B.B.
C...C
.B.B.
..A..

Hinweis: Zur besseren Lesbarkeit verwendet der Beispielcode Punkte statt Leerzeichen.

Die Münchner Schule beginnt die Umsetzung ebenso wie die Londoner Schule mit einem Akzeptanztest. Andere Schulen wie London oder St. Pauli können mit einem einfachen Akzeptanztest starten. Für die Diamond-Kata wäre das beispielsweise der Diamant zu "A":

expect( diamond("A") ).toEqual("A")

Die Münchner Schule setzt stattdessen auf einen komplexen Test, der alle wichtigen Fälle der zu findenden Implementierung enthält. Das bedeutet für die Diamond-Kata, dass zumindest jede Art von Zeile enthalten sein muss. Deswegen ist der Diamant zu "C" als erster Test gut geeignet. Der Diamant enthält:

  • die Zeile ..A.. mit nur einem Buchstaben,
  • die Zeile .B.B. mit zwei Buchstaben und Leerzeichen außen und in der Mitte sowie
  • die Zeile C...C, die keine Leerzeichen an den äußeren Rändern hat.

Der Diamant zu "D" ist nicht erforderlich, weil er keine weiteren Zeilentypen generiert. Der Weg bis zum "B"-Diamanten reicht dagegen nicht aus, da ihm die Zeile mit Leerzeichen außen und in der Mitte fehlt. Dementsprechend sieht der erste Akzeptanztest folgendermaßen aus:

describe('Diamond', () => {
  it('should return the whole diamond', () => {
    expect(diamond("C")).toEqual(
    "..A..\n" +
    ".B.B.\n" +
    "C...C\n" +
    ".B.B.\n" +
    "..A.."
    );
  });
});

Im nächsten Schritt würde die Londoner Schule einen inneren Test schreiben, der dazu dient, die zusammenarbeitenden Funktionen zu identifizieren und die Methodik der obersten Implementierungsebene mit Mocks zu testen. Die Münchner Schule setzt stattdessen auf das Fake-It-Konzept und liefert eine erste Fake-Implementierung.

const diamond = () =>
    "..A..\n" +
    ".B.B.\n" +
    "C...C\n" + 
    ".B.B.\n" +
    "..A..";

Dadurch ist der erste Test erfolgreich und der Spezialfall für "C" umgesetzt. Dabei ist die Implementierung nicht echt, da sie nur für den einen Spezialfall gilt. Die nächsten Schritte bei der Münchner Schule bestehen darin, über Refactoring den Fake schrittweise durch realen Code zu ersetzen.

Eine offensichtliche strukturelle Gemeinsamkeit ist der Zeilenumbruch. Um später die einzelnen Zeilen zu generieren, bietet es sich an, den Zeilenumbruch zu externalisieren:

const diamond = () =>
   ["..A..",
    ".B.B.",
    "C...C",
    ".B.B.",
    "..A.."].join("\n");

Nun ist es sinnvoll, die einzelnen Zeilen zu betrachten. Insbesondere die Mittellinie ist ein einfacher Kandidat, da sie laut Beschreibung den gewählten Buchstaben verwendet und dazwischen aus einer Anzahl von Leerzeichen besteht. Es ist außerdem an der Zeit, aus dem ersten grünen Akzeptanztest herauszubrechen und einen Zwischentest einzuführen:

describe("middleLine", () => {
  it('should return a middle Line for a letter', () => {
    expect(middleLine("C")).toEqual("C...C");
  });
});

Dafür genügt zunächst eine einfache Fake-Implementierung:

const middleLine = letter => "C...C";

Bei "C" handelt es sich um den Buchstaben aus dem Eingangsparameter letter:

const middleLine = letter => letter + "..." + letter;

Als Nächstes ist es sinnvoll, die Leerzeichen in der Mitte zu ersetzen. Die Funktion "space" generiert sie, entwickelt mit dem passenden Zwischentest:

describe("spaces", () => {
  it('should return a given number of spaces', () => {
    expect(spaces(3)).toEqual("...");
  });
});

const spaces = n => ".".repeat(n);

Sobald die Funktion existiert, lässt sich der String "..." durch den Aufruf spaces(3) ersetzen:

const middleLine = letter => letter + spaces(3) + letter;

Die Anzahl der Leerzeichen ist nun durch eine Zahl ausgedrückt und lässt sich somit für andere Buchstaben als C berechnen. In der Rechnung 1 + 2 * 1 ist die letzte 1 die Buchstabenposition für "C". Bei "B" wäre der Wert 0 und bei "D" 2. Auf der Grundlage lässt sich eine weitere Funktion zur Berechnung der Buchstabenposition plus dazugehörigem Zwischentest entwickeln. Nach dem Schema geht es weiter, bis kein Fake mehr vorhanden ist. Listing 1 zeigt die vollständige Implementierung.

Bei den zuletzt gezeigten Schritten ist das Kernmuster der Schule gut zu beobachten: das Backward-Calculation-Pattern. Dabei ersetzt man immer wieder einen Wert durch die Berechnung, die zu ihm führt.

Im Vergleich zu anderen TDD-Stilen hat die Münchner Schule einige Vor- und einige Nachteile.