Teststrategien

Automatisierte Tests können 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 und sich auf einen wichtigen Vorteil konzentrieren: Entwicklerproduktivität.

Teams erreichen ein höheres Produktivitätsniveau, wenn sie einen systematischen Ansatz für Tests in Verbindung mit Infrastrukturverbesserungen verwenden. So erhalten Sie zeitnah Feedback zum Verhalten des Codes. Eine gute Teststrategie umfasst Folgendes:

  • Probleme werden so früh wie möglich erkannt.
  • Schnelle Ausführung
  • Sie erhalten klare Hinweise, wenn etwas behoben werden muss.

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

Die Testpyramide

Tests in modernen Anwendungen lassen sich nach Größe kategorisieren. Kleine Tests konzentrieren sich nur auf einen kleinen Teil des Codes, wodurch sie schnell und zuverlässig sind. Große Tests haben einen breiten Anwendungsbereich und erfordern komplexere Setups, die schwer zu verwalten sind. Bei großen Tests ist die Wahrscheinlichkeit, Probleme zu finden, jedoch höher.

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

Die Verteilung der Anzahl der Tests nach Umfang wird in der Regel in einer Pyramide dargestellt.
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 haben. Die Verteilung der Tests in den einzelnen Kategorien sollte eine Pyramide bilden, wobei die zahlreichen kleinen Tests die Basis und die weniger zahlreichen großen Tests die Spitze bilden.

Kosten eines Fehlers minimieren

Eine gute Teststrategie maximiert die Produktivität von Entwicklern und minimiert gleichzeitig die Kosten für das Auffinden von Fehlern.

Sehen Sie sich ein Beispiel für eine möglicherweise ineffiziente Strategie an. Hier ist die Anzahl der Tests nach Größe nicht in einer Pyramide angeordnet. Es gibt zu viele umfangreiche End-to-End-Tests und zu wenige UI-Komponententests:

Eine strategie, bei der viele Tests manuell durchgeführt werden und die Gerätetests nur nachts ausgeführt werden.
Abbildung 2. Eine strategie, bei der viele 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 vorliegt, wird er möglicherweise erst bei den nächtlichen oder wöchentlichen End-to-End-Tests erkannt.

Es ist wichtig, die Auswirkungen auf die Kosten für das Erkennen und Beheben von Fehlern zu berücksichtigen und die Testbemühungen auf kleinere und häufigere Tests auszurichten:

  • Wenn der Fehler durch einen Einheitentest erkannt wird, ist er in der Regel innerhalb von Minuten behoben. Die Kosten sind also gering.
  • Bei einem End-to-End-Test kann es Tage dauern, bis derselbe Fehler gefunden wird. Dazu sind möglicherweise mehrere Teammitglieder erforderlich, was die Gesamtproduktivität verringert und die Veröffentlichung verzögern kann. Die Kosten dieses Fehlers sind höher.

Eine ineffiziente Teststrategie ist jedoch immer noch besser als gar keine. Wenn ein Fehler in die Produktion gelangt, dauert es lange, bis die Korrektur auf den Geräten der Nutzer landet, manchmal Wochen. Der Feedback-Zyklus ist also am längsten und teuersten.

Eine skalierbare Teststrategie

Die Testpyramide wurde traditionell in drei Kategorien unterteilt:

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

Da diese Konzepte jedoch keine genauen Definitionen haben, können Teams ihre Kategorien unterschiedlich definieren, z. B. mit fünf Ebenen:

Eine 5-schichtige Testpyramide mit den Kategorien Einheitentests, Komponententests, Funktionstests, Anwendungstests und Release-Kandidatentests in aufsteigender Reihenfolge.
Abbildung 3: Eine 5-schichtige Testpyramide.
  • Ein Einheitentest wird auf dem Hostcomputer ausgeführt und überprüft eine einzelne funktionale Logikeinheit ohne Abhängigkeiten vom Android-Framework.
    • Beispiel: Überprüfen von Off-by-One-Fehlern in 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 Unittests erstreckt sich der Testbereich eines Komponententests auf höhere Abstraktionen über einzelnen Methoden und Klassen.
  • Bei einem Funktionstest wird die Interaktion von zwei oder mehr unabhängigen Komponenten oder Modulen überprüft. Feature-Tests sind größer und komplexer und werden in der Regel auf Feature-Ebene ausgeführt.
    • Beispiel: UI-Verhaltenstests, mit denen die Statusverwaltung auf einem Bildschirm geprüft wird
  • Bei einem Anwendungstest wird die Funktionalität der gesamten Anwendung in Form einer bereitstellbaren Binärdatei geprüft. Es handelt sich um umfangreiche Integrationstests, bei denen ein debuggbares Binärprogramm, z. B. ein Entwickler-Build, der Testhooks enthalten kann, als zu testendes System verwendet wird.
    • Beispiel: UI-Verhaltenstest zur Überprüfung von Konfigurationsänderungen auf einem faltbaren Gerät, Lokalisierungs- und Barrierefreiheitstests
  • Mit einem Releasekandidatentest wird die Funktionalität eines Release-Builds überprüft. Sie ähneln Anwendungstests, mit dem Unterschied, dass das Anwendungs-Binärprogramm minimiert und optimiert wird. Dabei handelt es sich um umfangreiche End-to-End-Integrationstests, die in einer Umgebung ausgeführt werden, die der Produktionsumgebung so nahe wie möglich kommt, ohne die App öffentlichen Nutzerkonten oder öffentlichen Back-Ends auszusetzen.

Bei dieser Kategorisierung werden Genauigkeit, Zeit, Umfang und Isolierungsgrad berücksichtigt. Sie können verschiedene Arten von Tests für mehrere Ebenen durchführen. Die Anwendungstestebene kann beispielsweise Verhaltens-, Screenshot- und Leistungstests enthalten.

Umfang

Netzwerkzugriff

Umsetzung

Build-Typ

Lebenszyklus

Einheit

Einzelne Methode oder Klasse mit minimalen Abhängigkeiten.

Nein

Lokal

Debuggable

Vor dem Zusammenführen

Komponente

Modul- oder Komponentenebene

Mehrere Kurse zusammen

Nein

Lokal
Robolectric
Emulator

Debuggable

Vor dem Zusammenführen

Funktion

Funktionsebene

Einbindung in Komponenten, die anderen Teams gehören

Mocked

Lokal
Robolectric
Emulator
Geräte

Debuggable

Vor dem Zusammenführen

Anwendung

Anwendungsebene

Integration mit Funktionen und/oder Diensten, die anderen Teams gehören

Mocked
Staging-Server
Produktionsserver

Emulator
Geräte

Debuggable

Vor der Zusammenführung
Nach der Zusammenführung

Releasekandidat

Anwendungsebene

Integration mit Funktionen und/oder Diensten, die anderen Teams gehören

Produktionsserver

Emulator
Geräte

Minimierter Release-Build

Nach dem Zusammenführen
Vor der Veröffentlichung

Testkategorie auswählen

Als Faustregel gilt, dass Sie die unterste Ebene der Pyramide in Betracht ziehen sollten, die dem Team das richtige Feedback geben kann.

Überlegen Sie sich beispielsweise, wie Sie die Implementierung dieser Funktion testen können: die Benutzeroberfläche eines Anmeldevorgangs. Je nachdem, was Sie testen möchten, wählen Sie unterschiedliche Kategorien aus:

Zu testendes Subjekt

Beschreibung des Tests

Testkategorie

Beispiel für einen Testtyp

Logik für die Formularvalidierung

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

Einheitentests

Lokaler JVM-Unittest

Verhalten der Benutzeroberfläche des Anmeldeformulars

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

Komponententests

UI-Verhaltenstest, der auf Robolectric ausgeführt wird

Darstellung der Benutzeroberfläche des Anmeldeformulars

Ein Formular, das einer UX-Spezifikation entspricht

Komponententests

Compose Preview Screenshot test

Integration in den Auth-Manager

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

Funktionstests

JVM-Test mit Fakes

Anmeldedialogfeld

Ein Bildschirm mit dem Anmeldeformular, wenn die Schaltfläche „Anmelden“ gedrückt wird.

Anwendungstests

UI-Verhaltenstest, der auf Robolectric ausgeführt wird

Wichtiges Nutzerverhalten: Anmeldung

Vollständiger Anmeldevorgang mit einem Testkonto auf einem Staging-Server

Releasekandidat

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

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

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

Manuelle Tests können ebenfalls Teil Ihrer Teststrategie sein. In der Regel führen QA-Teams Release-Candidate-Tests durch, sie können aber auch in anderen Phasen beteiligt sein. Beispiel: Exploratives Testen einer Funktion ohne Skript auf Fehler.

Testinfrastruktur

Eine Teststrategie muss durch Infrastruktur und Tools unterstützt werden, damit Entwickler ihre Tests kontinuierlich ausführen und Regeln durchsetzen können, die dafür sorgen, dass alle Tests bestanden werden.

Sie können Tests nach Umfang kategorisieren, um festzulegen, wann und wo welche Tests ausgeführt werden sollen. Beispiel für das 5-Schicht-Modell:

Kategorie

Umgebung (wo)

Trigger (wann)

Einheit

[Lokal][4]

Jedes Commit

Komponente

Lokal

Jedes 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 oder Einreichen einer Änderung

Releasekandidat

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, aber nur für die betroffenen Module.
  • Alle Unit-, Komponenten- und Funktionstests werden vor dem Zusammenführen oder Einreichen einer Änderung ausgeführt.
  • Anwendungstests werden nach dem Zusammenführen ausgeführt.
  • Release-Candidate-Tests werden jede Nacht auf einem Smartphone, einem faltbaren Gerät und einem Tablet ausgeführt.
  • Vor einem Release werden Release-Kandidaten auf einer großen Anzahl von Geräten getestet.

Diese Regeln können sich im Laufe der Zeit ändern, wenn die Anzahl der Tests die Produktivität beeinträchtigt. Wenn Sie beispielsweise Tests auf einen nächtlichen Rhythmus umstellen, können Sie die Build- und Testzeiten für die kontinuierliche Integration verkürzen, aber auch den Feedbackzyklus verlängern.