Die geerbte Swing-Anwendung hat keine verlässlichen Tests, aber der erste Vorschlag im Planungstermin lautet: „Dann migrieren wir eben zu JavaFX.“ Für QA ist das meist die falsche Reihenfolge. Ein neues GUI-Framework beseitigt keine unbekannten Erwartungen an die alte Anwendung; es macht sie zunächst schwerer zu beobachten. Ich würde zuerst das Verhalten des bestehenden Programms absichern und erst danach über einen Austausch der Oberfläche entscheiden.
Eine JavaFX-Migration vergrößert zunächst die ungetestete Fläche
Eine Neuentwicklung der Oberfläche klingt nach einem Schnitt durch gewachsene Swing-Klassen. Tatsächlich müssen dabei zwei Fragen gleichzeitig beantwortet werden: Was tut die Anwendung heute, und wie bildet JavaFX dieses Verhalten ab? Ohne Tests lässt sich eine Abweichung keinem der beiden Schritte sicher zuordnen. Ein fehlender Tastatur-Shortcut kann ein Migrationsfehler sein; ebenso gut war er bislang nur an einen selten genutzten Fokuszustand gebunden.
Man kann JavaFX und Swing: Moderne Desktop GUIs in Java lesen, sollte „modern“ aber nicht als Teststrategie verstehen, weil ein anderer Widget-Baum weder fachliche Regeln dokumentiert noch erwartete Ergebnisse festlegt. Für ein Team, das den Code nicht geschrieben hat, ist gerade diese fehlende Erwartung das Problem. Ein grüner Build mit Maven und JDK 21 sagt nur, dass sich das Programm bauen lässt. Er beantwortet nicht, ob ein abgebrochener Import den zuvor geöffneten Datensatz unverändert lässt.
Die Migration ändert außerdem die Mechanik, an der Tests und Fehlerbilder hängen. Swing verlangt Änderungen an Komponenten auf dem Event Dispatch Thread; JavaFX verwendet den JavaFX Application Thread. Code, der bisher mit SwingUtilities.invokeLater eine Aktualisierung einplant, braucht im Zielsystem etwa Platform.runLater. Das ist keine bloße Umbenennung, weil Lebenszyklus, Fokuswechsel und Ereignisreihenfolge mit der Oberfläche zusammenhängen. Wer beides in einem Arbeitspaket austauscht, verliert eine brauchbare Vergleichsbasis für sporadische Fehler.
Auch ein visueller Vergleich reicht nicht aus. Zwei Dialoge können gleich aussehen und sich bei Enter, Escape, Tab oder beim Schließen während eines laufenden Hintergrundjobs unterschiedlich verhalten. Als erste, bewusst einstellbare Grenze würde ich 30 Sekunden für einen wiederholbaren Start-und-Öffnen-Test vorsehen: Dauert er länger, wird er seltener lokal ausgeführt. Diese Zahl ist kein Qualitätsurteil über das Produkt, sondern ein Wert, den das Team anhand seiner CI-Laufzeiten anpassen sollte.
Ich würde deshalb nicht alle Swing-Views vor dem ersten Charakterisierungstest nach JavaFX portieren, weil jeder umgeschriebene Dialog neue Abweichungen erzeugen kann, während das ursprüngliche Verhalten noch nicht festgehalten ist. Selbst eine technisch saubere Migration wäre dann für QA schwer freizugeben: Der Testauftrag hätte kein überprüfbares „vorher“.
Der erste Test muss eine Beobachtung sichern, nicht eine Architektur bestätigen
In einer ungetesteten Codebasis beginnt QA zweckmäßig bei einem kurzen, häufig genutzten Ablauf: Anwendung starten, Datensatz öffnen, einen Wert ändern, speichern, erneut öffnen. Die erwarteten Ergebnisse gehören in ein Protokoll, bevor jemand Implementierungsdetails als Wahrheit übernimmt. Dazu zählen der gespeicherte Wert, eine sichtbare Rückmeldung und das Verhalten bei einem absichtlich abgebrochenen Speichervorgang. Eine Beobachtung ist noch keine gewünschte Fachregel; diese Unterscheidung verhindert, dass ein alter Fehler durch den neuen Test dauerhaft vorgeschrieben wird.
Für Swing lassen sich kleine technische Annahmen ohne kompletten Fensterstart prüfen. Das folgende Programm läuft mit java SwingProbe.java auf einem JDK mit Swing-Unterstützung und kontrolliert, ob ein Button seinen registrierten Handler auf dem Event Dispatch Thread erreicht. Es ist kein Ersatz für einen Speichertest, aber ein ausführbarer Ausgangspunkt für eine bislang unbeobachtete Ereigniskette.
import javax.swing.*;
public class SwingProbe {
public static void main(String[] args) throws Exception {
System.setProperty("java.awt.headless", "true");
SwingUtilities.invokeAndWait(() -> {
JButton button = new JButton("Speichern");
final int[] calls = {0};
button.addActionListener(e -> calls[0]++);
button.doClick();
if (calls[0] != 1) throw new AssertionError("Handler fehlt");
});
}
}
Danach ist eine kleine Testpyramide sinnvoller als ein sofortiger Stapel Screenshot-Tests. JUnit Jupiter 5 kann fachliche Regeln und Adapter prüfen; Mockito kann eine langsame oder unzuverlässige Gegenstelle ersetzen, sofern der Mock nicht das eigentliche Produktverhalten erfindet. Für HTTP-Aufrufe liefert WireMock kontrollierbare Antworten, etwa einen Fehler nach einer erfolgreichen Anfrage. JaCoCo 0.8.12 hilft dabei, ausgeführte Pfade zu finden, beweist aber keine Korrektheit: Eine Zeile kann vollständig abgedeckt sein, obwohl der Test den falschen gespeicherten Wert akzeptiert.
Bei der CI-Ausführung sollte Maven Surefire, beispielsweise in Version 3.2.5, die schnellen JUnit-Tests von langsameren GUI-Läufen trennen. Als Zielwert zum Nachjustieren eignen sich zunächst 2 Minuten für die schnelle Suite: Überschreitet sie diese Grenze regelmäßig, lohnt sich die Suche nach blockierenden Netzwerkzugriffen oder unnötigen Fensterstarts. Für asynchrone Vorgänge ist Awaitility mit einer begrenzten Wartezeit belastbarer als Thread.sleep, weil der Test bei frühem Erfolg sofort weiterläuft und bei Zeitüberschreitung einen eindeutigen Fehler meldet.
Für die Reihenfolge der nächsten Tests zählt nicht die Anzahl der Fenster. Entscheidend sind Übergänge, bei denen Zustand verloren gehen oder unbemerkt wechseln kann: Speichern, Abbrechen, Importieren und erneutes Öffnen. Ein geerbter Dialog mit drei Schaltflächen kann dringlicher sein als eine große Tabelle, wenn sein Abbruchpfad Daten überschreibt. QA sollte diese Priorität am beobachteten Schaden begründen, nicht an der optischen Komplexität des Screens.
Swing und JavaFX gewinnen unter verschiedenen Testbedingungen
Swing behalten gewinnt, wenn die Anwendung zuverlässig startet, der kritische Ablauf in der bestehenden Oberfläche erreichbar ist und das Team schnell Vergleichstests braucht. Die Kosten sind reale Wartung an älteren Views und möglicherweise mühsame Entkopplung von Logik und Komponenten. Zu JavaFX migrieren gewinnt, wenn konkrete Oberflächenanforderungen mit der vorhandenen Swing-Struktur unverhältnismäßig teuer werden und eine bereits getestete fachliche Schicht das Verhalten schützt. Die Kosten sind eine zweite GUI-Implementierung während des Übergangs, neue Testtreiber und die Prüfung sämtlicher betroffener Interaktionen. Keines der beiden Frameworks gewinnt allein durch sein Erscheinungsbild.
Bei dieser Entscheidung sollte QA die Testbarkeit am vorhandenen System ausprobieren. AssertJ Swing kann Swing-Komponenten über Namen ansprechen; das kostet zunächst Arbeit beim Benennen der Widgets, reduziert aber die Abhängigkeit von Bildschirmkoordinaten. TestFX kann JavaFX-Oberflächen bedienen; auch dort müssen Selektoren stabil sein, sonst werden Tests bei harmlosen Layoutänderungen brüchig. Java Robot beziehungsweise java.awt.Robot kann echte Tastatur- und Mausereignisse auslösen, benötigt dafür aber eine passende grafische Sitzung. Ein Test, der nur unter dem Laptop einer Person läuft, ist für eine Migrationsfreigabe keine ausreichende Kontrolle.
Ein Vergleich sollte auf demselben fachlichen Ablauf beruhen. Im fiktiven Probelauf einer übernommenen Anwendung wurden für „öffnen, ändern, speichern, erneut öffnen“ 18 Interaktionen protokolliert; diese Zahl wäre ein gemessener Befund dieses Probelaufs, keine allgemeine Eigenschaft von Swing. Erst wenn das Team die Interaktionen tatsächlich erfasst hat, kann es prüfen, welche in einem JavaFX-Prototyp fehlen oder anders reagieren. Die Messung muss auch Tastaturwege enthalten, weil ein reiner Maustest Fokus- und Shortcut-Probleme nicht sichtbar macht.
Man kann JavaFX oder Swing wo Teams spaet Privacy Leaks erben lesen, sollte Privacy aber nicht zum einzigen Risikobegriff machen, weil ein nicht gespeicherter oder doppelt ausgelöster Vorgang ebenso eine Freigabe blockieren kann. Für QA ist die bessere Frage vor einer Framework-Entscheidung: Welche Abweichung können wir nach dem Umbau zuverlässig erkennen? Bleibt die Antwort bei „jemand klickt sich durch“, fehlen noch überprüfbare Beobachtungen.
Freigaben werden belastbar, wenn jede Änderung eine überprüfbare Behauptung hat
Ein Migrationsticket wie „Dialog auf JavaFX umstellen“ beschreibt Arbeit, aber kein akzeptierbares Verhalten. Ein prüfbares Ticket sagt dagegen: Nach einem fehlgeschlagenen Speicherversuch bleibt der zuvor geöffnete Wert erhalten, eine Fehlermeldung erscheint genau einmal, und ein erneuter Versuch kann erfolgreich sein. Das Team kann diese Behauptung zunächst gegen Swing und später gegen JavaFX laufen lassen. Scheitert der Test schon im Bestand, muss es entscheiden, ob er einen bestehenden Fehler oder eine falsch angenommene Erwartung zeigt.
Für solche Prüfungen braucht die Umgebung feste Grenzen. Eine lokale Testdatenbank oder ein kontrollierter Dateistand verhindert, dass zwei Läufe verschiedene Ausgangsdaten sehen. Ein WireMock-Server sollte nur die für den Ablauf nötigen HTTP-Antworten liefern; sonst prüft der Test womöglich die Nachbildung des Servers statt der Anwendung. Falls die Software Einstellungen über die Java Preferences API speichert, braucht jeder Lauf einen isolierten Testzustand, weil Werte aus einem früheren Lauf das Ergebnis verändern können. Diese Vorbereitung kostet Zeit, liefert aber eine Erklärung für Fehler, die sonst als „GUI-Flakiness“ abgetan werden.
Während eines GUI-Laufs lässt sich ein Hänger mit Java Flight Recorder oder jcmd untersuchen, statt den Timeout einfach zu erhöhen. Ein Thread-Dump kann beispielsweise zeigen, ob der Event Dispatch Thread auf eine Netzwerkantwort wartet. Das ist eine konkrete Ursache für eine nicht reagierende Oberfläche; eine JavaFX-Migration würde den blockierenden Aufruf nicht automatisch beseitigen. Als zu justierender Grenzwert kann ein Test 200 Millisekunden für eine lokale Reaktion verwenden, sofern das Team zuvor Hardware und CI-Last festlegt. Ohne diese Bedingungen würde die Zahl nur wechselnde Rechner vergleichen.
Die Freigabe sollte außerdem zwischen Testfehler und Produktfehler unterscheiden. Ein fehlendes Display in der CI ist ein Umgebungsproblem; ein Speicherdialog, der nach einem simulierten HTTP-Fehler nicht mehr bedienbar ist, ist ein Produktbefund. Logs mit Zeitstempeln, JUnit-Testergebnisse und ein Screenshot beim Fehlschlag helfen bei dieser Trennung, weil sie denselben Lauf aus mehreren Perspektiven zeigen. Für die Entscheidung über JavaFX zählt dann nicht die Menge grüner Häkchen, sondern ob die riskanten Zustandswechsel auf beiden Seiten vergleichbar geprüft wurden.
Der erste Schritt ist ein reproduzierbarer Speicherversuch im Bestand
Nimm morgen einen häufig benutzten Datensatz, notiere seinen Ausgangswert und führe Speichern, Abbrechen und erneutes Öffnen in der bestehenden Swing-Anwendung aus. Halte Ergebnis und offene Erwartungsfragen getrennt fest. Automatisiere anschließend genau einen dieser Übergänge mit kontrollierten Daten. Erst wenn dieser Test zuverlässig zwischen unverändertem und verlorenem Zustand unterscheidet, hat das Team eine belastbare Grundlage für ein JavaFX-Ticket.


