1. „Bei mir startet es doch!“ – ein vorübergehender Waffenstillstand
Es gibt ein sehr menschliches Gefühl: Wenn die Anwendung einmal in der IDE gestartet ist, dann heißt das „sie funktioniert“. Und dieses Gefühl ist logisch – das Gehirn liebt es, Aufgaben abzuschließen und dann einen Kaffee zu trinken. Aber Spring Boot ist so ein Konstrukt, bei dem ein Großteil der eigentlichen Arbeit beim Start passiert: Es sammelt den Kontext, verknüpft die Konfiguration, bringt die Infrastruktur hoch, startet den eingebetteten Server und gibt erst dann eine ordentliche Laufzeitumgebung. Ein Fehler kann sich im Wiring oder in der Konfiguration verstecken und dann „plötzlich“ im ungünstigsten Moment zum Vorschein kommen.
Stellen Sie sich vor, catalog-service sei nicht nur eine Sammlung von Klassen, sondern ein zusammengebautes System mit einem „Startritual“. Das Ritual ist nicht magisch – es ist nur lang. Und wenn Sie an einer Ecke etwas ändern (z. B. einen Parameter im Konstruktor eines Services hinzufügen), kann der Start brechen, obwohl das Teil der Funktionalität, das Sie bearbeitet haben, nicht betroffen ist.
Hier ein typisches Szenario ohne Tests:
flowchart TD
A[Sie haben Code oder YAML geändert] --> B[Einmal in der IDE gestartet]
B --> C{Startet es?}
C -->|Ja| D[Überzeugung: alles okay]
C -->|Nein| E[30 Minuten Fehlersuche investiert]
D --> F[Am nächsten Tag noch ein Refactoring]
F --> G[Plötzlich startet es nicht mehr]
Mit Tests wird das Szenario langweiliger (und langweilig ist ein Kompliment für die Technik):
flowchart TD
A[Sie haben Code oder YAML geändert] --> B[Tests gestartet]
B --> C{Kontext gestartet?}
C -->|Ja| D[Gelassen weitermachen]
C -->|Nein| E[Fehler sofort sichtbar: wo und warum]
Die Idee dieser Lektion ist einfach: Wir wollen einen „Start-Bruch“ so früh wie möglich erkennen, solange Sie sich noch daran erinnern, was Sie geändert haben. Nicht erst nach einer Stunde. Nicht nach dem Bau eines JAR. Nicht erst, nachdem Sie bereits Tomcat, Jackson, Gradle und das Schicksal beschuldigt haben. Sondern sofort.
2. Minimal test baseline: Sinn und Grenzen
Wenn ein Anfänger hört „wir fügen Tests hinzu“, gerät er oft in Panik: „Muss ich jetzt das ganze Projekt testen? Wie testet man Spring? Wo mocke ich die Welt?“. Keine Sorge: Heute machen wir nicht die „große Testpyramide“, sondern eine einfache Absicherungsschicht, die eine wichtige Sache beweist: Unser Boot-Service lässt sich zusammenbauen.
In diesem Kurs ist catalog-service eine Vorlage. Die Vorlage hat eine minimale Verantwortung: vorhersagbar zu starten. Deshalb ist das minimal baseline eine Reihe von Prüfungen, die Regression genau beim Starten und Aufbauen des Kontexts verhindern.
Um Begriffsverwirrung zu vermeiden, wollen wir ehrlich festhalten, was Smoke-Test in unserem Kontext bedeutet. Ein Smoke-Test ist eine „schnelle Prüfung, ob der Motor überhaupt anspringt“. Er beweist nicht, dass das Auto 1000 km fährt, aber er verhindert die Situation, dass wir vergessen haben, die Räder anzuschrauben.
Unten eine sehr bodenständige Tabelle, die meist unnötige Anspannung nimmt:
| Frage | Manueller Start aus der IDE | Smoke-Test auf Kontextstart |
|---|---|---|
| Wiederholbarkeit | hängt von der Stimmung, dem Kaffee und der Frage „Habe ich wirklich das Richtige gestartet?“ ab | hoch: jedes Mal die gleiche Aktion |
| Geschwindigkeit der Fehlererkennung | oft spät: wenn Sie bereits im Kontext einer anderen Aufgabe sind | früh: direkt nach der Änderung |
| Erkennt Wiring-/DI-Fehler | manchmal ja, aber Sie könnten einfach nicht neu starten | ja, das ist sein Hauptzweck |
| Erkennt Konfigurationsfehler | manchmal, aber das passende Profil könnte nicht aktiv sein | ja, wenn der Test das richtige Szenario startet |
| Dokumentiert die Erwartung | nein | ja: der Test ist ein „Vertrag“ über den Start |
Ein wichtiger Punkt: Das minimal baseline muss nicht groß sein. Im Gegenteil: Wenn Sie es zu groß machen, hört es auf „minimal“ zu sein und bremst die Entwicklung. Unser Ziel ist ein nützlicher Schutz zu minimalen Kosten.
3. Smoke-Tests für den Start von Boot-Projekten
Hier gibt es ein kleines Paradoxon. Spring Boot wurde entwickelt, um „Start und Zusammenbau der Anwendung zu vereinfachen“, aber gleichzeitig können Anfänger am leichtesten aus Versehen genau den Start brechen. Der Grund liegt nicht darin, dass Boot zerbrechlich wäre. Der Grund ist, dass Boot viel der Montagearbeit übernimmt, während Sie auf der Ebene von „Objekten und Annotationen“ arbeiten. Sie haben ein Objekt geändert – und der Effekt zeigt sich erst beim Zusammenbau des gesamten Systems.
Einfach ausgedrückt: Boot-Projekte brechen oft an drei Stellen.
Die erste Stelle ist DI und Wiring. Sie fügen eine Abhängigkeit im Konstruktor hinzu – und dann passiert es. Wenn der Bean nicht registriert ist, startet der Kontext nicht. Der Java-Compiler kann hier nicht helfen: Der Code kompiliert, aber das System „ist nicht zusammengebaut“.
Die zweite Stelle ist die Konfiguration. Wir haben bereits @ConfigurationProperties, ein immutables Modell und Validierung. Das ist großartig, aber es hat einen ehrlichen Preis: Eine kaputte Konfiguration bringt den Start zu Fall. Und das ist richtig (fail-fast), aber das bedeutet, dass auch die Konfiguration getestet werden muss.
Die dritte Stelle ist die Startlogik. Selbst der saubere StartupSummaryRunner kann von der Konfiguration oder Diensten abhängen. Ein Fehler zeigt sich dort oft noch bevor Sie einen einzigen Endpunkt berührt haben.
Und genau hier wirkt der Smoke-Test wie ein „Detektor dafür, dass das Projekt aufgehört hat, eine Boot-Anwendung zu sein“. Er ist nicht klüger als Sie, er ist nur disziplinierter: Er startet den Kontext jedes Mal und fällt ehrlich durch, wenn das Zusammenbauen unmöglich ist. An dieser Stelle brauchen wir noch keine konkrete Annotation oder detaillierte Mechanik. Wichtig ist, die Klasse der Probleme zu erkennen: Ein Boot-Projekt bricht oft genau beim Zusammenbau der Anwendung.
4. Geschichte: Wie man den Start ohne „Funktionalität“ bricht
Normalerweise wird der Start nicht böswillig gebrochen, sondern ganz alltäglich. Zum Beispiel entscheiden Sie, dass der Service in der Lage sein soll, „das Verzeichnis zu exportieren“ (auch nur für die interne Diagnose). Sie fügen eine neue Abhängigkeit in CourseCatalogService hinzu, weil das so „saubere Architektur“ aussieht: Der Service verwendet einen Exporter, der Exporter exportiert. Schön? Ja. Und jetzt der entscheidende Punkt: Wenn Sie vergessen, den Exporter-Bean zu registrieren – startet der Service überhaupt nicht.
Hier ein minimales, realitätsnahes Beispiel (und sehr ähnlich zu dem, was Studierende automatisch machen):
import org.springframework.stereotype.Service;
// Vertrag des Exporters: Der Service möchte etwas, das das Verzeichnis exportieren kann.
interface CatalogExporter {
void export();
}
@Service
class CourseCatalogService {
private final CatalogExporter exporter;
// WICHTIG: Wenn die Implementierung von CatalogExporter nicht als Spring-Bean registriert ist,
// wird der Kontextstart auf der Wiring/DI-Ebene fehlschlagen.
CourseCatalogService(CatalogExporter exporter) {
this.exporter = exporter;
}
}
Der Code kompiliert. Die IDE ist zufrieden. Aber die Anwendung wird beim Start etwa Folgendes in menschlicher Sprache sagen: „Du hast mich gebeten, CourseCatalogService zu erstellen, aber ich weiß nicht, woher ich CatalogExporter nehmen soll.“
Und hier ist es wichtig, den Sinn der Tests zu verstehen. Wir wollen diesen Fehler nicht zufällig entdecken, wenn Sie bereits zu einer anderen Aufgabe gewechselt sind. Wir wollen, dass er sofort nach der Änderung auftaucht – als roter Test, der sagt: „Der Kontext startet nicht. Schuld ist das Wiring.“
5. Praxis: minimal baseline
Was sichern wir im catalog-service ab?
Wenn wir von „minimalem Test-Baseline“ sprechen, ist es hilfreich, gedanklich zwei Schichten zu trennen: was die Tests jetzt liefern und was sie nicht versprechen.
Aktuell liefern sie eine einfache Garantie: Der Service ist in der Lage, als Boot-Anwendung mit der erwarteten Konfiguration zu starten. Das beinhaltet die Erstellung des Kontexts, die Registrierung der Beans, das Binden der Konfiguration und das Durchführen der Validierung.
Aktuell versprechen sie nicht, dass jeder Endpunkt perfektes JSON zurückgibt, dass die Filter in allen Zweigen funktionieren, dass Actuator die richtigen Felder liefert oder dass die Logging-Ausgabe hübsch ist. Das sind alles gute Themen, aber jedes hat seinen Preis. Der heutige Preis soll gering sein.
Unten ein Diagramm, das man sich gut merken kann:
flowchart TD
A[Minimal baseline] --> B[Kontext startet]
A --> C[Konfiguration wird gebunden und validiert]
A --> D[Wichtige Beans sind verfügbar]
A -.-> E["Wir überprüfen Web-Antworten nicht im Detail"]
A -.-> F["Wir prüfen nicht alle Katalog-Szenarien"]
A -.-> G[Wir bauen keine Infrastruktur für komplexe Tests]
Das heißt, wir bauen eine „Startabsicherung“, keinen „vollständigen Gerichtsprozess gegen die Anwendung“.
Smoke-Check des Kontextstarts
Das minimal baseline hat fast immer einen einzigen günstigsten Smoke-Check: Es zwingt Boot, den Startvorgang erneut zu durchlaufen, und zeigt sofort, ob die Anwendung als System zusammengebaut ist. Von außen sieht eine solche Prüfung vielleicht fast leer aus, aber der Wert liegt nicht in der Anzahl der Assertions, sondern in der Tatsache des kontrollierten Starts selbst.
Hier ist es wichtig, zwei Ebenen zu trennen. Die erste Frage: „Startet der Kontext überhaupt?“. Die zweite: „Ist der konkrete Bean verfügbar, wurde die Eigenschaftsüberschreibung angewandt, wurde die erwartete Property-Klasse gebunden?“. Für die Vorlage brauchen wir zunächst die erste Ebene: zumindest eine grundlegende Alarmanlage für einen gebrochenen Start.
Tests als Teil der Vorlage: Hand-Off-Denken
Unser Kurs handelt nicht von „Code schreiben und auseinandergehen“, sondern von „eine wiederverwendbare Vorlage bauen“. Eine Vorlage wird kopiert, weiterentwickelt, an einen anderen Entwickler übergeben, in das nächste Projekt mitgenommen und erweitert. Und dann werden Tests nicht zum Luxus, sondern zum Teil der Verpackung.
Wenn Sie keine Tests haben, ist Ihre Vorlage wie ein Koffer ohne Schloss: Sieht ordentlich aus, aber wenn man ihn nur ein bisschen schüttelt, fliegen die Sachen im Flughafen herum. Sind Tests vorhanden, können Sie mutiger Änderungen vornehmen: neue Beans hinzufügen, die Konfiguration umbauen, Pakete refaktorieren. Denn Sie haben eine „Alarmanlage“, die sofort schreit und nicht erst nach einer Woche.
Der technische Wert von Tests in einer Vorlage liegt darin, dass sie die „minimale Überlebensfähigkeit“ feststellen. Das ist wie zu prüfen, ob der Strom an ist und die Sicherungen an Ort und Stelle sind. Ja, das ist noch keine Garantie, dass der Wasserkocher das Wasser gut kocht, aber ohne das machen Sie überhaupt keinen Tee.
Manueller Start als Alternative
Oft scheint es, dass Tests nicht nötig sind, wenn Sie diszipliniert die Anwendung nach jeder Änderung neu starten. In einer idealen Welt könnte das funktionieren. In der realen Welt scheitert es meist an drei Dingen.
Erstens – ein Mensch ist kein Roboter. Sie werden müde, werden abgelenkt, vergessen etwas. Das ist normal.
Zweitens – ein Start in einem Boot-Projekt kann merklich Zeit in Anspruch nehmen, und Sie beginnen, Neustarts zu sparen. „Ich habe doch nur einen Kommentar geändert...“ – und dann stellt sich heraus, dass Sie zusammen mit dem Kommentar versehentlich die Konfiguration oder einen Import berührt haben.
Drittens – ein manueller Start hinterlässt keine Spuren. Er dokumentiert nicht, was Sie geprüft haben. Ein Test dokumentiert: „Wir halten es für normal, dass die Anwendung startet.“ Und das ist wichtig, selbst wenn Sie allein arbeiten. Gerade wenn Sie allein arbeiten: Niemand wird kommen und sagen „Sie haben vergessen, den Start zu prüfen“.
Deshalb geht es bei Tests nicht um Misstrauen gegen sich selbst. Es geht um Respekt vor dem eigenen zukünftigen Ich, das sich in einem Monat mit dem Projekt beschäftigen wird.
6. Typische Fehler beim Hinzufügen von test baseline
Fehler Nr. 1: „Das ist ein Boot-Kurs, kein Testkurs, also kann ich Tests weglassen.“
Dieser Gedanke klingt logisch, bis Sie Ihre erste Start-Regression wegen einer kleinen Änderung im Konstruktor oder in YAML erwischen. Minimale Smoke-Tests sind nicht der „Beruf des Testers“, sondern ein Sicherheitsgurt für den Entwickler. Ihre Aufgabe ist nicht, alles abzudecken, sondern einfach zu verhindern, dass das Projekt leise aufhört zu starten.
Fehler Nr. 2: Sich nur auf den manuellen Start aus der IDE zu verlassen und dies für eine ausreichende Prüfung zu halten.
Die IDE macht das Leben tatsächlich leichter, aber sie „weicht“ die Realität manchmal auf: Cache, automatische Neustarts, Ihre lokalen Einstellungen, ein versehentlich aktives Profil. Ein Test wird immer gleich ausgeführt und hängt nicht von Ihrer Stimmung oder der Anzahl der geöffneten Tabs ab.
Fehler Nr. 3: Sofort die gesamte Funktionalität von catalog-service testen zu wollen, „wie in einem richtigen Projekt“.
Der Wunsch ist verständlich: Wenn schon Tests, dann sollen sie alles abdecken. Aber das verwandelt die Basisschicht schnell in ein schweres System, das man nicht mehr starten möchte. Das minimal baseline muss minimal bleiben: Überprüfung des Starts und des Kontextaufbaus – und fertig, ohne den Versuch, durch einen einzigen Test zu beweisen, dass man schon produktionsreif ist.
Fehler Nr. 4: In einem Test den Start, die Anwendungslogik und „noch einen zusätzlichen Fall“ zu vermischen.
Ein solcher Test fängt an, bei jeder Kleinigkeit zu brechen, und Sie verlieren den Überblick, was genau er eigentlich prüft. Ein Smoke-Test ist gut, weil er ein klares Ziel hat: Kontext gestartet oder nicht. Je enger das Ziel, desto weniger Rätsel bei der Fehlersuche.
Fehler Nr. 5: Zu denken, dass ein „leerer Test“ eine Fiktion sei und nichts bringe.
In der Spring-Boot-Welt ist eine „leere Methode“ unter @SpringBootTest keine Leere, sondern eine Startprüfung, die bereits vor dem Betreten der Methode stattfindet. Wenn der Kontext nicht startet, wird der Test ehrlich rot. Manchmal ist das die wichtigste Prüfung im gesamten Projekt, denn ohne Start haben Sie überhaupt nichts: keine Endpunkte, kein JSON, keinen Actuator, kein „Aber ich bin doch fast fertig“.
GO TO FULL VERSION