Teststrategien

Mit automatisierten Tests können Sie die App-Qualität auf verschiedene Weise verbessern. So können Sie beispielsweise Validierungen durchführen, Regressionen erkennen und die Kompatibilität prüfen. Mit einer guten Teststrategie können Sie automatisierte Tests nutzen, um sich auf einen wichtigen Vorteil zu konzentrieren: die Produktivität der Entwickler.

Teams erzielen eine höhere Produktivität, wenn sie einen systematischen Testansatz mit Infrastrukturverbesserungen kombinieren. So erhalten Sie zeitnahes Feedback zum Verhalten des Codes. Eine gute Teststrategie erfüllt folgende Kriterien:

  • Probleme werden so früh wie möglich erkannt.
  • Sie werden schnell ausgeführt.
  • Sie gibt klare Hinweise, wenn etwas behoben werden muss.

Auf dieser Seite erfahren Sie, welche Arten von Tests Sie implementieren, wo Sie sie ausführen und wie oft.

Die Testpyramide

In modernen Anwendungen können Sie Tests nach Größe kategorisieren. Kleine Tests konzentrieren sich nur auf einen kleinen Teil des Codes und sind daher schnell und zuverlässig. Große Tests haben einen großen Umfang und erfordern komplexere Konfigurationen, die sich nur schwer verwalten lassen. Große Tests sind jedoch repräsentativer* und können auf einmal viel mehr Probleme aufdecken.

*Fidelity bezieht sich auf die Ähnlichkeit der Testlaufzeitumgebung mit der Produktionsumgebung.

Die Verteilung der Anzahl von Tests nach Umfang wird normalerweise in einer Pyramide visualisiert.
Abbildung 1. Die Verteilung der Anzahl der Tests nach Umfang wird in der Regel in einer Pyramide dargestellt.

Die meisten Apps sollten viele kleine und relativ wenige große Tests durchführen. Die Verteilung der Tests in jeder Kategorie sollte eine Pyramide bilden, wobei die Zahl der kleinen Tests die Basis und die weniger großen Tests die Basis bilden.

Kosten eines Bugs minimieren

Mit einer guten Teststrategie maximieren Sie die Produktivität der Entwickler und minimieren gleichzeitig die Kosten der Fehlersuche.

Sehen wir uns ein Beispiel für eine möglicherweise ineffiziente Strategie an. Hier wird die Anzahl der Tests nach Größe nicht zu einer Pyramide organisiert. Es gibt zu viele große End-to-End-Tests und zu wenige UI-Tests für Komponenten:

Eine Strategie, bei der viele Tests manuell und die Gerätetests nur über Nacht durchgeführt werden.
Abbildung 2: Eine top-heavy-Strategie, bei der viele der Tests manuell durchgeführt werden und die Gerätetests nur nachts ausgeführt werden.

Das bedeutet, dass vor dem Zusammenführen zu wenige Tests ausgeführt werden. Wenn ein Fehler auftritt, wird er möglicherweise erst bei den täglichen oder wöchentlichen End-to-End-Tests erkannt.

Es ist wichtig zu berücksichtigen, welche Auswirkungen dies auf die Kosten für die Identifizierung und Behebung von Fehlern hat und warum es wichtig ist, Ihre Testbemühungen auf kleinere und häufigere Tests zu konzentrieren:

  • Wenn der Fehler von einem Einheitentest erkannt wird, wird er normalerweise innerhalb weniger Minuten behoben, sodass die Kosten niedrig sind.
  • Bei einem End-to-End-Test kann es Tage dauern, bis derselbe Fehler gefunden wird. Dies könnte mehrere Teammitglieder in Anspruch nehmen, was die Produktivität insgesamt verringert und eine Veröffentlichung möglicherweise verzögert. Die Kosten für diesen Fehler sind höher.

Eine ineffiziente Teststrategie ist jedoch besser als gar keine Strategie. Wenn ein Fehler in die Produktion gelangt, dauert es oft Wochen, bis die Fehlerbehebung auf den Geräten der Nutzer verfügbar ist. Daher ist die Feedbackschleife die längste und teuerste.

Eine skalierbare Teststrategie

Die Testpyramide wurde traditionell in drei Kategorien unterteilt:

  • Einheitentests
  • Integrationstests
  • End-to-End-Tests

Diese Konzepte haben jedoch keine genauen Definitionen. Daher können Teams ihre Kategorien auch anders definieren, z. B. mit fünf Ebenen:

Testpyramide mit 5 Schichten mit den Kategorien Unittests, Komponententests, Funktionstests, Anwendungstests und Releasekandidatentests in aufsteigender Reihenfolge.
Abbildung 3: Eine Testpyramide mit 5 Schichten.
  • Ein Einheitstest wird auf dem Hostcomputer ausgeführt und prüft eine einzelne funktionale Logikeinheit ohne Abhängigkeiten vom Android-Framework.
    • Beispiel: Überprüfung von Abweichungen um eins bei einer mathematischen Funktion
  • Bei einem Komponententest wird die Funktionalität oder das Erscheinungsbild eines Moduls oder einer Komponente unabhängig von anderen Komponenten im System überprüft. Im Gegensatz zu Unit-Tests erstreckt sich der Bereich eines Komponententests auf höhere Abstraktionsebenen über einzelne Methoden und Klassen hinaus.
  • Bei einem Funktionstest wird die Interaktion von zwei oder mehr unabhängigen Komponenten oder Modulen überprüft. Funktionstests sind größer und komplexer und werden in der Regel auf Funktionsebene durchgeführt.
    • Beispiel: UI-Verhaltenstests, mit denen die Statusverwaltung auf einem Bildschirm überprüft wird
  • Bei einem Anwendungstest wird die Funktionalität der gesamten Anwendung in Form eines ausführbaren Binärprogramms überprüft. Das sind umfangreiche Integrationstests, bei denen ein debuggbarer Binärcode als Testsystem verwendet wird, z. B. ein Dev-Build, das Test-Hooks enthalten kann.
    • Beispiel: UI-Verhaltenstest zur Überprüfung von Konfigurationsänderungen in einem faltbaren Gerät, Lokalisierungs- und Bedienungshilfentests
  • Mit einem Release-Kandidatentest wird die Funktionalität eines Release-Builds überprüft. Sie ähneln Anwendungstests, mit der Ausnahme, dass das Binärprogramm minimiert und optimiert wird. Das sind umfangreiche End-to-End-Integrationstests, die in einer Umgebung ausgeführt werden, die der Produktionsumgebung so nahe wie möglich kommt, ohne dass die App öffentlichen Nutzerkonten oder öffentlichen Backends ausgesetzt wird.

Bei dieser Kategorisierung werden die Treue, die Zeit, der Umfang und die Isolationsstufe berücksichtigt. Sie können verschiedene Arten von Tests auf mehreren Ebenen durchführen. Die Anwendungstestebene kann beispielsweise Verhaltens-, Screenshot- und Leistungstests enthalten.

Aufgabenstellung

Netzwerkzugriff

Umsetzung

Build-Typ

Lebenszyklus

Einheit

Einzelne Methode oder Klasse mit minimalen Abhängigkeiten.

Nein

Lokal

Debug-fähig

Vor dem Zusammenführen

Komponente

Modul- oder Komponentenebene

Mehrere Kurse gleichzeitig

Nein

Lokal
Robolectric
Emulator

Debugfähig

Vor Zusammenführung

Funktion

Funktionsebene

Integration in Komponenten anderer Teams

Gemockt

Lokal
Robolectric
Emulator
Geräte

Debugfähig

Vor dem Zusammenführen

Anwendung

Anwendungsebene

Einbindung in Funktionen und/oder Dienste anderer Teams

Gemockter
Staging-Server
Produktionsserver

Emulator
Geräte

Debugfähig

Vor der Zusammenführung
Nach der Zusammenführung

Release-Kandidat

Anwendungsebene

Einbindung in Funktionen und/oder Dienste anderer Teams

Prod-Server

Emulator
Geräte

Minimierter Release-Build

Nach der Zusammenführung
Vor der Veröffentlichung

Testkategorie festlegen

Als Faustregel sollten Sie die unterste Schicht der Pyramide in Betracht ziehen, die dem Team das richtige Maß an Feedback geben kann.

Überlegen Sie zum Beispiel, wie Sie die Implementierung dieser Funktion testen können: die Benutzeroberfläche eines Anmeldevorgangs. Je nachdem, was Sie testen möchten, wählen Sie eine der folgenden Kategorien aus:

Testobjekt

Beschreibung dessen, was getestet wird

Testkategorie

Beispiel für einen Testtyp

Logik der Formularvalidierung

Eine Klasse, die die E-Mail-Adresse anhand eines regulären Ausdrucks validiert und prüft, ob das Passwortfeld eingegeben wurde. Es hat keine Abhängigkeiten.

Einheitentests

Test lokaler JVM-Einheiten

Verhalten der Benutzeroberfläche für das Anmeldeformular

Ein Formular mit einer Schaltfläche, die nur aktiviert wird, wenn das Formular validiert wurde

Komponententests

UI-Verhaltenstest, der mit Robolectric ausgeführt wird

Benutzeroberfläche des Anmeldeformulars

Ein Formular, das einer UX-Spezifikation folgt

Komponententests

Vorschau-Screenshot-Test erstellen

Integration mit dem Authentifizierungsmanager

Die Benutzeroberfläche, die Anmeldedaten an einen Authentifizierungsmanager sendet und Antworten empfängt, die verschiedene Fehler enthalten können.

Funktionstests

JVM-Test mit Fakes

Anmeldedialogfeld

Ein Bildschirm, auf dem das Anmeldeformular zu sehen ist, wenn die Anmeldeschaltfläche gedrückt wird.

Anwendungstests

UI-Verhaltenstest, der mit Robolectric ausgeführt wird

Wichtige User Journey: Anmeldung

Ein vollständiger Anmeldevorgang mit einem Testkonto auf einem Staging-Server

Release Candidate

End-to-End-Compose UI-Verhaltenstest, der auf dem Gerät ausgeführt wird

In einigen Fällen ist es subjektiv, ob etwas zu einer bestimmten Kategorie gehört. Es kann weitere Gründe geben, warum ein Test nach oben oder unten verschoben wird, z. B. Infrastrukturkosten, Instabilität und lange Testzeiten.

Die Testkategorie bestimmt nicht den Testtyp und nicht alle Funktionen müssen in jeder Kategorie getestet werden.

Manuelle Tests können auch Teil Ihrer Teststrategie sein. Üblicherweise führen QA-Teams Releasekandidatentests durch, können aber auch an anderen Phasen beteiligt sein. Zum Beispiel exploratives Testen auf Fehler in einer Funktion ohne Skript.

Testinfrastruktur

Eine Teststrategie muss von der Infrastruktur und den Tools unterstützt werden, damit Entwickler ihre Tests kontinuierlich ausführen und Regeln durchsetzen können, die das Bestehen aller Tests garantieren.

Sie können Tests nach Umfang kategorisieren, um zu definieren, wann und wo welche Tests ausgeführt werden. Hier ein Beispiel für das 5-Schichten-Modell:

Kategorie

Umgebung (wo)

Trigger (wenn)

Einheit

[Local][4]

Bei jedem Commit

Komponente

Lokal

Bei jedem Commit

Funktion

Lokal und Emulatoren

Vor dem Zusammenführen, bevor eine Änderung zusammengeführt oder eingereicht wird

Anwendung

Lokal, Emulatoren, 1 Smartphone, 1 faltbares Smartphone

Nach dem Zusammenführen, nachdem eine Zusammenführung oder Änderung eingereicht wurde

Releasekandidaten

8 verschiedene Smartphones, 1 faltbares Smartphone, 1 Tablet

Vor Veröffentlichung

  • Unit- und Komponententests werden für jeden neuen Commit im Continuous-Integration-System ausgeführt, jedoch nur für die betroffenen Module.
  • Alle Einheits-, Komponenten- und Funktionstests werden ausgeführt, bevor eine Änderung zusammengeführt oder eingereicht wird.
  • Anwendungstests werden nach dem Zusammenführen ausgeführt.
  • Release Candidate-Tests werden jede Nacht auf Smartphones, faltbaren Geräten und Tablets durchgeführt.
  • Vor einem Release werden Releasekandidaten-Tests auf einer großen Anzahl von Geräten ausgeführt.

Diese Regeln können sich im Laufe der Zeit ändern, wenn sich die Anzahl der Tests auf die Produktivität auswirkt. Wenn Sie beispielsweise die Tests auf eine nächtliche Taktung umstellen, können Sie die CI-Build- und Testzeiten verkürzen, aber auch die Feedbackschleife verlängern.