Zum Inhalt springen

11. Acceptance Test Driven Development mit Agenten

Zu Zen-Modus wechseln

Ein Agent hat ein Problem, das ein Mensch nicht hat: Er weiß nicht, wann er fertig ist. Ein Mensch merkt beim Ausprobieren, dass etwas nicht stimmt, erinnert sich an das Gespräch von gestern, sieht die Oberfläche. Ein Agent sieht nichts davon. Ihm bleibt der eigene Eindruck, und der lautet in praktisch jedem Fall: sieht gut aus.

Deshalb ist die Frage, die dieses Kapitel beantwortet, die zentrale Frage beim agentischen Arbeiten überhaupt: Woran erkennt der Agent, dass er fertig ist? Die Antwort heißt: an einem Kriterium, das eine Maschine prüfen kann und das nicht er selbst formuliert hat.

Acceptance Test Driven Development kennen Sie aus dem Kapitel über Teststrategien in Softwareentwicklung. Die Idee: Bevor implementiert wird, wird das gewünschte Verhalten aus Sicht des Anwenders in Beispielen festgehalten, und diese Beispiele werden als ausführbare Tests formuliert. Erst dann beginnt die Implementierung, und sie ist fertig, wenn die Tests grün sind.

Der Unterschied zu testgetriebener Entwicklung im engeren Sinn ist die Ebene. TDD arbeitet an einzelnen Einheiten, ATDD an der Fachlichkeit: nicht „gibt die Funktion validate bei leerem String false zurück”, sondern „ein Projekt ohne Titel kann nicht angelegt werden, und der Benutzer erfährt warum”.

Bei menschlichen Teams ist ATDD eine Disziplinfrage: Man kann auch ohne, es wird nur schlechter. Bei Agenten ist es keine Disziplinfrage, sondern eine technische Voraussetzung, und dafür gibt es drei Gründe.

Der Agent braucht ein maschinelles Signal. Aus Kapitel 8: Ein Agent ist so gut wie seine Rückmeldung. Ein fehlschlagender Test ist die präziseste Rückmeldung, die es gibt, weil sie nicht nur sagt, dass etwas falsch ist, sondern was.

Die Tests schließen die Spezifikationslücke. Ihr Auftrag in natürlicher Sprache ist zwangsläufig unvollständig; das ist die Ursache der Fehlleistung aus Kapitel 10. Ein Test ist unmissverständlich. Wo Prosa mehrdeutig ist, ist ein Testfall eindeutig, und die Mehrdeutigkeit fällt beim Schreiben des Tests auf, nicht nach der Implementierung.

Sie kontrollieren das Ergebnis, ohne jede Zeile zu lesen. Bei zweihundert geänderten Zeilen ist ein zeilenweises Review teuer. Wenn Sie stattdessen die Tests geschrieben oder geprüft haben und diese grün sind, haben Sie eine Aussage über das Verhalten, unabhängig davon, wie der Code aussieht. Das Review verlagert sich vom „ist das richtig” auf „ist das gut gebaut”, und das ist die angenehmere Frage.

flowchart TD
  A["User Story<br/>Als ... möchte ich ... damit ..."] --> B["Akzeptanzkriterien<br/>Given / When / Then"]
  B --> C["Tests schreiben<br/>Mensch, oder Agent + Review"]
  C --> D{"Tests rot?"}
  D -->|nein| E["Test prüft nichts<br/>-> korrigieren"]
  E --> C
  D -->|ja| F["Agent implementiert<br/>Build Mode"]
  F --> G{"Tests grün?"}
  G -->|nein| F
  G -->|ja| H["Refactoring durch den Agenten<br/>Tests bleiben grün"]
  H --> I["Review durch den Menschen"]

Zwei Kästchen darin werden regelmäßig übersprungen, und beide zu Unrecht.

„Tests rot?” Ein Test, der vor der Implementierung grün ist, prüft nichts. Das ist der älteste Test-Fehler überhaupt, und er passiert mit Agenten häufiger, weil die Tests schneller entstehen. Der rote Lauf ist der Beweis, dass der Test überhaupt etwas misst.

„Review durch den Menschen” Grüne Tests sagen, dass das gewünschte Verhalten da ist. Sie sagen nichts über Struktur, Lesbarkeit, Sicherheit oder darüber, ob der Code in die Architektur passt. Diese Fragen bleiben beim Menschen (Kapitel 12).

Nehmen wir die Aufgabe aus dem Plan in Kapitel 10.

Aus der User Story werden Beispiele. Die Form Given/When/Then zwingt dazu, Vorbedingung, Aktion und erwartetes Ergebnis zu trennen, und deckt beim Schreiben Lücken auf.

Feature: Create a project
Scenario: A valid project is created
Given the project number "P-2026-001" is not taken
When a project titled "Website Relaunch" is created with that number
Then the service responds with 201
And the response contains an id
Scenario: The title is missing
When a project without a title is created
Then the service responds with 400
And the response marks the field "title" as invalid
Scenario: The project number has the wrong format
When a project is created with the number "2026-1"
Then the service responds with 400
And the response marks the field "projectNumber" as invalid
Scenario: The project number is already taken
Given a project with the number "P-2026-001" exists
When a project is created with the same number
Then the service responds with 409

Vier Szenarien, ein Gutfall und drei Fehlerfälle. Dieses Verhältnis ist typisch und ein brauchbarer Prüfwert: Wenn Ihre Akzeptanzkriterien überwiegend Gutfälle sind, fehlt etwas.

Aus den Szenarien werden Tests. Ob dazwischen ein Gherkin-Werkzeug steht oder ob direkt Testcode geschrieben wird, ist eine Werkzeugfrage; entscheidend ist, dass jedes Szenario genau einen Test hat.

tests/api/projects.post.test.ts
import { describe, it, expect, beforeEach } from "vitest";
import { POST } from "@/app/api/projects/route";
import { resetDatabase, seedProject } from "../helpers/db";
const request = (body: unknown) =>
new Request("http://localhost/api/projects", {
method: "POST",
headers: { "content-type": "application/json" },
body: JSON.stringify(body),
});
describe("POST /api/projects", () => {
beforeEach(async () => {
await resetDatabase();
});
it("creates a valid project", async () => {
const res = await POST(request({ title: "Website Relaunch", projectNumber: "P-2026-001" }));
expect(res.status).toBe(201);
expect((await res.json()).id).toBeDefined();
});
it("rejects a project without a title", async () => {
const res = await POST(request({ projectNumber: "P-2026-001" }));
expect(res.status).toBe(400);
expect((await res.json()).error.fields).toHaveProperty("title");
});
it("rejects a project number with the wrong format", async () => {
const res = await POST(request({ title: "Test", projectNumber: "2026-1" }));
expect(res.status).toBe(400);
expect((await res.json()).error.fields).toHaveProperty("projectNumber");
});
it("rejects a project number that is already taken", async () => {
await seedProject({ title: "Existing", projectNumber: "P-2026-001" });
const res = await POST(request({ title: "New", projectNumber: "P-2026-001" }));
expect(res.status).toBe(409);
});
});

Jetzt erst kommt die Implementierung ins Spiel, und der Auftrag ist kurz, weil die Spezifikation in den Tests steht.

Implementiere POST /api/projects, sodass tests/api/projects.post.test.ts
grün wird.
Randbedingungen:
- Architektur und Fehlerformat nach AGENTS.md, Datenzugriff nur im Repository
- Eingabevalidierung mit Zod
- Die Testdatei wird NICHT verändert. Wenn ein Test aus deiner Sicht falsch
ist, halte an und melde das, statt ihn anzupassen.
- Führe nach jeder Änderung `bun run test` und `bun run typecheck` aus.

Der dritte Punkt ist der wichtige. Er wird auch dann aufgeschrieben, wenn er technisch abgesichert ist, weil er den Agenten in die richtige Reaktion lenkt: anhalten und melden. Ein Test, den der Agent für falsch hält, ist übrigens ein wertvoller Befund. In etwa jedem fünften Fall hat er recht, und dann haben Sie einen Denkfehler in der Spezifikation gefunden, bevor er im Code steckt.

Eine Bitte im Prompt ist eine Bitte. Wer die Regel verlässlich haben will, setzt sie im Harness durch. Drei Ebenen, aufsteigend nach Verlässlichkeit.

  1. Im Prompt und in der Regeldatei. Die schwächste Stufe, aber sie kostet nichts und wirkt in den meisten Fällen. In AGENTS.md: „Dateien unter tests/ sind für dich schreibgeschützt.”

  2. Über die Werkzeugrechte. Viele Agenten erlauben Ausschlussmuster für Schreibzugriffe oder eine Bestätigungspflicht für bestimmte Pfade. Damit wird aus der Bitte eine Rückfrage, die Sie sehen.

  3. Im Prozess. Die verlässlichste Stufe und die einzige, die auch bei einem Menschen greift: Tests kommen in einen eigenen Commit vor der Implementierung. Damit ist im Merge Request auf einen Blick sichtbar, ob danach noch jemand an ihnen gedreht hat. In der CI lässt sich zusätzlich prüfen, ob ein Merge Request, der Quellcode ändert, gleichzeitig Testdateien ändert, und das zumindest markieren.

Für den Unterricht ist Stufe 3 verbindlich: Der Test-Commit steht vor dem Implementierungs-Commit, und beide sind in der Git-Historie getrennt sichtbar. Das ist gleichzeitig der Nachweis, dass Sie die Reihenfolge eingehalten haben.

Die naheliegende Frage, und die Antwort ist abgestuft.

Die Akzeptanzkriterien schreiben Sie. Immer. Sie sind die fachliche Aussage, und sie aus dem Auftrag abzuleiten ist genau die Denkarbeit, die niemand abnehmen kann. Ein Agent, der sich die Kriterien selbst ausdenkt, prüft seine eigene Interpretation des Auftrags.

Den Testcode darf ein Agent schreiben, wenn die Kriterien feststehen und Sie den erzeugten Code lesen. Aus Given/When/Then Vitest-Code zu machen, ist Übersetzungsarbeit, und darin sind Agenten gut. Prüfen Sie dabei drei Dinge: Prüft der Test wirklich das Kriterium? Ist er rot, bevor implementiert wird? Prüft er das Verhalten und nicht die Implementierung?

Der dritte Punkt ist der, der am häufigsten schiefgeht. Ein Test, der prüft, dass eine bestimmte interne Funktion aufgerufen wurde, hält jedes Refactoring auf, ohne etwas über die Fachlichkeit auszusagen. Ein Test, der die HTTP-Antwort prüft, überlebt jeden Umbau darunter.

Nicht alles ist so prüfbar, und es hilft nichts, das zu behaupten.

Gut testbarSchlecht testbarWas stattdessen
API-Verhalten, StatuscodesOb eine Oberfläche schön istEntwurfsabnahme durch Menschen
Validierung und FehlerfälleOb ein Text verständlich istLesen lassen
Geschäftslogik, BerechnungenOb der Ablauf sich gut anfühltUsability-Test (5. Klasse)
Datenzugriff, MigrationenOb die Architektur passtCode-Review
Verhalten unter LastBarrierefreiheit im DetailWerkzeuge plus manuelle Prüfung

Für die schwer testbare Spalte gibt es Teilhilfen: Screenshot-Vergleiche für UI-Regressionen, automatisierte Prüfungen für einen Teil der Barrierefreiheitsregeln, Lasttests für das Verhalten unter Druck. Sie ersetzen die menschliche Beurteilung nicht, sie fangen nur die Rückschritte ab.

Und eine Warnung, die auch ohne Agenten gilt: Grüne Tests sind kein Beweis für Korrektheit, sondern ein Beweis dafür, dass die geprüften Fälle stimmen. Ein Agent optimiert genau auf das, was gemessen wird. Wenn Ihre Tests einen Fall nicht abdecken, ist die Wahrscheinlichkeit hoch, dass dieser Fall nicht funktioniert.

  1. Den roten Lauf überspringen. Ein Test, der von Anfang an grün ist, prüft nichts, und niemand merkt es.
  2. Den Agenten die Tests anpassen lassen. Damit ist das Verfahren wertlos, und zwar vollständig.
  3. Tests und Implementierung in einem Commit. Dann ist die Reihenfolge nicht mehr nachweisbar.
  4. Nur Gutfälle testen. Fehlerfälle sind der Ort, an dem Software auseinanderfällt.
  5. Implementierung statt Verhalten testen. Solche Tests brechen bei jedem Refactoring und sagen fachlich nichts.
  6. Den Agenten die Akzeptanzkriterien erfinden lassen. Er prüft dann seine eigene Auslegung des Auftrags.
  7. Grüne Tests für Vollständigkeit halten. Sie sagen etwas über das Geprüfte, sonst nichts.
  8. Auf das Review verzichten, weil die Tests grün sind. Tests prüfen Verhalten, nicht Struktur und nicht Sicherheit.

Lernergebnisse: Was Sie nach diesem Kapitel können sollten

Abschnitt betitelt „Lernergebnisse: Was Sie nach diesem Kapitel können sollten“
  • Erklären: begründen, warum ein Agent ein maschinell prüfbares Erfolgskriterium braucht, und dies mit der Rückmeldeschleife aus Kapitel 8 verbinden.
  • Anwenden: aus einer User Story Akzeptanzkriterien in der Form Given/When/Then ableiten, mit Gut- und Fehlerfällen.
  • Anwenden: Akzeptanzkriterien in ausführbare Tests überführen und den roten Lauf als Nachweis führen.
  • Anwenden: einen Auftrag an einen Agenten so formulieren, dass die Tests die Spezifikation sind.
  • Erklären: erklären, warum der Agent die Tests nicht verändern darf, und drei Ebenen der Durchsetzung benennen.
  • Anwenden: Tests und Implementierung in getrennten Commits ablegen und die Reihenfolge in der Git-Historie nachweisen.
  • Beurteilen: beurteilen, ob ein Test das Verhalten oder die Implementierung prüft, und ihn gegebenenfalls umformulieren.
  • Beurteilen: die Grenzen des Verfahrens benennen und für schwer testbare Anforderungen ein geeignetes Prüfverfahren wählen.