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

Sven Ruppert

Die Mühsal echter Kalenderserver im Test

Wer schon einmal die Logik zur Kalendersynchronisation gegen einen vollwertigen CalDAV-Server geprüft hat, kennt das eigentümliche Unbehagen, das sich einstellt, sobald eine Prüfung beim ersten Lauf grün leuchtet und beim zweiten Lauf grundlos scheitert. Der Verdacht fällt zunächst auf den eigenen Quelltext, doch die Ursache liegt häufig anderswo: nicht in der zu prüfenden Logik, sondern in der Beschaffenheit des Gegenübers, gegen den geprüft wird. Ein echter Kalenderserver ist für den Produktivbetrieb gemacht, nicht für die Reproduzierbarkeit, die eine belastbare Prüfung verlangt, und genau dieser Widerspruch erzeugt eine Reihe von Schwierigkeiten, die sich mit Sorgfalt zwar mildern, aber nicht beseitigen lassen.

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

Die erste dieser Schwierigkeiten ist der fortbestehende Zustand zwischen den Läufen. Ein Termin, den die vorangegangene Prüfung angelegt hat, bleibt auf dem Server liegen und wird der nächsten Prüfung als unerwarteter Altbestand begegnet. Damit verlieren die einzelnen Prüfungen ihre Unabhängigkeit voneinander; ihre Reihenfolge beginnt eine Rolle zu spielen, das Aufräumen wird zur fehleranfälligen Pflicht, und ein einziger fehlgeschlagener Durchgang vergiftet die folgenden, weil er den Server in einem Zustand zurücklässt, mit dem niemand gerechnet hat.

Die zweite Schwierigkeit ist die Netzlatenz. Ein echter Server lebt jenseits einer Verbindung, und jede Anfrage trägt das Risiko einer Zeitüberschreitung in sich. So entstehen sporadische Fehlschläge, die mit der eigentlich zu prüfenden Sache nichts zu tun haben und sich obendrein der Wiederholbarkeit entziehen: jener Aufbau, der allein auf der Maschine der durchgehenden Integration scheitert, und auch dort nur gelegentlich, raubt mehr Vertrauen in die Prüfungen, als zehn echte Fehler es vermögen.

Die dritte Schwierigkeit betrifft die Zeit selbst, die sich an einem fremden Server nicht anhalten lässt. Kalenderlogik ist ihrem Wesen nach an den gegenwärtigen Augenblick gebunden, und eine Zeitbereichsabfrage, die den heutigen Tag einschließt, liefert an jedem Tag ein anderes Ergebnis. Eine Prüfung, die im Mai noch verlässlich erschien, kann im Juni stillschweigend ihre Aussagekraft verlieren, weil sich die Welt unter ihr weitergedreht hat.

Die vierte Schwierigkeit, die schließlich die Dritte verschärft, betrifft die Wiederkehr. Wird ein wiederkehrender Termin gegen das tatsächliche Datum entfaltet, hängt die Anzahl der Vorkommen innerhalb eines Fensters davon ab, wann die Prüfung stattfindet. Jede Zusicherung über diese Anzahl wird damit zur Mutmaßung, und gerade die Wiederkehr, die in der Praxis am häufigsten Fehler verbirgt, lässt sich auf diese Weise am schlechtesten festnageln.

Auf diese vier Übel antwortet die gängige Praxis mit zwei Auswegen, die beide am Kern vorbeigehen. Der eine greift zum verkleinerten Produktivserver, etwa zu einem Abbild eines vollständigen Kalenderservers in einem Container, und erbt damit sämtliche genannten Leiden, denen er nun lediglich das betriebliche Gewicht eines bei jedem Aufbau zu startenden Abbildes hinzufügt. Der andere greift zum grob nachgebildeten Doppelgänger, der jedoch nicht das tatsächliche Verhalten des Servers nachbildet, sondern die Annahmen des Entwicklers darüber, wie der Server zu antworten habe. Ein solcher Doppelgänger besteht die Prüfung gerade deshalb, weil er die Irrtümer des Clients teilt, und verbürgt so nichts als die Übereinstimmung des Codes mit sich selbst.

Damit ist die eigentliche Frage gestellt. Sie lautet nicht, wie sich ein echter Server zu wohlverhalten überreden ließe, und auch nicht, wie er sich möglichst billig imitieren ließe, sondern ob es ein Gegenüber gänzlich anderer Art gibt — eines, das schnell und vorhersagbar ist, den Zustand bei jedem Lauf von Neuem ordnet, die Zeit stillzustellen erlaubt und zugleich das echte Protokollverhalten zeigt, auf das es ankommt. Eben dieses Gegenüber ist Gegenstand des folgenden Kapitels.

Ein Prüfstand ist kein verkleinerter Server

Die am Ende des vorigen Kapitels gestellte Frage nach einem Gegenüber gänzlich anderer Art lässt sich nur beantworten, wenn man sich zuvor von einer stillschweigenden Annahme löst: dass das Testgegenüber notwendigerweise eine Abart des Produktivservers sein müsse, sei es ein verkleinertes Abbild, sei es ein nachgebildeter Doppelgänger. Diese Annahme führt geradewegs in die im ersten Kapitel beschriebenen Übel zurück, weil sie das eigentliche Wesen der beiden Aufgaben verkennt. Ein Produktivserver beantwortet die Frage, wie sich viele Benutzer im laufenden Betrieb verlässlich und sicher bedienen lassen. Eine Prüfung stellt diese Frage nicht. Sie stellt eine andere Frage: ob der eigene Client das Protokoll korrekt implementiert. Und diese andere Frage verlangt nach einem Gegenüber, das anderen Gütekriterien gehorcht.

Drei solcher Kriterien treten an die Stelle der Vollständigkeit, die ein Produktivserver zu Recht anstrebt. Das Erste ist die Vorhersagbarkeit: Jeder Lauf beginnt aus einem bekannten, geordneten Zustand, nichts wird aus einem früheren Durchgang verschleppt, die Zeit lässt sich anhalten, und die Entfaltung wiederkehrender Termine bleibt in nachvollziehbaren Grenzen. Das Zweite ist die Geschwindigkeit: Ein Gegenüber, das im selben Prozess wohnt, kennt keine Netzlatenz, startet und endet in Sekundenbruchteilen und lässt sich daher für jede einzelne Prüfung von Neuem erzeugen. Das Dritte ist die Inspizierbarkeit: Der innere Zustand des Prüfstands ist offen, sodass eine fehlgeschlagene Prüfung nicht zur Mutmaßung über ein verschlossenes System gerät, sondern zur nachvollziehbaren Beobachtung wird.

Eben die Vollständigkeit, der ein echter Server seinen Wert verdankt, ist es, die ihn als Testgegenüber untauglich macht. Wer das einsieht, erkennt die erklärten Nichtziele des Prüfstands nicht länger als Mangel, sondern als bewusste Setzung. Dass er keine Erkennungssequenz beherrscht, keine Authentifizierung verlangt, kein TLS aufspannt, keine Synchronisationsmechanik nachbildet und nichts über den einzelnen Lauf hinaus dauerhaft ablegt, ist kein Eingeständnis von Unfertigkeit. Jede einzelne dieser Auslassungen entfernt eine Quelle von Rauschen oder Unbestimmtheit und schärft dadurch den Vertrag, den der Client einhalten soll. Die Grenze des Unterstützten ist hier nichts, wofür man sich zu entschuldigen hätte, sondern eine bewusst gezogene und sichtbar gemachte Linie — ein Gedanke, der in einem späteren Kapitel noch eigens zu entfalten sein wird.

Aus dieser gewollten Schmalheit folgt unmittelbar eine gestalterische Leichtigkeit, die ihrerseits kein Zufall ist, sondern der unmittelbare Lohn der Beschränkung. Weil der Prüfstand so wenig tut, kommt er mit dem bordeigenen HTTP-Server des JDK und einer einzigen schlanken Abhängigkeit für die Verarbeitung der iCalendar-Daten aus. Er benötigt weder einen Container noch einen gesonderten Prozess; er wohnt im Speicher des Testlaufs und vergeht mit ihm. So definiert sich der Prüfstand am Ende nicht über das, was er zu leisten vermag, sondern über das, was er mit Bedacht unterlässt. Welcher Ausschnitt des Protokolls bei dieser Beschränkung gleichwohl unverzichtbar bleibt und aus welchem Grund gerade dieser, ist Gegenstand des folgenden Kapitels.

Der bewusst geschnittene Ausschnitt von RFC 4791

Welcher Ausschnitt des Protokolls bei aller Beschränkung gleichwohl unverzichtbar bleibt, lässt sich nicht aus dem Standard selbst ablesen, sondern allein aus dem Gespräch, das der eigene Client tatsächlich mit dem Server führt. RFC 4791 beschreibt einen weiten Raum an Möglichkeiten; ein bestimmter Client nutzt davon nur wenige. Eben diese wenigen, und nicht den vollen Umfang, hat der Prüfstand nachzubilden. Das Auswahlprinzip ist mithin kein technisches, sondern ein an der Notwendigkeit ausgerichtetes: Aufgenommen wird, was der Client braucht, und nichts darüber hinaus.

Den Grund dieses Gesprächs bilden die Grundoperationen auf einer einzelnen Kalenderressource. Über PUT legt der Client eine Ressource an oder ersetzt eine bestehende, über GET liest er sie aus, über DELETE entfernt er sie. In allen drei Fällen wird die Ressource als adressierbarer Gegenstand innerhalb einer Sammlung verstanden, deren Pfad sie eindeutig benennt. Schon diese schlichte Dreiheit deckt einen großen Teil dessen ab, was ein Client im Alltag verlangt, und doch wäre sie für sich allein gefährlich unvollständig.

Denn sobald mehrere Beteiligte denselben Bestand verändern, genügt das bloße Schreiben nicht mehr. Hier tritt die bedingte Schreiboperation über die ETag-Kennung hinzu, die ich ohne Zögern als den eigentlichen Prüfstein bezeichne. Mit If-None-Match verlangt der Client, eine Ressource nur dann anzulegen, wenn an ihrer Stelle noch nichts liegt; mit If-Match verlangt er, eine Ressource nur dann zu ersetzen, wenn sie sich seit dem letzten Auslesen nicht verändert hat. In diesem Zusammenspiel wohnt die nebenläufige Konfliktbehandlung, und gerade sie gehört zu den Stellen, an denen ein Client am leichtesten fehlgeht. Ein Prüfstand, der die ETag-Bedingungen ernst nimmt und einen verletzten Vorbehalt mit dem gebührenden Fehlerstatus quittiert, zwingt den Client, dieses Verhalten von Beginn an korrekt zu handhaben.

Bevor ein Client lesen oder schreiben kann, muss er die vorhandenen Ressourcen auffinden und deren Eigenschaften erfassen. Diesem Zweck dient PROPFIND, das in seiner hier benötigten, knappen Form die Sammlung und ihre Mitglieder samt der wenigen Eigenschaften zurückgibt, auf die es ankommt — allen voran die ETag-Kennung, ohne die die bedingten Schreiboperationen ins Leere liefen. Mehr als dieses Mindestmaß verlangt der Prüfstand nicht, und weniger genügte nicht.

Den anspruchsvollsten Baustein bildet schließlich REPORT, und zwar in zwei deutlich unterschiedlichen Spielarten. Die erste ruft eine bereits bekannte Menge an Ressourcen in einem einzigen Zug ab und erspart dem Client das vielfache Nachfragen. Die zweite überlässt dem Server die Auswahl, insbesondere die Filterung nach einem Zeitbereich. Eben diese zweite Spielart ist der entscheidende Grund, weshalb der Prüfstand die iCalendar-Daten nicht bloß durchreichen darf, sondern verstehen muss: Er hat zu erkennen, welche Termine in das angefragte Fenster fallen, und er hat wiederkehrende Termine in eben jenem Fenster — und nur dort, in maßvollen Grenzen — zu entfalten. Hier endet die Unschuld des reinen Weiterreichens und beginnt die eigentliche Protokolltreue.

So ergibt sich der Ausschnitt nicht aus einer willkürlichen Linie, sondern aus der Notwendigkeit des Gesprächs: die Grundoperationen, die bedingte Schreiboperation, die Eigenschaftsabfrage und die beiden Berichtsformen. Die Grenze, an der dieser Ausschnitt endet, ist mit Bedacht gezogen, und gerade ihre bewusste Sichtbarkeit ist es, die das folgende Kapitel zum Gegenstand hat.

Ausführbare Dokumentation

Die im vorigen Kapitel betonte bewusste Sichtbarkeit der Grenze ist mehr als eine ordnende Geste; sie ist der Schlüssel zu einer Einsicht, die den Prüfstand über sein unmittelbares Handwerk hinaushebt. Sein Verhalten dient nicht allein dem Prüfen, sondern ist selbst eine Form der Dokumentation. Eine gegen ihn geschriebene Prüfung beschreibt nicht beiläufig, sondern ausdrücklich den Vertrag, den der Client einzuhalten hat, und sie tut dies mit einer Genauigkeit und Verbindlichkeit, die erläuternde Prosa niemals erreicht. Wo ein Fließtext beschreibt, wie sich der Server verhalten solle, führt die Prüfung vor, wie er sich verhält — und zwar nachprüfbar, bei jedem Lauf von Neuem.

Der eigentliche Reiz dieses Gedankens liegt in seiner Zweiseitigkeit. Auf der einen Seite belegt eine bestandene Prüfung das zugesagte Verhalten. Dass eine bedingte Schreiboperation bei verletztem Vorbehalt mit dem gebührenden Fehlerstatus quittiert wird, ist dann nicht eine in Worten festgehaltene Behauptung, die irgendwann einmal galt, sondern eine fortlaufend bestätigte Tatsache. Auf der anderen Seite, und das ist der weniger offensichtliche und darum lehrreichere Teil, macht eine Prüfung, die eine Zurückweisung festschreibt, gerade das Nichtunterstützte sichtbar. Verlangt ein Client etwas, das außerhalb des bewusst gezogenen Ausschnitts liegt, und antwortet der Prüfstand mit einer klaren Zurückweisung, so liegt diese Grenze nicht länger im Verborgenen, sondern ist ausgesprochen und festgeschrieben. Das erklärte Nichtziel verwandelt sich auf diese Weise vom bloßen Mangel in eine dokumentierte Grenze, die der Client kennen und beachten muss.

Hierin liegt die Überlegenheit dieser Dokumentationsform gegenüber der erläuternden Prosa. Eine in Worte gefasste Beschreibung trägt ihren Verfall in sich: Sobald sich das Verhalten ändert, entfernt sie sich stillschweigend von der Wirklichkeit, und niemand bemerkt die Lücke, bis sie Schaden angerichtet hat. Eine ausführbare Dokumentation kann auf diese Weise nicht veralten. Ändert sich das Verhalten, ohne dass die Prüfung erfolgt, schlägt sie fehl und ruft die Abweichung selbst zur Ordnung. Das laufende Verhalten und seine Prüfungen bilden damit eine einzige, in sich stimmige Quelle der Wahrheit, an der keine zweite, womöglich überholte Beschreibung vorbeizieht.

Diese Stärke ist freilich an eine maßvolle Einschränkung gebunden, die man nicht verschweigen sollte. Dokumentiert wird stets der Ausschnitt, also das tatsächliche Gespräch zwischen Client und Server, und nicht der gesamte Standard. Wer in den Prüfungen eine vollständige Beschreibung von RFC 4791 sucht, wird sie nicht finden — und soll sie auch nicht finden. Eben darin liegt kein Widerspruch zur Sache, sondern ihre Konsequenz: Die ausführbare Dokumentation beschreibt genau so viel, wie der Client braucht, und gewinnt ihre Verbindlichkeit gerade aus dieser Beschränkung.

So fügt sich der Gedanke der ausführbaren Dokumentation bruchlos an die Gestalt des Prüfstands an, wie sie die vorangegangenen Kapitel entworfen haben. Er bereitet zugleich eine letzte, reizvolle Wendung vor: dass ein Server, der selbst testgetrieben entstanden ist, durch sein dokumentierendes Verhalten das testgetriebene Bauen verbürgt. Eben diese Selbstbezüglichkeit ist Gegenstand des abschließenden Kapitels.

Die Rekursion des testgetriebenen Vorgehens

Die am Ende des vorigen Kapitels angedeutete Selbstbezüglichkeit verdient eine eigene Betrachtung, denn sie ist nicht bloß eine hübsche Pointe, sondern beschreibt das Verhältnis des Prüfstands zur Methode, der er dient, in ihrer ganzen Eigenart. Die Rekursion läuft in zwei Richtungen, und erst beide zusammen ergeben das vollständige Bild.

Die erste Richtung richtet den Blick auf die Entstehung des Prüfstands selbst. Sein Verhalten wurde nicht vorab in allen Einzelheiten entworfen und anschließend niedergeschrieben, sondern schrittweise erarbeitet, entlang einer Folge von Prüfungen, die zunächst fehlschlugen und dann, nach jeweils kleinstmöglichen Ergänzungen, erfolgreich wurden. Was die vorangegangenen Kapitel als gesicherte Gestalt beschrieben haben — die bedingte Schreiboperation, die Eigenschaftsabfrage, die beiden Berichtsformen —, ist der Niederschlag eben dieses Vorgehens. Der Prüfstand trägt die Disziplin, der er seine Form verdankt, gleichsam in seinem Inneren.

Die zweite Richtung blickt auf seinen Gebrauch. Eben dieser testgetriebene Server dient nun seinerseits als Gegenüber, gegen das sich die Clientlogik testgetrieben erarbeiten lässt. Wer einen CalDAV-Client schreibt, formuliert zuerst eine fehlschlagende Prüfung gegen den Prüfstand, sieht sie scheitern, ergänzt die kleinste Menge an Clientlogik, die sie erfüllt, und schreitet so von einer gesicherten Aussage zur nächsten. Der reizvolle Gedanke ist, dass das Werkzeug die Methode verkörpert, die es ermöglicht: Dieselbe Disziplin, die ihm seine Gestalt gab, ist die Disziplin, die er nun stützt.

Um diese bislang erörterte Bewegung greifbar zu machen, mag ein einziges, bewusst knapp gehaltenes Prüfbeispiel genügen. Es startet den Prüfstand, legt einen Termin über PUT an, liest ihn über GET zurück und sichert den Hin- und Rückweg samt der ETag-Kennung zu:

@Test
void roundTripPreservesEventAndYieldsEtag() throws Exception {
  try (CalDavFixture fixture = CalDavFixture.startWithCalendars("personal")) {

    HttpResponse<String> put = fixture.putEvent("personal", "lunch.ics",
        event("lunch")
            .summary("Lunch")
            .starts(Instant.parse("2026-06-12T11:00:00Z"))
            .ends(Instant.parse("2026-06-12T12:00:00Z")));

    assertEquals(201, put.statusCode());
    String etag = put.headers().firstValue("ETag").orElseThrow();

    HttpResponse<String> get = fixture.get("personal", "lunch.ics");
    assertEquals(200, get.statusCode());
    assertEquals(etag, get.headers().firstValue("ETag").orElseThrow());
    assertTrue(get.body().contains("SUMMARY:Lunch"));
  }
}

So knapp dieses Beispiel ist, zeigt es bereits die wesentlichen Züge. Der Prüfstand wird innerhalb eines try-Blocks mit Ressourcenverwaltung gestartet und am Ende selbsttätig wieder geschlossen, sodass jeder Lauf aus einem frischen, geordneten Zustand beginnt. Das Anlegen über PUT quittiert der Server mit dem Status 201 und einer ETag-Kennung; das Auslesen über GET liefert den Status 200, dieselbe Kennung und den zuvor abgelegten Inhalt. Vor allem aber liest sich die Prüfung selbst als Aussage über den Vertrag — genau jenes Leitmotiv der ausführbaren Dokumentation, das uns im zweiten Teil wieder begegnen wird.

Damit ist der erste Teil an sein Ziel gelangt. Er hat die gedankliche Grundlage gelegt: das Problem echter Kalenderserver im Test, den Prüfstand als eigene Gattung, den bewusst geschnittenen Protokollausschnitt, die ausführbare Dokumentation und schließlich die Rekursion, die das Werkzeug mit seiner Methode verbindet. Der zweite Teil wird diese Grundlage verlassen und sich ganz der Praxis zuwenden — der Test-Support-Schnittstelle, die aus dem Prüfstand erst ein bequem zu handhabendes Werkzeug macht.

Total
0
Shares
Previous Post

Java Performance-Optimierung mit Agentic AI: Autonome Diagnostik und umsetzbare Empfehlungen

Next Post

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

Related Posts