Eine CalDAV-Testbench für die testgetriebene Entwicklung — Teil 2: Die Test-Support-Schnittstelle in der Praxis

Sven Ruppert

Einbindung als test-bezogene Abhängigkeit

Der zweite Teil verlässt die erörterte Höhe des ersten Teils und wendet sich der Praxis zu. Sie beginnt bei einer schlichten Voraussetzung, die gleichwohl Beachtung verdient: Bevor sich auch nur eine einzige Prüfung gegen den Prüfstand schreiben lässt, muss dieser im Aufbau des Projekts bereits vorhanden sein. Die Art und Weise, in der er aufgenommen wird, ist dabei kein gleichgültiges Detail, sondern spiegelt seine innere Ordnung wider.

Die Quellen zu diesem Open-Source-Projekt befinden sich auf GitHub unter https://8g8.eu/caldav

Der Prüfstand wird als testbezogene Abhängigkeit eingebunden, also ausschließlich im Geltungsbereich der Prüfungen und ohne jede Spur im ausgelieferten Erzeugnis. Im Maven-Aufbau genügt dafür der folgende Eintrag:

<dependency>
  <groupId>com.svenruppert</groupId>
  <artifactId>caldav-testbench</artifactId>
  <version>00.10.00</version>
  <scope>test</scope>
</dependency>

Die Beschränkung auf den Testbereich ist mehr als nur eine Förmlichkeit. Eine auf den Testpfad begrenzte Abhängigkeit vergrößert die Angriffsfläche des Produktivkerns nicht und erscheint in keiner ausgelieferten Stückliste; die Sichtung durch Sicherheitswerkzeuge in der durchgehenden Integration wird dadurch spürbar entlastet. Dieses Hauptartefakt steuert den Server samt der vollständigen Test-Support-Schnittstelle — alles, was die meisten Prüfungen verlangen.

Neben dem Hauptartefakt besteht ein gesondertes Artefakt mit dem Classifier tests, das man nur dann hinzunimmt, wenn eine Prüfung die JUnit-Erweiterung verwendet:

<dependency>
  <groupId>com.svenruppert</groupId>
  <artifactId>caldav-testbench</artifactId>
  <version>00.10.00</version>
  <classifier>tests</classifier>
  <scope>test</scope>
</dependency>

Der Grund für diese Zweiteilung ist gut nachvollziehbar. Das Hauptartefakt enthält den Server und die gesamte Fixture-API; das gesonderte Artefakt fügt allein die CalDavExtension hinzu. Diese Erweiterung hängt von der Erweiterungs-API von JUnit Jupiter ab und kann daher nicht im Hauptartefakt wohnen, das von einer solchen Abhängigkeit frei bleiben soll. Wer die selbsttätige Lebenszyklussteuerung der Erweiterung nutzen möchte, nimmt das Tests-Artefakt hinzu; wer den Prüfstand lieber von Hand startet und schließt, kommt ohne es aus.

Hinter dieser Aufteilung steht eine bewusste Grenzziehung zwischen dem, was die öffentliche Schnittstelle bildet, und dem, was bloßes Umsetzungsdetail bleibt. Als öffentlich und auf Dauer verlässlich gelten die Pakete com.svenruppert.caldav.server und com.svenruppert.caldav.testsupport sowie der Erweiterungspunkt com.svenruppert.caldav.store.CalendarStore. Pakete wie handler, report und xml hingegen sind ausdrücklich als innere Mechanik gekennzeichnet und dürfen sich vor der Fassung 1.0 noch ändern; der nutzende Code sollte sie weder sehen noch berühren. Die Art der Einbindung spiegelt somit genau jene Trennung wider, die bereits den Entwurf des Prüfstands geleitet hat.

Damit ist die Voraussetzung geschaffen. Der Prüfstand ist im Aufbau gegenwärtig, die öffentliche Schnittstelle steht bereit, und die innere Mechanik bleibt verborgen. Im folgenden Kapitel führen wir auf dieser Grundlage den ersten wirklichen Test durch.

Der erste Test mit CalDavFixture und CalDavExtension

Nachdem der Prüfstand aufgebaut ist, lässt sich der erste Test schreiben. Dabei stehen zwei Wege offen, einen laufenden Prüfstand zu gewinnen, und der Unterschied zwischen ihnen liegt allein darin, wer sich um seinen Lebenszyklus kümmert — die Prüfung selbst oder eine Erweiterung, die ihr diese Sorge abnimmt.

Der erste Weg führt über die CalDavFixture, die der Test innerhalb eines try-Blocks mit Ressourcenverwaltung öffnet und am Ende selbsttätig wieder schließt:

@Test
void createsAnEvent() throws Exception {
  try (CalDavFixture caldav = CalDavFixture.startWithCalendars("personal")) {

    HttpResponse<String> response = caldav.putEvent("personal", "standup.ics",
        event("standup")
            .summary("Daily Stand-up")
            .starts(Instant.parse("2026-06-12T08:00:00Z"))
            .ends(Instant.parse("2026-06-12T08:15:00Z")));

    assertEquals(201, response.statusCode());
  }
}

An diesem schlichten Beispiel lassen sich drei Eigenschaften ablesen, die den Prüfstand auszeichnen. Erstens erzeugt erstartWithCalendars einen frischen Server auf einem vom Betriebssystem zugeteilten Port; die zugrunde liegende Konfiguration verlangt die Portnummer 0, woraufhin das Betriebssystem einen freien Port zuteilt und kein Streit um eine fest vergebene Nummer entstehen kann. Zweitens sendet die Fixture mit Methoden wie putEvent echte HTTP-Anfragen über die Rückschleife des eigenen Rechners, getragen vom bordeigenen JDK; es wird also nicht etwa ein Verhalten vorgetäuscht, sondern tatsächlich kommuniziert. Drittens schließt sich die Fixture am Ende des try-Blocks von selbst, sodass der nächste Test wieder aus einem geordneten Zustand beginnt. Schon die einzige Zusicherung dieses Tests — dass ein wohlgeformter Termin mit dem Status 201 quittiert wird — liest sich als Aussage über den Vertrag.

Der zweite Weg führt über die JUnit-Erweiterung CalDavExtension, die über @RegisterExtension in die Prüfklasse eingebracht wird und der Prüfung den Auf- und Abbau vollständig abnimmt:

@RegisterExtension
static final CalDavExtension caldav = CalDavExtension.withCalendar("personal");

@Test
void createsAnEvent() throws Exception {
  HttpResponse<String> response = caldav.fixture().putEvent("personal", "standup.ics",
      event("standup")
          .summary("Daily Stand-up")
          .starts(Instant.parse("2026-06-12T08:00:00Z"))
          .ends(Instant.parse("2026-06-12T08:15:00Z")));

  assertEquals(201, response.statusCode());
}

Die Erweiterung startet vor jedem Test eine frische Fixture und schließt sie danach wieder; der Test erreicht den laufenden Prüfstand über caldav.fixture(). Der wiederkehrende Auf- und Abbau verschwindet damit aus dem Prüfkörper, der sich nun ganz auf die zu prüfende Wechselwirkung beschränkt. Zu beachten ist allein, dass die CalDavExtension im tests-Artefakt wohnt und daher, wie im vorigen Kapitel beschrieben, eigens aufzunehmen ist.

Welcher der beiden Wege der angemessene ist, hängt vom Zuschnitt der Prüfung ab. Verlangt sie ausdrückliche Kontrolle über den Lebenszyklus — etwa, weil mehrere Fixtures innerhalb eines Tests nebeneinander bestehen sollen —, so empfiehlt sich die CalDavFixture im try-Block. Soll sich die Prüfung hingegen gar nicht erst mit Start und Stopp befassen, so nimmt ihr die CalDavExtension diese Last ab. Beide Wege senden echte HTTP-Anfragen, und beide beginnen aus einem frischen, geordneten Zustand.

So bescheiden dieser erste Test auch ist, führt er die ausführbare Dokumentation erstmals im praktischen Gewand vor. Das folgende Kapitel wendet sich der Frage zu, wie sich solche Prüfungen lesbar halten lassen, wenn die Testdaten und die erwarteten Antworten umfangreicher werden — durch sprechende Fixtures für die Daten und durch strukturierte Zusicherungen für die Antworten.

Lesbare Testdaten und strukturierte Zusicherungen

Sobald eine Prüfung über die bloße Anlage eines einzelnen Termins hinausgeht, treten zwei Quellen des Rauschens zutage, die die Lesbarkeit bedrohen. Die eine liegt am Eingang: das mühselige Verfertigen von iCalendar-Daten und XML-Anfragerümpfen von Hand, das den Blick auf das eigentlich Geprüfte verstellt. Die andere liegt am Ausgang: das umständliche Zerlegen jener vielteiligen Antworten, mit denen der Server auf eine Mehrfachabfrage oder einen Fehler reagiert. Die Test-Support-Schnittstelle begegnet beiden, und der Gewinn ist nicht bloß Bequemlichkeit, sondern Lesbarkeit — und eine lesbare Prüfung dokumentiert den Vertrag besser als eine, die unter ihrem eigenen Beiwerk verschwindet.

Am Eingang stehen zwei Erbauer. Die

liefern über einen sprechenden Aufruf lesbare iCalendar-Daten, ohne dass man eine einzige Zeile des Formats von Hand schreiben müsste; aus entsteht ein vollständiger, gültiger Termin.
Die WebDavXmlFixtures wiederum stellen die Rümpfe für PROPFIND und REPORT bereit, etwa über calendarMultiget(…) für das gebündelte Abrufen oder calendarQueryAll(…) für die Abfrage. Am Ausgang lösen strukturierte Zusicherungen das Zerlegen der Antworten aus. Die MultiStatusAssert prüft eine Mehrfachstatus-Antwort mit Status 207 in den
benannten Schritten. Das folgende Beispiel führt beides zusammen:

@Test
void multigetReportsStatusPerResource() throws Exception {
  try (CalDavFixture caldav = CalDavFixture.start()) {

    caldav.putEvent("personal", "cafe.ics",
        event("cafe")
            .summary("Cafe")
            .starts(Instant.parse("2026-06-12T10:00:00Z"))
            .ends(Instant.parse("2026-06-12T11:00:00Z")));

    HttpResponse<String> multiget = caldav.report("personal",
        WebDavXmlFixtures.calendarMultiget(
            "/calendars/personal/cafe.ics",
            "/calendars/personal/missing.ics"));

    assertEquals(207, multiget.statusCode());
    assertThatMultistatus(multiget.body())
        .containsHref("/calendars/personal/cafe.ics")
        .hasStatus("/calendars/personal/cafe.ics", 200)
        .hasCalendarData("/calendars/personal/cafe.ics")
        .hasStatus("/calendars/personal/missing.ics", 404);
  }
}

Die beiden Erbauer liefern hier den Termin und den Rumpf der Mehrfachabfrage, ohne dass eine Zeile im iCalendar- oder XML-Prüfkörper sichtbar ist. Die Antwort, ein Mehrfachstatus mit dem Status 207, wird anschließend nicht von Hand durchsucht, sondern über assertThatMultistatus in lesbaren Schritten erschlossen: Der vorhandene Verweis wird bestätigt, der zugehörige Einzelstatus 200 geprüft, das Vorhandensein der Kalenderdaten zugesichert und für die fehlende Ressource der Status 404 erwartet. Für strukturierte Fehlermeldungen gibt es mit assertThatErrorResponse ein entsprechendes Gegenstück, das eine als XML-Datei gespeicherte Fehlerantwort auf dieselbe Weise zugänglich macht.

Wo das Verhalten des Clients mehr zählt als die Einzelheiten des HTTP-Verkehrs, empfiehlt sich ein zweiter, szenarienhafterer Stil. Über CalendarScenario gliedert sich eine Prüfung in die knappen Schritte des Anordnens, Handelns und Prüfens, wobei der gesamte XML-Aufwand verschwindet:

@Test
void findsEventsInsideTheRequestedWindow() throws Exception {
  try (CalDavFixture caldav = CalDavFixture.start()) {
    caldav.givenCalendar("personal")
        .contains(event("standup")
            .summary("Daily Stand-up")
            .starts(Instant.parse("2026-06-12T08:00:00Z"))
            .ends(Instant.parse("2026-06-12T08:15:00Z")))
        .contains(event("offsite")
            .summary("Team Offsite")
            .starts(Instant.parse("2026-07-01T08:00:00Z"))
            .ends(Instant.parse("2026-07-01T17:00:00Z")))
        .whenQuery("20260612T000000Z", "20260613T000000Z")
        .thenContains("standup.ics")
        .thenDoesNotContain("offsite.ics");
  }
}

Diese Prüfung ordnet zwei Termine an, fragt ein Fenster ab und sichert zu, welcher Termin hineinfällt und welcher nicht. Der gesamte XML-Aufwand ist verschwunden, und doch bleiben die Anfragen echte HTTP-Anfragen über den laufenden Prüfstand; der szenenhafte Stil verbirgt allein das wiederkehrende Beiwerk, nicht die tatsächliche Wechselwirkung. Wer wiederkehrende Anordnungen über mehrere Prüfungen hinweg benötigt, findet in den ScenarioFixtures vorbereitete Kalenderanordnungen, zu denen auch das Laden goldener Vorlagen aus dem Klassenpfad gehört.

So gewinnt die Prüfung an beiden Enden ihre Lesbarkeit zurück, und mit der Lesbarkeit wächst ihre Kraft als Dokumentation. Das folgende Kapitel wendet sich der Eigenschaft zu, die jede dieser Prüfungen erst verlässlich wiederholbar macht: dem Determinismus als Entwurfsprinzip.

Determinismus als Entwurfsprinzip

Was jede der bislang gezeigten Prüfungen erst verlässlich wiederholbar macht, ist eine Eigenschaft, die der Prüfstand nicht dem Zufall verdankt, sondern ausdrücklich anstrebt: der Determinismus. Er ist damit jene Stelle, an der Teil 2 den Bogen zu den im ersten Teil benannten Übeln zurückschlägt — zur Zeit, die sich gegen einen fremden Server nicht anhalten lässt, und zur Wiederkehr, deren Ergebnis vom heutigen Datum abhängt. Eben diesen begegnet die Konfiguration des Prüfstands unmittelbar, und sie tut es über drei Stellschrauben, die auf der CalDavServerConfig sitzen.

Die erste ist der einstellbare Zeitgeber, ein fester Clock, der den gegenwärtigen Augenblick einfriert; die zweite ist der ETag-Startwert, der die Kennungen vorhersagbar macht; die dritte ist die Obergrenze der Wiederkehrentfaltung, die jede Reihe in einer endlichen Schranke hält. Das folgende Beispiel führt alle drei zusammen:

@Test
void fixedClockAndEtagSeedMakeResponsesReproducible() throws Exception {
  CalDavServerConfig config = CalDavServerConfig.builder()
      .port(0)
      .calendarIds(List.of("personal"))
      .clock(Clock.fixed(Instant.parse("2026-06-12T12:30:00Z"), ZoneOffset.UTC))
      .etagSeed(41)
      .recurrenceMaxOccurrences(10)
      .build();

  try (CalDavFixture caldav = CalDavFixture.start(config)) {
    HttpResponse<String> created = caldav.putEvent("personal", "seeded.ics",
        event("seeded")
            .starts(Instant.parse("2026-06-12T10:00:00Z"))
            .ends(Instant.parse("2026-06-12T11:00:00Z")));

    assertEquals(201, created.statusCode());
    assertEquals("\"000000000000002a\"",
        created.headers().firstValue("ETag").orElseThrow());
  }
}

Die Wirkung der drei Stellschrauben lässt sich am Ergebnis ablesen. Der eingefrorene Zeitgeber, der auf den 12. Juni 2026 um 12:30 Uhr festgelegt ist, sorgt dafür, dass jeder erzeugte Zeitstempel — etwa das Änderungsdatum einer Ressource — bei jedem Lauf derselbe bleibt. Der Startwert 41 macht die ETag-Vergabe vorhersagbar: Die erste abgelegte Ressource erhält die Kennung 42, hier in hexadezimaler Schreibweise als "000000000000002a", und nicht etwa einen jedes Mal anderen Wert. Die auf zehn gesetzte Obergrenze hält schließlich selbst eine unbegrenzte Wiederkehr in einer endlichen, schnellen Schranke. Über diese drei hinaus lassen sich auch die Größenschranken für XML- und iCalendar-Rümpfe — xmlMaxBytes und iCalendarMaxBytes — festlegen, was eine Prüfung des Status 413 ohne riesige künstliche Datenmengen erlaubt.

Was der eingefrorene Zeitgeber und der feste Startwert allein nicht erfassen, übernehmen zwei Hilfen der Test-Support-Schnittstelle. Das TimeWindow benennt CalDAV-Zeitbereiche — über on(...) für einen Tag, weekOf(...) für eine Woche oder between(...) für einen frei gewählten Bereich — und ersetzt damit die rohen Zeitstempel-Zeichenketten einer Abfrage durch sprechende Bereiche. Die ResponseSnapshots wiederum normalisieren jene Werte, die selbst bei eingefrorener Zeit flüchtig bleiben: voran den vom Betriebssystem zugeteilten Port der Rückschleife, daneben die ETag-Kennungen und die HTTP-Datumsangaben, die sie sämtlich durch stabile Platzhalter ersetzen. Das folgende Beispiel führt beide zusammen:

@Test
void namedWindowsAndSnapshotsKeepAQueryReproducible() throws Exception {
  try (CalDavFixture caldav = CalDavFixture.start()) {
    caldav.givenCalendar("personal")
        .contains(event("standup")
            .starts(Instant.parse("2026-06-12T08:00:00Z"))
            .ends(Instant.parse("2026-06-12T08:15:00Z")))
        .whenQuery(TimeWindow.on(LocalDate.of(2026, 6, 12)))
        .thenContains("standup.ics");

    HttpResponse<String> report = caldav.report("personal",
        WebDavXmlFixtures.calendarQueryAll("<D:getetag/>"));

    String snapshot = ResponseSnapshots.normalise(report.body());
    assertTrue(snapshot.contains("\"<etag>\""));
  }
}

Hier benennt TimeWindow.on den abgefragten Tag, ohne dass eine rohe Zeitstempel-Zeichenkette im Prüfkörper erscheinen müsste, und der szenarienhafte Schritt sichert zu, dass der Termin in dieses Fenster fällt. Anschließend wird die Antwort eines Berichts über ResponseSnapshots.normalise von ihren flüchtigen Werten befreit, sodass sich der so gewonnene Abzug unabhängig vom jeweils zugeteilten Port und der erzeugten ETag-Kennung vergleichen lässt.

Erst das Zusammenspiel aus eingefrorener Zeit, vorhersagbaren Kennungen, beschränkter Wiederkehr und normalisierten Restwerten verwandelt eine Prüfung von einer Mutmaßung in eine verlässliche Aussage. Damit ist der Boden für das folgende Kapitel bereitet, das den Blick von der vorhersagbaren auf die absichtlich fehlerhafte Antwort richtet.

Fehlerorientierte Prüfung

Nachdem das vorige Kapitel die Antwort des Prüfstands berechenbar gemacht hat, wendet sich dieses Kapitel dem Unerfreulichen zu: der absichtlich fehlerhaften Antwort. Der Grund liegt in einer schlichten Beobachtung. Gegen einen echten, geteilten Server lassen sich ein Dienstausfall, eine schleppende Antwort oder gar bewusst fehlerhafte serverseitige Daten kaum auf Verlangen herbeiführen — eben jene Randfälle aber sind es, an denen sich die Widerstandsfähigkeit eines Clients entscheidet. Der Prüfstand macht sie reproduzierbar und rückt damit das schwer Prüfbare in Reichweite.

Das erste Mittel dazu ist der FaultInjector, den die Fixture über faults() zugänglich macht. Mit failNext(method, status) bringt man den nächsten Aufruf einer bestimmten Methode zum Scheitern, mit delayNext(method, duration) verzögert man ihn um eine festgelegte Dauer:

@Test
void clientErrorAndTimeoutHandlingCanBeDriven() throws Exception {
  try (CalDavFixture caldav = CalDavFixture.start()) {
    caldav.faults().failNext("GET", 503);
    assertEquals(503, caldav.get("personal", "event.ics").statusCode());

    caldav.faults().delayNext("GET", Duration.ofMillis(50));
    assertEquals(404, caldav.get("personal", "event.ics").statusCode());
  }
}

Die beiden eingestreuten Störungen wirken jeweils nur auf den unmittelbar betroffenen Aufruf. Die erste lässt die folgende GET-Anfrage mit Status 503 scheitern und führt damit den Fehlerpfad des Clients vor, der andernfalls kaum je durchschritten würde. Die zweite verzögert die darauffolgende GET-Anfrage um die angegebene Dauer und prüft so den Zeitüberschreitungspfad; im Übrigen antwortet der Server wie gewohnt, hier mit dem Status 404 für die nicht vorhandene Ressource. Da jede Störung von einem einzigen Aufruf verbraucht wird, kehrt der Prüfstand danach von selbst zu seinem regulären Verhalten zurück.

Das zweite Mittel besteht im Gegensatz zwischen zwei Betriebsmodi. Der strenge Modus, die Voreinstellung, weist ungültige iCalendar-Eingaben mit Status 400 zurück; der nachsichtige Modus hingegen nimmt auch fehlerhafte serverseitige Daten an. Das folgende Beispiel stellt beide nebeneinander:

@Test
void strictRejectsAndLenientToleratesMalformedData() throws Exception {
  try (CalDavFixture strict = CalDavFixture.start()) {
    assertEquals(400,
        strict.putCalendar("personal", "broken.ics", "not an iCalendar").statusCode());
  }

  CalDavServerConfig lenient = CalDavServerConfig.builder()
      .port(0)
      .calendarIds(List.of("personal"))
      .mode(CalDavMode.LENIENT)
      .build();

  try (CalDavFixture caldav = CalDavFixture.start(lenient)) {
    assertEquals(201,
        caldav.putCalendar("personal", "broken.ics", "not an iCalendar").statusCode());
  }
}

Derselbe unbrauchbare Rumpf wird vom strengen Voreinstellungsmodus mit dem Status 400 zurückgewiesen und vom nachsichtigen Server mit dem Status 201 angenommen. Der nachsichtige Modus dient dabei nicht der Bequemlichkeit, sondern einem klar umrissenen Zweck: Er erlaubt zu prüfen, ob der untersuchte Client nicht in sich zusammenbricht, wenn ihm der Server etwas Unparsbares vorsetzt — eine Lage, die ein gutmütiger echter Server gar nicht erst herbeiführen würde.

So erweist sich die fehlerorientierte Prüfung nicht als Gegenteil der ausführbaren Dokumentation, sondern als ihre Erweiterung auf das Unerfreuliche: Auch eine festgeschriebene Zurückweisung und eine geduldete Fehlerhaftigkeit gehören zum Vertrag, den der Client kennen muss. Schlägt eine solche Prüfung fehl, so möchte man freilich in den Prüfstand hineinsehen können — und eben dieser Inspizierbarkeit widmet sich das folgende Kapitel.

Inspizierbarkeit des Prüfstandzustands

Schlägt eine Prüfung fehl, so steht man zunächst vor einer kargen Zahl — einem erwarteten Status, der nicht eingetreten ist — und weiß noch nicht, woran es lag. In dieser Lage möchte man in den Prüfstand hineinsehen können, statt im Dunkeln zu tappen. Eben dazu stellt die Test-Support-Schnittstelle drei ausschließlich für den Test gedachte Mittel der Inspektion bereit, die in der Praxis Hand in Hand gehen und sich daher gemeinsam vorführen lassen:

@Test
void inspectingTheHarnessAfterAnInteraction() throws Exception {
  try (CalDavFixture caldav = CalDavFixture.startWithCalendars("personal")) {
    caldav.putEvent("personal", "logged.ics",
        event("logged")
            .starts(Instant.parse("2026-06-12T10:00:00Z"))
            .ends(Instant.parse("2026-06-12T11:00:00Z")));

    assertEquals("PUT", caldav.interactions().last().orElseThrow().method());
    assertEquals(201, caldav.interactions().last().orElseThrow().statusCode());
    assertTrue(caldav.storeView().resources("personal").contains("logged.ics"));
    assertEquals("personal", caldav.diagnostics().calendars().get(0));
    assertTrue(caldav.diagnostics().baseUri().toString().startsWith("http://127.0.0.1:"));
  }
}

Die drei Mittel beleuchten jeweils eine andere Seite desselben Zustands. Das InteractionLog zeichnet die ausgetauschten Anfragen auf; über interactions().last() erreicht man den jüngsten Eintrag, hier eine PUT-Anfrage mit dem Status 201, und sieht damit, welche Anfrage tatsächlich gesendet wurde und wie der Server sie beantwortet hat. Die StoreView legt über storeView().resources("personal") offen, welche Ressourcen wirklich in der Sammlung liegen — hier logged.ics —, sodass sich bestätigen lässt, ob ein Schreibvorgang tatsächlich eingegangen ist. Die ServerDiagnostics halten schließlich über diagnostics() die Grunddaten des laufenden Servers bereit: die Sammlungen über calendars(), die Basisadresse der Rückschleife über baseUri() sowie die geltenden Schranken und den Betriebsmodus.

Im Zusammenspiel verwandeln diese drei eine fehlgeschlagene Prüfung von einer bloßen Behauptung — „erwartet 201, erhalten 400″ — in eine nachvollziehbare Beobachtung, die verrät, welche Anfrage gesendet wurde, was der Bestand wirklich enthält und wie der Server eingestellt war. Wichtig ist dabei, dass diese Inspektion bewusst auf den Test beschränkt bleibt: Sie hat im Produktivbetrieb nichts zu suchen und ist kein Teil des ausgelieferten Protokolls. Ihr einziges Ziel ist es, eine Prüfung für denjenigen lesbar zu machen, der sie verfasst hat.

So bestätigt die einsehbare innere Verfassung des Prüfstands, was der Vertrag tatsächlich bewirkt hat, und fügt sich damit dem Leitmotiv der ausführbaren Dokumentation ein letztes Mal an. Vom Beobachten des Bestehenden führt der Weg nun zu seiner Erweiterung und zu seinen bewussten Grenzen, denen sich das abschließende Kapitel widmet.

Erweiterung, Grenzen und Ausblick

Nachdem das vorige Kapitel gezeigt hat, wie sich der innere Zustand des Prüfstands beobachten lässt, bleibt zum Abschluss zu klären, an welcher Stelle er sich erweitern lässt, wo seine bewussten Grenzen verlaufen und in welchen größeren Zusammenhang er gehört.

Erweitern lässt sich der Prüfstand an genau einem ausdrücklich vorgesehenen Punkt: dem CalendarStore. In der Voreinstellung hält ein speicherinterner Bestand die Kalenderressourcen, und für die weitaus meisten Prüfungen ist eben dies das Richtige, denn er ist schnell, vorhersagbar und verliert sich mit dem Testlauf. Wer jedoch eine abweichende Datenhaltung benötigt — etwa, um einen besonderen Bestand vorzugeben oder ein bestimmtes Verhalten der Ablage nachzubilden —, setzt eine eigene Umsetzung dieser Schnittstelle ein, ohne die übrige Mechanik des Servers auch nur zu berühren. Dieser eine offene Punkt ist sorgfältig von allem Übrigen abzugrenzen: Die Pakete, die die Verarbeitung von HTTP-Methoden, Berichten und XML übernehmen, sind ausdrücklich als innere Mechanik gekennzeichnet und dürfen sich vor der Fassung 1.0 noch ändern; sie sind weder zum Ansehen noch zum Erweitern bestimmt. Die Erweiterbarkeit ist somit nicht großzügig, sondern gezielt — ein einziges Tor in einer ansonsten geschlossenen Mauer.

Ebenso klar wie der eine offene Punkt sind die bewussten Auslassungen, die schon den ersten Teil durchzogen und hier noch einmal nüchtern zu nennen sind. Der Prüfstand beherrscht keine Erkennungssequenz, verlangt keine Authentifizierung, spannt kein TLS auf, bildet keine Synchronisationsmechanik nach und legt nichts über den einzelnen Lauf hinaus dauerhaft ab; auch die Entfaltung wiederkehrender Termine bleibt mit Bedacht beschränkt. Wer eines dieser Dinge benötigt, ist beim Prüfstand an der falschen Adresse und sollte stattdessen zu einem echten Kalenderserver greifen. Damit schließt sich der Kreis zu jenem frühen Kapitel des ersten Teils, das den Prüfstand als eigene Gattung gegen den verkleinerten Produktivserver abgesetzt hat: Seine Grenzen sind nicht das, was ihm fehlt, sondern das, was ihn ausmacht.

Der Ausblick greift schließlich zum letzten Mal das Leitmotiv der ausführbaren Dokumentation auf. Ein Client, der testgetrieben gegen den Prüfstand erarbeitet wurde, trägt die Gewissheit in sich, das benötigte Stück des Protokolls richtig zu sprechen — eine Gewissheit, die in jeder erfüllten Prüfung niedergelegt und gegen jede künftige Änderung gesichert ist. Ein solcher Client lässt sich anschließend mit Zuversicht in eine Anwendung einbinden, etwa um eine Kalenderdarstellung mit einem CalDAV-Server zu verbinden, ohne dass die mühsame Frage nach der Protokolltreue erneut aufgeworfen werden muss. Der Prüfstand erweist sich auf diese Weise nicht als Selbstzweck, sondern als Grundlage: ein bescheidenes, scharf umrissenes Werkzeug, das seinen Wert weniger aus dem zieht, was es selbst tut, als aus dem, was es anderen testgetrieben zu bauen erlaubt. Eben darin liegt, am Ende beider Teile, sein eigentlicher Beitrag.

Total
0
Shares
Previous Post

Eine CalDAV-Testbench für die testgetriebene Entwicklung — Teil 1: Vom Problem zum Prüfstand

Next Post

Runes of Reason – ein RPG als Spielwiese für KI-Integration

Related Posts