Snapshot- und Golden-Testing
Snapshot- und Golden-Tests prüfen deine UI automatisch auf visuelle Regressionen. Du lernst, wann und wie du sie gezielt einsetzt.
Visuelle Regressionen gehören zu den heimtückischsten Fehlern in Android-Projekten: Eine Schriftgröße ändert sich unbemerkt, ein Abstand verschwindet, und der UI-Test läuft trotzdem grün – bis der nächste Code-Review oder Nutzerbericht aufdeckt, dass das Layout seit drei Sprints falsch aussieht. Snapshot- und Golden-Tests schließen genau diese Lücke, indem sie das Erscheinungsbild deiner Komponenten automatisch gegen eine gespeicherte Referenz prüfen.
Was ist das?
Ein Snapshot-Test – häufig auch Golden Test genannt – nimmt beim ersten Durchlauf ein Referenzbild einer UI-Komponente auf und speichert es im Repository. Bei jedem späteren Testlauf rendert das Framework dieselbe Komponente erneut und vergleicht das Ergebnis pixelgenau mit dem gespeicherten Bild. Weicht das aktuelle Rendering ab, schlägt der Test fehl und liefert dir ein Diff-Bild, das genau zeigt, was sich verändert hat.
Der Begriff Golden File stammt aus der Compiler-Welt, wo erwartete Ausgaben in Dateien gespeichert wurden. In Android überträgst du dieses Muster auf UI-Komponenten: Du definierst einmal den Soll-Zustand und gibst damit dem CI-System einen visuellen Ankerpunkt.
Snapshot-Tests ergänzen Unit- und Instrumentierungstests, ersetzen sie aber nicht. Sie prüfen ausschließlich, wie etwas aussieht – nicht ob es sich korrekt verhält. Deshalb lohnen sie sich vor allem dort, wo visuelle Konsistenz besonders teuer ist: geteilte Design-System-Komponenten, komplexe Zustände (Loading, Error, Empty) oder Screens, die nach jedem Sprint erneut manuell geprüft werden müssten.
Wie funktioniert es?
Paparazzi (Square) ist das bekannteste JVM-basierte Framework für Compose und View-basierte Layouts. Es rendert Composables oder Views vollständig auf dem JVM, ohne Emulator oder physisches Gerät:
class ButtonSnapshotTest {
@get:Rule
val paparazzi = Paparazzi(
deviceConfig = DeviceConfig.PIXEL_6,
theme = "Theme.MyApp"
)
@Test
fun primaryButton_default() {
paparazzi.snapshot {
PrimaryButton(label = "Speichern", onClick = {})
}
}
}
Beim ersten ./gradlew recordPaparazziDebug wird das Golden File unter src/test/snapshots/ abgelegt. Danach prüft ./gradlew verifyPaparazziDebug bei jedem Build, ob das aktuelle Rendering damit übereinstimmt.
Compose Screenshot Testing (AndroidX) verwendet denselben Ansatz, läuft jedoch als Instrumentierungstest auf einem Emulator oder Gerät und erzeugt daher realistischere Renderings – auf Kosten der Geschwindigkeit.
Der Workflow ist in beiden Fällen identisch:
- Record – Golden File erstellen oder nach einer gewollten Änderung aktualisieren.
- Verify – Im CI ausführen; bei Abweichung schlägt der Build fehl.
- Review – Diff-Bild im Pull Request inspizieren; Referenz nur bei gewollter Änderung aktualisieren.
Schritt 3 ist entscheidend: Das Framework kann nicht beurteilen, ob ein visueller Unterschied ein Fehler oder ein Feature ist. Diese Entscheidung trifft immer ein Mensch im Code Review.
In der Praxis
Wann Snapshot-Tests einsetzen? Nicht flächendeckend – sondern dort, wo eine visuelle Regression teuer ist:
- Ja: Atoms und Molecules deines Design-Systems (Buttons, Cards, Text-Stile), weil eine Änderung hier alle Screens betrifft.
- Ja: Screens mit schwer zu reproduzierenden Zuständen wie Skeleton-Loading oder leeren Listen.
- Nein: Screens mit häufig wechselnden Daten oder laufenden Animationen, weil Pixel-Vergleiche dort dauerhaft Falsch-Positive liefern.
Typische Stolperfalle – veraltete Golden Files: Teams, die Snapshot-Tests einführen, neigen dazu, fehlgeschlagene Tests schnell mit recordPaparazziDebug zu „reparieren”, ohne das Diff vorher zu prüfen. Damit verliert der Test jeden Wert. Stelle sicher, dass aktualisierte Golden Files im PR immer ein Diff-Artifact mitführen, damit Reviewer auf einen Blick sehen, was sich verändert hat.
Schriftarten und Sub-Pixel-Rendering: Paparazzi rendert mit einem systeminternen Font-Renderer. Weicht dieser von Compose Preview oder dem echten Gerät ab, entstehen konstante Sub-Pixel-Unterschiede, die den Test permanent brechen. Die Lösung ist entweder ein explizit konfigurierter Typeface oder eine kleine Toleranzschwelle:
val paparazzi = Paparazzi(
deviceConfig = DeviceConfig.PIXEL_6,
renderingMode = RenderingMode.NORMAL,
maxPercentDifference = 0.1 // 0,1 % Toleranz für Anti-Aliasing-Unterschiede
)
Fange klein an: Ein einziger Snapshot-Test für deine meistgenutzte Komponente ist wertvoller als hundert Tests für Screens, die sich wöchentlich ändern.
Fazit
Snapshot- und Golden-Tests entfalten ihren Wert nur dann, wenn du sie gezielt einsetzt und den Review-Prozess ernst nimmst. Füge heute einen Paparazzi-Test für die am häufigsten genutzte Komponente deines Projekts hinzu, lass ihn einmal recorden und dann im CI laufen. Ändere danach bewusst Schriftgröße oder Padding und beobachte, wie das Framework den Unterschied meldet und visualisiert. Wer diesen Kreislauf aus Record, Verify und Review verinnerlicht hat, fängt visuelle Regressionen zuverlässig ab, bevor sie den Nutzer erreichen.