Zum Inhalt springen

13. Qualität im Team

Zu Zen-Modus wechseln

Das Abschlussprojekt dieses Jahres wird in Teams gebaut, und Teamarbeit ändert die Spielregeln: Code, den nur seine Autor:in versteht, ist eine Hypothek; ein Fehler auf dem Hauptbranch blockiert alle; ungetestete Änderungen brechen fremde Arbeit. Dieses Kapitel richtet die drei Schutzmechanismen ein, mit denen professionelle Teams arbeiten: einen verbindlichen Branch-Workflow mit Merge Requests, Code-Reviews mit belastbaren Regeln und automatisierte Tests, die bei jedem Push laufen. Eine Woche Theorie, danach sind sie Projektalltag.

Die Git-Grundlagen (Commits, Branches, Merges, Remotes) werden vorausgesetzt; neu ist die Verbindlichkeit und der Weg über die Plattform. Die Regeln für das Projekt:

  1. main ist geschützt. Niemand pusht direkt; der Branch enthält jederzeit einen lauffähigen Stand (am Schul-GitLab als Protected Branch konfiguriert, die Regel wird also erzwungen, nicht erhofft).
  2. Jede Änderung reist per Feature-Branch: feature/job-export, fix/delete-running-job. Kurzlebig, ein Thema, sprechender Name.
  3. Der Weg zurück führt über einen Merge Request (MR; andere Plattformen sagen Pull Request): Der Branch wird gepusht, der MR beschreibt die Änderung (was, warum, wie getestet), mindestens ein Teammitglied reviewt, erst dann wird gemergt.
  4. Grüne Pipeline ist Merge-Bedingung: Die automatisierten Tests (unten) müssen durchlaufen, sonst ist der Merge-Knopf gesperrt.

Der MR ist dabei mehr als ein Genehmigungsschritt: Er ist die Einheit, in der über Code gesprochen wird, mit Kommentaren an konkreten Zeilen, nachvollziehbar für alle und dauerhaft dokumentiert.

Reviews sind das wirksamste bekannte Mittel gegen Fehler und zugleich das Mittel, mit dem Wissen im Team wandert; beides funktioniert nur mit Kultur. Die Regeln, in beide Richtungen:

Für Reviewer: Kritisiert wird Code, nie die Person (“diese Funktion prüft den Fehlerfall nicht”, nicht “du hast vergessen”). Jede Anmerkung ist konkret (Zeile, Vorschlag) und begründet (welches Kriterium ist verletzt); “gefällt mir nicht” ist kein Befund. Fragen sind erlaubt und erwünscht (“warum reicht hier kein PATCH?”), Lob für gute Lösungen ebenso. Und: Ein Review, das nur Tippfehler findet, hat die Architektur nicht angesehen.

Für Autoren: Kleine MRs schreiben (ein reviewbarer MR hat eher 100 als 1000 geänderte Zeilen), die Beschreibung ernst nehmen, Anmerkungen nicht als Angriff lesen. Jede Anmerkung wird beantwortet: umgesetzt oder mit Begründung abgelehnt; stilles Ignorieren ist keine Option.

Die inhaltliche Review-Checkliste dieses Jahrgangs, abgeleitet aus den Kapiteln:

  • Hält die Änderung den API-Vertrag (Statuscodes, Fehlerformat, dokumentierte Felder)?
  • Respektiert sie die Schichten (Importe prüfen: kein HTTP im Service, kein SQL in der Komponente)?
  • Wird jede Eingabe an der Grenze validiert; laufen Mutationen gegen Doppelauslösung geschützt?
  • Sind asynchrone Abläufe korrekt (kein vergessenes await, kein unnötig sequentielles Laden)?
  • Gibt es Tests für neue Logik, und sagen die Namen, was geprüft wird?

Die Mehrschichtarchitektur zahlt hier ihre Dividende: Die Geschäftslogik hängt nur am Repository-Interface und ist damit ohne Server, ohne HTTP und ohne Datenbank testbar. Als Testframework verwenden wir Vitest, den Standard im TypeScript-Umfeld; die Konzepte (Testdateien, sprechende Testnamen, Arrange-Act-Assert, erwartete Fehler) sind die üblichen jedes Testframeworks.

lib/services/job-service.test.ts
import { describe, expect, it } from "vitest";
import { JobService, ConflictError } from "./job-service";
import { MemoryJobRepository } from "@/lib/repository/memory-job-repository";
describe("JobService", () => {
it("creates a job with state queued", async () => {
const service = new JobService(new MemoryJobRepository());
const job = await service.createJob({ filename: "intro.mp4", preset: "web-1080p" });
expect(job.state).toBe("queued");
expect(job.id).toBeGreaterThan(0);
});
it("refuses to delete a running job", async () => {
const repository = new MemoryJobRepository();
const service = new JobService(repository);
const job = await service.createJob({ filename: "a.mp4", preset: "web-1080p" });
await repository.update({ ...job, state: "running" });
await expect(service.deleteJob(job.id)).rejects.toThrow(ConflictError);
});
});

npx vitest führt alle *.test.ts-Dateien aus und bleibt im Watch-Modus; npx vitest run läuft einmal durch (die Form für die Pipeline). Die zweite Testebene prüft die API von außen, als Konsument gegen den laufenden Dev-Server; das testet zusätzlich Routing, Validierung und Fehlerformat:

// tests/api.test.ts: run against `npm run dev`
it("rejects a job without filename with 400 and the error format", async () => {
const response = await fetch("http://localhost:3000/api/jobs", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ filename: "" }),
});
expect(response.status).toBe(400);
const body = await response.json();
expect(body.error.code).toBe("VALIDATION_FAILED");
});

Die Arbeitsteilung: Viele schnelle Service-Tests für die Regeln, wenige API-Tests für den Vertrag und das Zusammenspiel. Wer eine Regel nur über die API testen kann, hat sie vermutlich in den Handler statt in den Service gelegt; Tests sind damit auch ein Architektur-Frühwarnsystem.

Tests, die nur manuell laufen, laufen irgendwann nicht mehr. Continuous Integration (CI) heißt: Die Plattform führt sie bei jedem Push selbst aus. Am Schul-GitLab genügt dafür eine Datei im Projektwurzelverzeichnis:

.gitlab-ci.yml
test:
image: node:22
script:
- npm ci # clean install from package-lock.json
- npx vitest run

Ab jetzt zeigt jeder Push und jeder MR eine Pipeline: grün oder rot, für alle sichtbar, und als Merge-Bedingung verdrahtet. Das ist CI in einfachster Form, und mehr braucht das Projekt nicht; entscheidend ist der Kulturwechsel: Ob die Tests laufen, ist keine Erinnerungsfrage mehr, sondern Infrastruktur.

Mit den Werkzeugen steht die Organisation des Abschlussprojekts; drei Festlegungen pro Team, schriftlich vor dem ersten Commit:

  • Rollen: Wer verantwortet was (API, Frontend, Persistenz, Review-Koordination)? Verantwortung heißt kümmern, nicht alleine bauen.
  • Board: Aufgaben als Issues am GitLab-Board (offen, in Arbeit, im Review, fertig), jede Aufgabe klein genug für einen MR.
  • Definition of Done: Wann ist eine Aufgabe fertig? Referenzfassung: Code auf main gemergt, Review beantwortet, Tests grün in der Pipeline, Vertrag/Doku aktualisiert. “Läuft bei mir” kommt in der Liste nicht vor.

Lernergebnisse: Was Sie nach diesem Kapitel können sollten

Abschnitt betitelt „Lernergebnisse: Was Sie nach diesem Kapitel können sollten“

Nach Abschluss dieses Kapitels sollten Schülerinnen und Schüler in der Lage sein:

  • Anwenden: im geschützten Branch-Workflow arbeiten: Feature-Branch, Merge Request mit Beschreibung, Review, Merge nach grüner Pipeline.
  • Anwenden: Code-Reviews nach den Regeln geben (konkret, begründet, respektvoll) und Anmerkungen als Autor professionell beantworten.
  • Anwenden: Geschäftslogik mit Vitest gegen das Memory-Repository testen und die API von außen gegen den Vertrag prüfen.
  • Erklären: die Arbeitsteilung zwischen Service-Tests und API-Tests begründen und Tests als Architektur-Indikator nutzen.
  • Anwenden: eine minimale CI-Pipeline einrichten, die Tests bei jedem Push ausführt.
  • Entwerfen: die Projektorganisation (Rollen, Board, Definition of Done) für ein Teamprojekt festlegen.
  • Aufgabe 21 - Review-Runde
  • Aufgabe 22 - Projekt: Client-Server-Anwendung (Start)