Logo marqably

marqably

Digitale Agentur

Testing in Flutter

Description

Simon Auer von marqably nimmt in seinem devjobs.at TechTalk das Thema von automatischen Softwaretests in Flutter aus verschiedenen Blickpunkten unter die Lupe.

Beim Videoaufruf stimmst Du der Datenübermittlung an YouTube und der Datenschutzerklärung zu.

Video Zusammenfassung

In "Testing in Flutter" zeigt Simon Auer, warum automatisierte Tests unverzichtbar sind: höhere Qualität, weniger manuelles Testen, sichereres Refactoring, bessere Dokumentation und unmittelbares Feedback – trotz eines anfänglichen Mehraufwands von 30–40 %. Er führt durch die Testpyramide (Unit-, Widget-, Integrationstests), empfiehlt Namenskonventionen und das AAA-Muster und demonstriert an einer einfachen Such-App konkrete Tests mit Findern, pumpWidget, pumpAndSettle und dem Integration‑Test‑Paket von Flutter. Zuschauer:innen lernen, wo sie Testaufwand priorisieren sollten und wie sie UI‑Komponenten und End‑to‑End‑Workflows zuverlässig automatisieren – Konzepte, die auch in anderen Frameworks vertraut wirken.

Automatisiertes Testen in Flutter: Unit-, Widget- und Integrationstests praxistauglich umsetzen – Erkenntnisse aus „Testing in Flutter“ von Simon Auer (marqably)

Kontext und Einordnung

In der Session „Testing in Flutter“ führte Simon Auer (CEO von marqably) sehr klar vor Augen, warum automatisierte Tests für jede App-Größe entscheidend sind – von Startup-Projekten bis zu Anwendungen großer Marken. Sein Anspruch: Qualität und Wartbarkeit haben absolute Priorität. Der Weg dorthin führt konsequent über automatisiertes Testen – nicht manuelles Klicken durch die App nach jeder Codeänderung, sondern Code, der Code prüft.

Aus unserer DevJobs.at-Perspektive war besonders überzeugend, wie Auer das Thema entkompliziert: Flutter bringt Test-Werkzeuge direkt mit, die Syntax ist vielen Frameworks ähnlich, und die Grundprinzipien (Unit-, Widget- und Integrationstests) sind übertragbar. Wer noch zögert, findet in dieser Session eine klare Anleitung, warum, wann und wie man testet – plus eine anschauliche Demo auf Basis einer kleinen Such-App mit Früchten.

Warum testen? Die wichtigsten Motive

Auer beginnt mit dem „Warum“ und bleibt praxisnah:

  • Qualität und Stabilität jederzeit prüfen:
  • Automatisierte Tests validieren alle Fälle, immer wieder und reproduzierbar. Wenn neue Features bereits funktionierende Pfade brechen, schlagen Tests an – sofort und zielgenau.
  • Weniger manuelles Testen:
  • Lässt man die Tests bei jedem Deployment laufen, reduziert sich der manuelle Aufwand spürbar. Idealerweise ist das anschließende händische Prüfen nur noch Bestätigung.
  • Mehr Fokus auf Edge Cases:
  • Wer Tests schreibt, denkt systematischer über Randfälle nach. Das steigert Robustheit und verhindert „blinde Flecken“.
  • Refactoring wird angenehmer:
  • „Output und Input bleiben gleich, nur die Implementierung ändert sich“ – genau dafür sind gute Tests da. Bricht etwas, merkt man es sofort.
  • Schutz vor Abhängigkeitsproblemen im Team:
  • Ändert jemand eine Methode, die andere Module verwenden, signalisiert ein Testbruch unmittelbar: Hier stimmt etwas nicht – bitte fixen.
  • Ergänzung zu manuellen Testern:
  • Auch mit einem Testteam bleibt Automatisierung sinnvoll. Sie spart Zeit, deckt Wissenslücken über interne Details ab und erhöht die Testtiefe.
  • Vertrauen und Ruhe bei Deployments:
  • „Deployen und nicht groß nachdenken“ – Tests schaffen diese Gelassenheit. Fällt etwas durch, liefert die Pipeline sofort Feedback.
  • Tests als Dokumentation:
  • Besonders betont Auer die Rolle von Tests als lebende Beispiele: Sie zeigen direkt im Code, wie Methoden, Widgets und Features korrekt verwendet werden und welche Varianten möglich sind.

Wann testen? Praktisch immer

Die kurze Antwort: immer – außer der Code wird nie wieder angefasst. Gerade wenn sich ein Modul selten ändert, sind Tests der Wissensspeicher des Systems. Auer ist offen: Tests kosten Zeit. Rechnet mit 30–40 % Mehraufwand bei der Implementierung. Langfristig spart ihr aber massig Stunden, weil Bugs schneller gefunden werden, weniger manuell regressionsgetestet werden muss und das Einarbeiten in alte Bereiche entfällt.

Die Testpyramide in Flutter

Auer strukturiert in drei Ebenen, die sich von günstig/schnell (unten) zu teurer/aufwendiger (oben) steigern:

  1. Unit-Tests
  • Günstig, schnell, sehr wartbar; testen Funktionen, Klassen und Services mit klarem Input/Output.
  1. Widget-Tests
  • Prüfen UI-Komponenten (Widgets) isoliert; überschaubarer Aufwand, gelegentlich mehr Pflege notwendig.
  1. Integrationstests
  • End-to-End-Flows mit „echten“ Interaktionen (Tippen, Scrollen, Navigieren). Mächtig, aber wartungsintensiv und ressourcenhungrig.

Es gibt darüber hinaus weitere Testarten (z. B. Accessibility-, Akzeptanz-, Performance-Tests). In dieser Session fokussiert Auer bewusst auf die drei in Flutter typischen Ebenen.

Prinzipien für gute Tests in Flutter

Auer teilt drei pragmatische Leitlinien, die wir direkt übernehmen würden:

  • Saubere Namenskonventionen:
  • Testdateien spiegeln ihre Pendants in lib wider und enden auf „_test.dart“. Orte sie unter test/.
  • AAA-Muster (Arrange – Act – Assert):
  • Arrange: Testdaten und -umgebung vorbereiten.
  • Act: Die zu prüfende Aktion ausführen (Methode aufrufen, Widget rendern, Button tippen).
  • Assert: Erwartetes Verhalten überprüfen.
  • Testgetriebene Entwicklung (TDD) – so oft wie sinnvoll:
  • Idealerweise schreibt ihr Erwartungen vor der Implementierung. Das hilft, falsche Positivfälle zu vermeiden und zwingt zu klaren Anforderungen.

Testarten im Detail

Unit-Tests: Logik wie durch eine Blackbox prüfen

  • Fokus: Klassen, Services und Funktionen mit deterministischem Input/Output.
  • Eigenschaften: schnell, lesbar, unabhängig vom Rest der App.
  • Antipattern: Keine Implementierungslogik im Test schreiben, um Implementierungslogik zu testen. Sonst testet ihr den Test statt das Ziel.
  • Beispiel aus der Session: Eine Methode zur Ergebnisermittlung (getSearchResults), die eine hart codierte Früchte-Liste filtert und passende Einträge zurückliefert – etwa bei „berry“ die Treffer „strawberries“ und „raspberries“.
  • Gute Dokumentationswirkung: Ein präziser Unit-Test zeigt, was rein- und rausgeht. Neue Teammitglieder sehen sofort, wie die API gedacht ist.

Widget-Tests: Verhalten und Struktur einzelner UI-Teile

  • Fokus: Einzelne Widgets (in anderen Frameworks: Komponenten) isoliert rendern und prüfen.
  • Keine zusätzlichen Widgets nur zum Testen bauen – testet die Einheit, nicht die Umgebung.
  • Golden Tests existieren in Flutter: Pixelgenaue Snapshots, um Rendering-Änderungen zu erkennen. Auer erwähnt sie, fokussiert hier aber bewusst auf Verhaltensprüfungen (z. B. was ein Button tut, nicht welche Farbe er hat).
  • Typischer Ablauf: Widget per „pump“ auf einer unsichtbaren Testfläche rendern, Finders verwenden (Text, Typen, etc.), Erwartungen setzen (z. B. ein bestimmter Text existiert genau einmal).
  • Beispiele aus der Demo:
  • Wenn die Suchanfrage leer ist, erscheint der Hinweis „please enter a search term“.
  • Wenn die Suchanfrage nicht leer ist, erscheinen Listeneinträge (ListTiles) mit Treffern.

Integrationstests: Nutzerflüsse wie ein Mensch ausführen

  • Fokus: Ganze Workflows mit echten Interaktionen: App starten, durch Screens navigieren, Elemente finden, Tippen/Scrollen/Sichtbarkeit sicherstellen.
  • Syntax ähnelt Widget-Tests; die Tests laufen jedoch gegen eine „echte“ App-Instanz (Emulator oder Headless-Umgebung) und benötigen das Integration-Test-Paket von Flutter.
  • Aufwand: Höher in Entwicklung und Wartung. Änderungen an UI/Flows erfordern Testanpassungen. Auer empfiehlt sie für besonders wichtige Kernpfade (z. B. Login).
  • Ablauf-Begriffe aus der Session:
  • App initialisieren, Widget „pumpen“, Text in Felder eingeben, „pump and settle“ zum Re-Rendern, Sichtbarkeit von Elementen sicherstellen, Elemente antippen.
  • Geeignet für CI/CD-Pipelines: Auer erwähnt, dass die ressourcenintensiven Integrationstests oft getrennt organisiert werden und in der Pipeline gegen Emulatoren oder Headless-Chrome laufen.

Die Demo-App: Suchfeld + Ergebnisliste

Um die Testarten greifbar zu machen, präsentiert Auer eine kleine Flutter-App:

  • Aufbau:
  • Ein einfaches UI mit Suchfeld oben und Ergebnissen unten.
  • Eine Liste von Früchten dient als Datenbasis.
  • Verhalten:
  • Leere Eingabe: Es erscheint der Hinweis „please enter a search term“.
  • Eingabe „berry“: Es finden sich passende Beeren („strawberries“, „raspberries“).
  • Wichtige Widgets:
  • „Super Search“ als übergeordnetes UI.
  • „Super Search Results“ als zentrales Test-Target: Es nimmt eine Query entgegen und zeigt entsprechend entweder den Hinweis oder eine Liste an. Die Logik getSearchResults filtert die Einträge.

Diese kleine App deckt alle drei Testebenen ab und zeigt, wie sich Tests als „sprechende Beispiele“ lesen: Welche Eingaben führen zu welchen UI-Zuständen und Ergebnissen?

Unit-Test in der Demo: Ergebnislogik prüfen

  • Ziel: getSearchResults erhält eine Zeichenkette und filtert die Liste der Früchte.
  • Testidee: Für die Query „berry“ sollten zwei Treffer zurückkommen – „strawberries“ und „raspberries“.
  • Struktur: AAA-Muster mit klarer Beschreibung des Tests. Das vereinfacht Fehlersuche und dokumentiert die beabsichtigte Verwendung.
  • Guter Praxis-Tipp aus der Session: Tests gezielt auch einmal „rot“ laufen lassen. Ein absichtlicher Bruch prüft, ob der Test wirklich anschlägt und nicht fälschlich immer grün ist.

Widget-Tests in der Demo: UI-Zustand je nach Eingabe

  • Fall 1: Leere Query
  • Erwartung: Der Hinweistext „please enter a search term“ erscheint genau einmal.
  • Umsetzung: Widget „pumpen“, per Finder nach dem Text suchen, Erwartung setzen.
  • Fall 2: Nicht-leere Query
  • Erwartung: Mindestens ein ListTile wird gerendert (da es Treffer gibt).
  • Umsetzung: Widget „pumpen“ mit passender Query, nach Widgets eines bestimmten Typs (ListTile) suchen, Erwartung setzen.

Die Tests bleiben bewusst unabhängig und greifen nur auf das zu testende Widget zu. Ergebnis: Gute Lesbarkeit und geringe Kopplung.

Integrationstest in der Demo: Tippen, Eingeben, Prüfen

  • Ziel: Einen Mini-Flow wie ein Nutzer durchspielen – App starten, Zustand ohne Eingabe prüfen, Text eingeben, UI neu rendern lassen, Treffer prüfen und ggf. einen Eintrag sichtbar machen und antippen.
  • Besondere Schritte:
  • App/Framework initialisieren und das eigentliche App-Widget „pumpen“.
  • Text in das Suchfeld eingeben und mit „pump and settle“ den neuen Zustand rendern.
  • Per Finder prüfen, ob ListTiles vorhanden sind.
  • Sichtbarkeit erzwingen, um weiter unten liegende Elemente erreichen zu können, und Elemente antippen.
  • Praxis-Hinweis: Integrationstests kosten mehr, lohnen sich aber für Business-kritische Flüsse. Auer führt an, dass sie in separaten Ordnern geführt und in der Pipeline gegen Emulatoren oder Headless-Umgebungen laufen können.

Tooling und Workflow

  • VS Code-Unterstützung:
  • Nützliche DevTools-Funktion „go to test“/„go to implementation“: Erleichtert das Wechseln zwischen Produktiv- und Testdatei und legt fehlende Testdateien mit korrekter Struktur an.
  • Testlauf und Feedback:
  • Flutter liefert klare, aussagekräftige Fehlermeldungen mit Erwartung vs. Ist-Zustand. Das beschleunigt Debugging und Testhygiene.
  • Ordnerstruktur:
  • Produktivcode unter lib/, Tests unter test/. Integrationstests oft separat, um deren Laufzeitkosten gezielt in der Pipeline zu steuern.

Wartung, Stabilität und Fokus

  • Kapselung und Unabhängigkeit:
  • Unit- und Widget-Tests sollen nicht von unnötigen Abhängigkeiten leben. Testet die Einheit, nicht das Framework.
  • Keine Test-Logik, die wiederum getestet werden muss:
  • Sonst baut ihr Test-Pyramiden über Test-Pyramiden – und verliert Fokus auf die eigentliche App.
  • Integrationstests selektiv einsetzen:
  • Sie sind wertvoll, aber empfindlicher gegenüber UI-Änderungen. Nutzt sie für Kernpfade (z. B. Login, Checkout, kritische Formulare).
  • Kombi-Strategie:
  • Automatisierte Tests plus manuelles Testen ergänzen sich. Was die Pipeline zuverlässig prüft, muss manuell nur noch nachgewiesen werden – idealerweise ohne Überraschungen.

TDD im Alltag: Dos und Don’ts

  • Sinnvoll, wenn Anforderungen klar formulierbar sind:
  • Erwartetes Verhalten zuerst beschreiben, dann implementieren. Das reduziert die Gefahr falscher Positivfälle („Test ist grün, aber prüft das Falsche“).
  • Realismus bewahren:
  • TDD geht nicht immer – Auer sagt das explizit. Wo es passt, bringt es Disziplin in Design und Schnittstellen.
  • Negativfälle nicht vergessen:
  • Tests sollen nicht nur bestätigen, sondern auch abgrenzen. Was darf nicht passieren? Welche Eingaben führen zu keinem Ergebnis?

Checkliste für den Start in Flutter-Tests

  • Unit-Tests
  • Für jede Funktion mit klarem Input/Output mindestens einen Positiv- und einen Negativfall.
  • Keine zusätzliche Logik im Test – nur Arrangieren, Ausführen, Prüfen.
  • Widget-Tests
  • Widgets isoliert rendern und beobachtbare Signale prüfen (Texte, Typen, Anzahlen).
  • Golden Tests bei visuell stabilen Komponenten erwägen.
  • Integrationstests
  • Kritische Nutzerflüsse definieren: z. B. Login, Suchen, Checkout.
  • Sichtbarkeit erzwingen, Pump-Zyklen korrekt nutzen („pump and settle“), Navigation prüfen.
  • Struktur & Tools
  • Spiegelstruktur lib/ → test/; Dateinamen mit „_test.dart“.
  • VS Code-Shortcuts für schnelle Navigation nutzen.
  • Integrationstests in separatem Ordner für gezielte Pipeline-Steuerung.
  • Kultur
  • Tests als Dokumentation behandeln: ausdrucksstarke Beschreibungen, klare Arrange/Act/Assert-Kommentare.
  • Regelmäßig Tests absichtlich scheitern lassen, um ihre Wirksamkeit zu verifizieren.

Fazit: Tests als Qualitäts- und Wissensanker

„Testing in Flutter“ von Simon Auer (marqably) zeigt: Automatisiertes Testen ist kein Luxus, sondern das Rückgrat robuster Flutter-Apps. Unit-, Widget- und Integrationstests greifen ineinander – von schneller Logikprüfung über UI-Verhalten bis zu realistischen Nutzerflüssen. Die Vorteile sind handfest: weniger manuelles Testen, mehr Abdeckung, bessere Dokumentation, reibungsloseres Refactoring und mehr Vertrauen beim Deployment.

Besonders stark ist die Botschaft, dass Tests Wissen konservieren. Sie beschreiben, wie Komponenten zu benutzen sind, welche Ergebnisse erwartet werden und was nicht passieren darf. Kombiniert man dies mit einer pragmatischen Pyramidenstrategie, sauberer Struktur, AAA-Muster und (wo sinnvoll) TDD, entsteht eine Test-Suite, die Entwicklung beschleunigt statt bremst.

Die kleine Früchte-Suche mag simpel wirken – gerade dadurch transportiert sie die Essenz: Tests sind leichtgewichtig genug, um Spaß zu machen, und mächtig genug, um echte Qualität zu liefern. Wer Flutter im Einsatz hat, kann mit den gezeigten Bausteinen unmittelbar starten. Und wer bereits testet, bekommt mit Auers Klarheit und Praxisblick wertvolle Anregungen für noch bessere Test-Suites.

„Wenn die Tests grün sind, ist es das beste Gefühl der Welt.“ – Dieser Satz aus der Session bleibt hängen. Er fasst zusammen, was gute Test-Praxis in Flutter leistet: Verlässlichkeit, Geschwindigkeit und das gute Gefühl, jederzeit mit Vertrauen deployen zu können.

Weitere Tech Lead Stories